Skip to content

Migrating from PlanetScale to Layerbase

10 min readPlanetScaleMySQLMariaDBDatabases

Short version: you move to plain managed MySQL, or MariaDB if you prefer, and because a PlanetScale Vitess database speaks the MySQL wire protocol your tables, indexes, and queries port directly. The copy is one service token in the wizard at layerbase.com/migrate/planetscale, which mints a short-lived read-only password, copies schema and data server-side, then deletes the password; by hand it is a mysqldump with --set-gtid-purged=OFF. The app-side change is the driver: @planetscale/database gives way to mysql2 or whatever standard MySQL client you already use elsewhere, which is one line on a Node runtime. What does not port is PlanetScale's branching and deploy-request workflow, since that is platform machinery rather than data. What you get back is real foreign keys, which Vitess never enforced.

PlanetScale built a genuinely good product on Vitess: horizontal scale, branching, online schema changes. They have since added a Postgres offering alongside it, so "PlanetScale" now means two engines rather than one. But the pricing has moved steadily up-market. The free Hobby tier was retired in 2024, and every Vitess SKU is a 3-node HA cluster, so the smallest MySQL database on the price list is $39 a month before storage, backups, egress, or extra replicas. For a side project that used to run on Hobby, that is a real bill where there was none.

The good news: on the Vitess side it is still MySQL under the hood, and your data is portable either way. PlanetScale bills a cluster size plus storage, backups, egress, and any extra replicas or dedicated PgBouncer on top; if you would rather pay one flat monthly figure that includes those, 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.

PlanetScale rates verified 2026-08-26 from planetscale.com/pricing: no free tier, Vitess starts at PS-10 non-metal for $39 a month as a 3-node HA cluster, and the $5 PS-5 entry price everyone quotes is a single-node Postgres SKU, not a MySQL one.

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. It is written for a PlanetScale MySQL database. If yours is PlanetScale Postgres, that is standard Postgres and the move is the same shape but simpler: a connection-string copy into Layerbase Postgres, with no driver swap and no foreign-key story to unwind.

Just want it done? Start at layerbase.com/migrate/planetscale. You sign in, the wizard opens with PlanetScale already selected, and it copies your schema and data into managed MySQL (or MariaDB) using a short-lived read-only password it mints and then deletes: read-once, nothing written back to PlanetScale. For a PlanetScale Postgres database, pick the Other Postgres source instead and paste its connection string. The rest of this post is that same migration explained step by step, plus the manual path.

Contents

What Actually Changes

A PlanetScale Vitess database 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 TABLE works 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 the Layerbase CLI

Before touching production, run MySQL locally and migrate into it so you can compare. The fastest way is the Layerbase CLI (formerly SpinDB): one CLI, no Docker, no manual install. (What is the Layerbase CLI?)

Install the Layerbase CLI:

bash
npm i -g layerbase    # npm
pnpm add -g layerbase # pnpm

Create and start a MySQL instance:

bash
lbase create planetscale-migration -e mysql --start

Open a shell on it:

bash
lbase connect planetscale-migration

You'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 instance above), mysqldump works, with two Vitess-specific flags:

bash
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:

bash
# Local instance
mysql -h 127.0.0.1 -P $(lbase 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.sql

Grab 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):

ts
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):

ts
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:

prisma
// 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:

sql
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 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 mysql2 use a pool and set a sane connectionLimit; serverless deployments should use a small pool per instance or a pooler.
  • Re-check AUTO_INCREMENT values. 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 three-node cluster minimum, onto an engine you can read and run.

FAQ

Do I have to take the app down to move?

Not for the copy. Both paths read from PlanetScale and write nothing back: the wizard uses a short-lived read-only password on a branch, and the manual mysqldump runs with --single-transaction for a consistent snapshot without locking. The only moment that needs care is the cutover, because writes that land on PlanetScale after the dump started will not be in the copy. Rehearse against the local instance, then take a short write pause when you swap the connection string.

Does all of my data come across?

Everything that is actually MySQL does: tables, indexes, rows, views, and stored routines all restore from the dump. What does not come across is the PlanetScale platform layer around it, meaning branches, deploy requests, and connection passwords. Check AUTO_INCREMENT values on tables you write to immediately after the restore, since that is the one detail worth verifying rather than assuming.

What replaces deploy requests and PlanetScale branching?

Layerbase has database branching, so the branch-per-preview habit survives, but the deploy-request review flow around schema changes does not port one-to-one. On standard MySQL a schema change is ALTER TABLE, run through whatever migration tool your framework already ships. If that review workflow is the main reason you are on PlanetScale, weigh it before moving, because it is the piece you would be rebuilding.

I deploy to an edge runtime. Can I still connect?

@planetscale/database exists because some edge platforms cannot open raw TCP connections, so this is worth checking before you start. On a Node runtime, which includes most Next.js apps, mysql2 connects directly and the swap is trivial. If a specific path genuinely runs at the edge, put a small API route on a Node runtime in front of it and let that route talk to MySQL over TCP.

Is this cheaper, and is there still a free option?

PlanetScale has no free tier since Hobby was retired, and because every Vitess SKU is a 3-node HA cluster the smallest MySQL database on their list is $39 a month before storage, backups, egress, and replicas. Layerbase MySQL is a flat per-instance price with those included, and the pricing page has the current numbers. If you spot a $5 PlanetScale figure quoted somewhere, that is PS-5 on the Postgres side, a different product from the Vitess database this post is about.

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 the Layerbase CLI:

bash
lbase stop planetscale-migration    # Stop the server
lbase start planetscale-migration   # Start it again
lbase url planetscale-migration     # Print the connection URL
lbase list                          # See all your instances

The Layerbase CLI 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.