typebase-io-cli db
Manage your database schema.
Last updated on
Move your schema in either direction: push the Drizzle schema in your project to a database, or pull a database you already have into your project. Or record your schema changes as migrations and apply those instead.
Which commands apply here depends on the mode your project is in. A project with a typebase/db/migrations/ folder is in migrations mode and
uses migrate; every other project is in push mode and uses push. See Migrations.
You often won't need these by hand. deploy pushes or applies migrations for the target it deploys to, and
start does the same for the database your local run is using, every time something under typebase/db/
changes.
Pushing your schema
db dev push
Push your schema to the dev database.
npx typebase-io-cli db dev pushdb prod push
Push your schema to the production database.
npx typebase-io-cli db prod pushdb local push
Push your schema to a Postgres instance running on your own machine (Docker, Postgres.app, a managed Postgres you connect to directly, etc.).
Pass the connection string with --url:
npx typebase-io-cli db local push --url postgres://localhost:5432/mydbOr set DATABASE_URL (in your shell or in your project-root .env) and omit the flag:
npx typebase-io-cli db local pushOptions
| Flag | Description |
|---|---|
--url <url> | db local push only. Connection string, defaulting to DATABASE_URL |
--skip-confirmation | Apply destructive changes without asking. They are still reported |
--skip-confirmation exists for CI, where there is nobody to answer the prompt. It approves dropped columns and dropped tables in advance, and
dropped data is unrecoverable. The warnings are still printed, so what was applied is on the record — but nothing stops it.
What happens
Every db push:
- Builds your schema files
- Connects to your database
- Pushes the schema changes using Drizzle Kit
db push applies schema changes directly to the database. Additive changes (new tables, new columns) are safe, but destructive changes (dropped
columns, renamed tables) cannot be undone. The CLI will detect these and prompt for confirmation before continuing. Treat db prod push with extra
care since dropped data is unrecoverable.
Push in migrations mode
If typebase/db/migrations/ exists, push refuses to run and names the command to use instead:
This project uses migrations, so pushing would change the schema without recording it.
Run `db dev migrate` to apply your migrations instead.It does not warn-and-proceed and does not silently redirect: a push would change the schema without recording anything, and the next migration you generated would be diffed against a snapshot that no longer describes the database.
Applying migrations
These commands exist for projects in migrations mode. They refuse to run in a project with no migrations folder, and
point at db migrations init.
db dev migrate
Apply pending migrations to the dev database.
npx typebase-io-cli db dev migratedb prod migrate
Apply pending migrations to the production database.
npx typebase-io-cli db prod migratedb local migrate
Apply pending migrations to a Postgres running on your own machine.
npx typebase-io-cli db local migrate --url postgres://localhost:5432/mydbAs with db local push, the --url flag falls back to DATABASE_URL from your shell or your project-root .env.
| Flag | Description |
|---|---|
--url <url> | Connection string. Defaults to DATABASE_URL when omitted |
What happens
Each migration runs exactly once per target, so re-running is safe. The command reports what it applied, or that the target was already up to date:
✔ 2 migrations applied.
20260101120000_add_priority_to_todos
20260102090000_backfill_priorityIf your schema files hold changes no migration records, migrate warns and carries on, since bringing a target up to date is often what you are doing
before you continue working on the schema. It never generates the missing migration for you.
Recording migrations
db migrations generate
Diff your schema files against the last recorded migration and write the difference as a new migration. Offline: no database is contacted.
npx typebase-io-cli db migrations generate --name "add priority to todos"| Flag | Description |
|---|---|
--name <name> | Name for the migration, so the history reads well in a pull request |
--custom | Create an empty migration and write the SQL yourself, for changes the schema diff cannot express |
--ignore-conflicts | Generate even though the migration history has forked |
Generating when nothing has changed reports that the schema is up to date and writes nothing. Generating on a forked
history refuses and names the competing migrations, unless you pass --ignore-conflicts.
db migrations init
Adopt migrations in a project that has been pushing its schema. Records a baseline holding the creation SQL for your current schema, and marks the databases that already have those tables as having applied it.
npx typebase-io-cli db migrations initIt asks before recording anything, and names the targets it is about to mark:
? Record a baseline for your current schema and mark dev and prod as having applied it? (Y/n)Only targets that already have a database are inspected. Nothing is ever provisioned, so a target with no database yet is reported and skipped; it replays the baseline for real the first time you migrate it. Each existing target is compared against your schema files first, the comparison applies nothing, and a target that has drifted can be brought in line before the baseline is recorded.
Pulling an existing database
db pull
The opposite direction: read an existing PostgreSQL database and write it into your project as db/schema.ts and db/relations.ts. This is how you
start from a database you already have, whether that's a Supabase project, a database from another framework, or one you run yourself.
npx typebase-io-cli db pullThe CLI asks for the connection string of the database to read, or you can pass it directly:
npx typebase-io-cli db pull --url postgres://user:pass@host:5432/mydbThe database needs to be reachable from your machine, so include whatever SSL parameters your provider expects in the connection string (Supabase
and Neon both want ?sslmode=require).
What you get:
db/schema.ts: every table in the database'spublicschema, as Drizzle tables written withp. Column types, nullability, defaults, primary keys (including composite ones), unique constraints, indexes, foreign keys with theironDeletebehavior, enums, and views all come across.db/relations.ts: aq.defineRelationscall with a relation for every foreign key, plus an entry for every other table (Typebase needs each table registered, even the ones with no relations).- Regenerated
_generated/types, so the pulled tables are typed ondbinside your actions right away.
A pull gives you a schema that is equivalent to the database, not a copy of the file that created it. Table order follows the database, relation
names are derived from foreign keys, and anything that only ever existed in TypeScript ($onUpdate, your own relation names, your comments) cannot be
read back out of Postgres. What it does guarantee is that pushing what you pulled reports no changes.
Options
| Flag | Description |
|---|---|
--url <url> | Connection string of the database to read. Prompted for if omitted |
-f, --force | Replace db/schema.ts and db/relations.ts without asking |
Pull in migrations mode
In migrations mode a pull would orphan the recorded history, since what it writes is equivalent to the source database
rather than identical to the files that history describes. So it refuses without --force:
This project records its schema as migrations, and pulling would replace the schema files that
history describes. Re-run with `--force` to pull anyway and rebaseline the history from what was
pulled.With --force it pulls, then replaces the history with a single baseline built from what it read. Your databases still record the migrations that
baseline replaced, so the pull tells you that every target has to be marked again with db migrations init. It changes nothing on any database itself.
In a project with no migrations, pull offers to start using them, since adopting is a natural thing to want at that moment. Declining leaves everything as it was.
What to watch out for
db pull replaces both files with whatever the database contains. It asks first, unless you pass --force. If you had run auth generate, your better-auth tables are part of schema.ts and go with it, so run auth generate again afterwards unless the
database you pulled from already holds those tables.
Only the public schema is read, so Supabase's storage.* tables stay behind. The one exception is a table you reference across schemas: a user_id
pointing at Supabase's auth.users brings that table along as a p.pgSchema('auth') block, because the reference in the file has to resolve. The CLI
tells you when that happens, and those are the lines to clean up first.
The pulled schema describes the database you read, not the one Typebase deploys to. Review it, then push it with db dev push
to create the same tables on your own database, or point db local push --url at wherever you want them.