Migrating from Turso to Layerbase
Short version: you move to managed libSQL, which is the same sqld server, so your tables, indexes, SQL, transactions, and your @libsql/client code all come along untouched. The wizard reads your database over HTTP from a Turso database URL plus an auth token and copies the schema and data with every SQLite type preserved; by hand it is a .dump from the Turso shell loaded into the new database. Cutover is two environment variables and a redeploy. The thing that actually changes is the pricing model, flat per instance instead of a meter on rows read and rows written, which also means a fixed memory and CPU budget rather than a request allowance, so size a write-heavy workload accordingly.
Turso made libSQL easy to host: a managed SQLite-compatible database you reach over the network, with a generous free tier. The catch shows up on real workloads, because the meter runs per row read and per row written. A busy app, an analytics query that scans a table, or a write-heavy job (a trading bot logging ticks, say) burns through the monthly allowance faster than you would expect, and the usual fix is to jump into a higher-limit tier rather than a flat price you can forecast.
If you would rather pay a flat per-instance price for the same database, libSQL on Layerbase Cloud is a direct swap. It is the same libSQL server (sqld) under the hood, so your schema, your SQL, and your @libsql/client code all come along. The only real work is copying your data and pointing the client at a new URL.
Just want it done? Start at layerbase.com/migrate/turso. You sign in, the wizard opens with Turso already selected, and it copies your schema and data into managed libSQL over HTTP with every SQLite type preserved: read-once, nothing written back to Turso. The rest of this post is that same migration explained step by step, plus the manual path.
Contents
- What Actually Changes
- Set Up libSQL Locally with the Layerbase CLI
- Copy Your Data
- Point Your App at Layerbase
- What to Test
- FAQ
- The Managed Path: Layerbase Cloud
What Actually Changes
libSQL is libSQL on both ends, so most of your stack is untouched. Two things differ:
- The pricing model. Turso meters per row read and written (with fixed-plan options on top). Layerbase libSQL is flat per instance: the bill is the same whether you read a thousand rows a month or a billion. For anything past a hobby workload, this is the whole reason to move.
- The connection. You point
@libsql/clientat a new URL and auth token. That is usually a one-line env change.
What does not change: your tables, indexes, SQL, transactions, and the SQLite semantics your app relies on. The client library stays the same.
One honest note for write-heavy workloads: a flat instance has a fixed memory and CPU budget instead of a request meter. The difference is that you can see it and size it. Layerbase warns you as a database approaches its memory limit and lets you move up a tier or add headroom, rather than capping you at an opaque request count. If you are running something write-intensive, start on a plan with real headroom (Pro) rather than the entry tier.
Set Up libSQL Locally with the Layerbase CLI
Stand up libSQL locally first so you can load your dump and verify it before touching anything in production. The Layerbase CLI (formerly SpinDB) runs it with one CLI, no Docker. (What is the Layerbase CLI?)
Install the Layerbase CLI:
npm i -g layerbase # npm
pnpm add -g layerbase # pnpmCreate a libSQL instance and start it:
lbase create turso-migration -e libsql --startGet its URL:
lbase url turso-migrationhttp://127.0.0.1:8080You now have a local libSQL server to load your data into and test against.
Copy Your Data
libSQL speaks SQLite, so the cleanest path is a SQL dump. Export your Turso database to a .sql file:
turso db shell your-database ".dump" > turso-dump.sqlThat file is plain SQL: CREATE TABLE statements followed by INSERTs. As a sanity check, a tiny schema looks like this:
CREATE TABLE trades (
id INTEGER PRIMARY KEY,
symbol TEXT NOT NULL,
side TEXT NOT NULL,
price REAL NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO trades (symbol, side, price, created_at)
VALUES ('BTC-USD', 'buy', 64250.0, '2026-06-28T17:00:00Z');Load the dump into your local libSQL instance to confirm it restores cleanly:
lbase shell turso-migration < turso-dump.sqlVerify the row counts match what Turso reported:
SELECT count(*) FROM trades;count(*)
--------
1Once the local copy checks out, you load the same dump into your Layerbase database (next section).
Point Your App at Layerbase
Create a libSQL database on Layerbase Cloud. The dashboard gives you a connection URL (an https://...cloud.layerbase.dev host) and an auth token. Load your dump into it the same way, then change your app's connection.
If your code looks like this on Turso:
import { createClient } from '@libsql/client'
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
})The only change is the two environment variables:
# Before (Turso)
TURSO_DATABASE_URL=libsql://your-db.turso.io
TURSO_AUTH_TOKEN=...
# After (Layerbase)
LIBSQL_DATABASE_URL=https://your-db.cloud.layerbase.dev
LIBSQL_AUTH_TOKEN=...const client = createClient({
url: process.env.LIBSQL_DATABASE_URL!,
authToken: process.env.LIBSQL_AUTH_TOKEN!,
})Your queries, transactions, and migrations are unchanged. It is the same client talking to the same kind of server.
What to Test
Before you cut over, confirm:
- Row counts match for every table, between Turso and the new database. Matching counts are necessary and not sufficient: if you moved the data with a dump from a tool that renders values through JavaScript, large integers can round while every count stays right. Spot-check
CAST(id AS TEXT)on anything above 2^53. - Your hot queries run and return the same results.
- Write throughput holds under your real load. This is the one to watch if Turso's limits were your reason for leaving. Run your actual write pattern (the bulk insert, the tight logging loop) and confirm latency stays flat. If you see memory pressure, Layerbase tells you, and you size up a tier instead of hitting a request wall.
- Auth and connection limits behave the way your app expects from the new URL.
FAQ
What does the migration wizard need from me?
Two values, both from the Turso CLI: the database URL that turso db show <db> --url prints (a libsql://... string) and an auth token from turso db tokens create <db>. There is no connection-string paste mode for a libSQL target, because libSQL is reached over HTTP rather than a TCP socket, so this pair is the only way in. The wizard reads the database over that URL and copies the schema and data into a new managed libSQL instance.
Does any of my application code change?
Only the two environment variables. You keep @libsql/client, you keep createClient({ url, authToken }), and you keep your queries, transactions, and migration files. The new URL is an https://...cloud.layerbase.dev host instead of a libsql://...turso.io one, and the dashboard issues the token.
Do I lose data fidelity in the copy?
Not in the type system: every SQLite type is preserved on both the wizard path and a .dump reload, because it is SQLite text at rest on both ends. The failure mode worth checking is not ours: if you export through a tool that renders values through JavaScript, large integers can round silently while every row count still matches. Spot-check CAST(id AS TEXT) on anything above 2^53 before you cut over.
How do I avoid a gap during cutover?
Copy while nothing is writing, or accept that writes landing on Turso after the copy started stay behind. There is no live replication here: it is a one-shot copy, so the sequence is copy, verify counts and hot queries against the new database, then change the env vars and redeploy. Keeping the Turso database around for a few days is the cheapest rollback available.
What happens if my workload outgrows the instance?
You size up, which is the part a request meter does not let you do. A flat instance has a fixed memory and CPU budget, and Layerbase warns you as a database approaches its memory limit so you can move up a tier or add headroom rather than hitting an opaque request wall. If you are leaving Turso specifically because of write volume, start with headroom rather than the entry tier and run your real write pattern before you cut over.
The Managed Path: Layerbase Cloud
Spin up managed libSQL on Layerbase Cloud and you get a flat per-instance price with no per-row meter, daily backups, and your choice of scale-to-zero (it sleeps when idle and wakes on the next connection) or always-on (pinned, never sleeps). The same libSQL you already build against, priced so a busy month does not surprise you.
If you want to prototype locally first, the Layerbase CLI runs the identical engine on your machine, so you can develop against a local libSQL and deploy the same schema to the cloud when you are ready.
Keep reading
- libSQL vs TursolibSQL is the open source SQLite fork. Turso is the company behind it, its managed platform, and now a separate Rust rewrite with the same name. Here is how the three fit together in September 2026.
- Your SQLite File Deserves a URLA SQLite file stops working the moment something other than one process needs it. Here is how to put the same file behind an address, with a Postgres driver or the libSQL client, without rewriting a schema.
- Cloudflare D1 alternatives: what you are actually coupled toD1 is SQLite, so your SQL is portable. The coupling is somewhere else: there is no wire protocol, the SQL export rounds any integer past 2^53, and you cannot get the file underneath. Here is an honest inventory, the alternatives, and when staying on D1 is the right call.
- Turso alternatives: libSQL hosting without row-read limitsTurso is the dominant libSQL host, but its per-row-read pricing surprises many users on real workloads. Here are alternatives, including self-hosted libSQL.