Migrating from PlanetScale to Layerbase
PlanetScale built a genuinely good product on Vitess: horizontal scale, branching, online schema changes. But the pricing has moved steadily up-market. The free Hobby tier was retired in 2024, and the cheapest paid plan now starts in the tens of dollars a month, before you add read replicas or storage. For a side project or a small app, that is a lot for a MySQL database.
The good news: under the hood it is still MySQL, and your data is portable. If you want plain managed MySQL with flat per-instance pricing and no per-row metering, moving to Layerbase Cloud is mostly a data copy and a driver swap. You also get one thing back that PlanetScale takes away: real foreign keys.
This guide covers what actually changes, how to stand up MySQL locally to test against, how to copy your data (one service token, or by hand), and the small app-side change.
Contents
- What Actually Changes
- Set Up MySQL Locally with SpinDB
- Copy Your Data
- Swap the Driver in Your App
- Foreign Keys, Back Again
- What to Test
- The Managed Path: Layerbase Cloud
What Actually Changes
PlanetScale speaks the MySQL wire protocol, so most of your stack does not care where the database lives. Three things are worth knowing about:
- The connection. PlanetScale pushed the serverless HTTP driver (
@planetscale/database) for edge runtimes. Layerbase MySQL is a standard MySQL server, so you connect with a normal client (mysql2,mysqlclient, JDBC, whatever you already use elsewhere) over a TLS connection string. If you're on a normal Node/Python/Go server, this is a one-line change. If you're on a serverless/edge runtime that needs HTTP, see the driver section below. - Vitess constraints go away. PlanetScale runs Vitess, which historically did not support foreign keys, so most PlanetScale apps disabled them (Prisma's
relationMode = "prisma", Drizzle without FK constraints) and enforced relationships in application code. Standard MySQL has foreign keys, so you can turn them back on. - Branching is a different model. PlanetScale's database branching + deploy requests are specific to their platform. Layerbase has its own database branching, but if you lean heavily on PlanetScale's schema-change workflow, that is the piece that does not port one-to-one. Plain
ALTER TABLEworks as it does on any MySQL.
Everything else (your tables, indexes, queries, stored data) is standard MySQL and copies straight over.
Set Up MySQL Locally with SpinDB
Before touching production, run MySQL locally and migrate into it so you can compare. The fastest way is SpinDB: one CLI, no Docker, no manual install. (What is SpinDB?)
Install SpinDB:
npm i -g spindb # npm
pnpm add -g spindb # pnpmCreate and start a MySQL instance:
spindb create planetscale-migration -e mysql --startGrab the connection URL:
spindb connect planetscale-migrationYou're now in a mysql> shell against a local MySQL, ready to receive a dump.
Copy Your Data
Two paths, depending on how much you want to automate.
The managed wizard (one service token)
On Layerbase Cloud, the create flow does the copy for you. Choose Migrating from another platform, pick PlanetScale, and paste a service token and its ID (PlanetScale dashboard, then Settings, then Service tokens; the token needs an org-level read-databases permission). It lists your databases; for the one you pick it mints a short-lived read-only password on a branch, copies the schema and data into a fresh managed MySQL, then revokes the password. You never handle a connection string, and nothing is left behind on the PlanetScale side.
By hand with mysqldump
If you'd rather do it yourself (or into the local SpinDB instance above), mysqldump works, with two Vitess-specific flags:
mysqldump \
--host=<your-db>.<region>.psdb.cloud \
--user=<username> --password=<password> \
--single-transaction \
--set-gtid-purged=OFF \
--no-tablespaces \
<database_name> > dump.sql--single-transaction gives a consistent snapshot without locking. --set-gtid-purged=OFF is required because Vitess reports GTID state that a plain MySQL import doesn't want. Then load it into your target:
# Local SpinDB instance
mysql -h 127.0.0.1 -P $(spindb port planetscale-migration) -u root <database_name> < dump.sql
# Or a managed Layerbase MySQL (connection string from Quick Connect)
mysql -h <your-host>.cloud.layerbase.dev -P <port> -u layerbase -p<password> --ssl <database_name> < dump.sqlGrab a PlanetScale connection password from the dashboard's "Connect" panel (or mint a dedicated one for the dump and delete it afterward).
Swap the Driver in Your App
If your app connects with a normal MySQL client already, you only change the connection string. The change that matters is for apps using PlanetScale's serverless HTTP driver.
Before (PlanetScale serverless driver):
import { connect } from '@planetscale/database'
const conn = connect({ url: process.env.DATABASE_URL })
const results = await conn.execute('select * from users where id = ?', [id])After (standard MySQL with mysql2):
import mysql from 'mysql2/promise'
const pool = mysql.createPool(process.env.DATABASE_URL) // mysql://user:pass@host:port/db?ssl={"rejectUnauthorized":true}
const [rows] = await pool.execute('select * from users where id = ?', [id])Same SQL, same placeholders. If you're on Drizzle, switch the driver from drizzle-orm/planetscale-serverless to drizzle-orm/mysql2. On Prisma, change the datasource provider to mysql and point url at the new connection string.
A note on edge runtimes: @planetscale/database exists because some edge platforms can't open raw TCP connections. If you deploy to a Node runtime (most apps, including Next.js with the Node runtime), mysql2 is the simpler choice. If you specifically need HTTP-based access from an edge function, keep that call path behind a small API route on a Node runtime that talks to MySQL over TCP.
Foreign Keys, Back Again
This is the upgrade people forget. Because Vitess didn't enforce foreign keys, most PlanetScale apps turned relational integrity off:
// PlanetScale: relationships enforced in app code only
datasource db {
provider = "mysql"
relationMode = "prisma"
}On standard MySQL you can drop relationMode = "prisma" and let the database enforce foreign keys again, which means no more orphaned rows from a missed application-level check:
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;If you previously relied on relationMode = "prisma" indexes, real foreign keys create the backing indexes for you. This is a quiet but real correctness win from leaving Vitess.
What to Test
- Run your test suite against the local SpinDB MySQL. Standard MySQL is a superset of what Vitess allowed, so queries that worked on PlanetScale work here. The things to watch are anywhere you worked around Vitess.
- Check any raw SQL that assumed no foreign keys. Re-enabling FKs can surface inserts that were silently creating orphans. That's the constraint doing its job; fix the insert order or add the missing parent rows.
- Confirm connection pooling. PlanetScale's HTTP driver hid connection management. With
mysql2use a pool and set a saneconnectionLimit; serverless deployments should use a small pool per instance or a pooler. - Re-check
AUTO_INCREMENTvalues. After a dump/restore, confirm sequences resume above your max id (a fresh import usually handles this, but verify on tables you write to immediately).
The Managed Path: Layerbase Cloud
The do-it-yourself version above is worth running once to understand what moved. When you want managed MySQL you don't operate yourself, Layerbase Cloud runs it with TLS, backups, and a dashboard, on flat per-instance pricing rather than PlanetScale's metered model. Prefer MariaDB? It's a click away and the same migration path applies (a MySQL dump restores straight into MariaDB).
And the migration is the wizard described above: Migrating from another platform, paste a PlanetScale service token, pick the database, and it copies everything across server-side and hands you a standard MySQL connection string for your app. Off the per-row meter, onto an engine you can read and run.
Wrapping Up
Leaving PlanetScale is mostly mechanical: it's MySQL, so your tables and queries port directly. The real changes are swapping the serverless driver for a standard MySQL client (one line on a Node runtime) and, if you want it, turning foreign keys back on now that you're off Vitess. The data copy is either one service token through the managed wizard, or a mysqldump with --set-gtid-purged=OFF.
Manage your local MySQL instance with SpinDB:
spindb stop planetscale-migration # Stop the server
spindb start planetscale-migration # Start it again
spindb url planetscale-migration # Print the connection URL
spindb list # See all your instancesSpinDB handles 20+ database engines, so your MySQL can sit next to Postgres, Redis, or Meilisearch while you verify the move. Layerbase Desktop wraps the same thing in a GUI on macOS. For the MySQL-vs-MariaDB decision, see MySQL vs MariaDB.