CLI
The turbine CLI ships with turbine-orm. Run npx turbine <command> from your project root.
Command index#
npx turbine <command> [options]
Commands:
init Initialize a Turbine project
generate | pull Introspect the database, generate the typed client
push Apply defineSchema() output to the database
migrate create <name> Create a SQL migration file (--auto | --from-diff | --recipe)
migrate up Apply pending migrations
migrate deploy Apply pending migrations without prompts (CI)
migrate down Roll back the last migration (or the last N)
migrate status Show applied vs pending migrations
seed Run the seed file (.ts / .js / .sql)
status | info Show the introspected schema
doctor Missing-index triage + cached-plan divergence
migrate-from-prisma Map a schema.prisma onto Turbine (report + typed name map)
studio Launch the local Studio web UI
mcp Start the read-only MCP server over stdio
observe Launch the local metrics dashboard
skill Install the agent query skillEvery command takes --help. An unknown flag stops the command and names the closest valid one.
Global options#
| Flag | Description |
|---|---|
--url, -u <url> | Connection string. Overrides DATABASE_URL and the config file. |
--out, -o <dir> | Generated-code directory (default ./generated/turbine). |
--schema, -s <name> | Postgres schema name (default public). |
--include <a,b> / --exclude <a,b> | Limit the tables a command sees. |
--verbose, -v | Detailed logging. |
turbine init#
Bootstraps a project, running only the steps that are missing: write turbine.config.ts, scaffold ./turbine/schema.ts and ./turbine/seed.ts, then, when a database is reachable, offer to push, generate and seed.
npx turbine init
npx turbine init --url postgres://user:pass@localhost:5432/mydb --schema app| Flag | Description |
|---|---|
--yes, -y | Accept every default without prompting. |
--force, -f | Overwrite an existing turbine.config.ts. |
--schema-file <path> | Where to scaffold the schema file (default ./turbine/schema.ts). |
--skip-schema / --with-schema | Skip the schema file, or scaffold it even when the database already has tables. |
--skip-seed, --skip-push, --skip-generate | Skip that step. |
- Re-running is safe. Without a TTY,
initscaffolds and generates, and skips push and seed. - A
--urlwith a password goes to.env(added to.gitignore), and the config readsprocess.env.DATABASE_URL. --schema <name>is written into the config, so later commands target the same namespace.
turbine pull / turbine generate#
Introspects the live database and writes a typed client. pull is an alias.
npx turbine generate
npx turbine generate --out ./src/db --zodIt writes types.ts (entity and input types), metadata.ts (the runtime SchemaMetadata) and index.ts (a typed TurbineClient subclass and the turbine() factory).
| Flag | Description |
|---|---|
--schema-file <path> | Your defineSchema() file, whose column tags are applied (default ./turbine/schema.ts). |
--zod | Also emit zod.ts. Your project supplies zod. See Zod Schemas. |
--include-views | Include views and materialized views as read-only entities. See Views. |
--keep-column-names | Use raw column names (created_at) as field names. Config: keepColumnNames. |
--no-timestamp | Omit the Generated at header, for byte-identical output. |
--import-ext <mode> | Extension on imports between generated files: js, ts, none or auto (default). Config: importExtension. |
--legacy-to-many-uniques | Type a child with a unique foreign key as hasMany instead of hasOne. Config: legacyToManyUniques. |
--allow-empty | Generate even when no tables match. |
- Column tags.
generatereads thepii,updatedAt,maxLengthandtsTypetags from your schema file, since the database cannot store them. A named file that is missing or fails to load stops the command; a missing default is skipped. See How generate picks up the tags. --import-ext tsis for running the client with Node's type stripping (Node 22.18+ or 23.6+). Your type checker then needsallowImportingTsExtensionsorrewriteRelativeImportExtensions.- Field collisions. Two columns that camelCase to one field (
"createdAt"besidecreated_at) fail withTURBINE_E003. Use--keep-column-namesor rename a column.
Typing columns, columnTypes#
A json / jsonb column generates as unknown. columnTypes in turbine.config.ts replaces the type of any column, keyed "<table>.<column>" by database names:
const config: TurbineCliConfig = {
columnTypes: {
'users.settings': '{ theme: string; beta?: boolean }',
'orders.meta': "import('../src/types.js').OrderMeta",
},
};The text is emitted verbatim into the row, create and update types, so an import() resolves from the generated directory. It must be one type on one line. A nullable column still gets | null. An unknown key or malformed type throws TURBINE_E003 with the closest name. columnTypes wins over a schema file's tsType.
Renaming derived relations, relationNames#
Introspection names relations from foreign keys, so two foreign keys to one table give names nobody would guess:
messages.sender_id -> people.msgsBySender
messages.recipient_id -> people.msgsByRecipientDeclare better names once in turbine.config.ts:
// turbine.config.ts
export default {
relationNames: {
people: {
msgsBySender: 'sentMessages',
msgsByRecipient: 'receivedMessages',
},
},
};generate emits the new names in the metadata and types, for use in where, with and orderBy. An unknown table or relation, or a name that shadows a column, fails with TURBINE_E003 and lists the names that exist.
turbine push#
Applies your defineSchema() file to the database, in one transaction. Use it in development; use migrations in production.
npx turbine push
npx turbine push --dry-run --exit-code # CI: exit 2 when the database differs| Flag | Description |
|---|---|
--schema-file <path> | Your defineSchema() file (default ./turbine/schema.ts). |
--dry-run | Print the SQL without running it. |
--exit-code | With --dry-run: exit 2 when the database differs or the diff has a warning it cannot apply, 0 when in sync. |
--allow-destructive | Skip the confirmation for data-destroying statements. |
When the database already matches, push does nothing. It lists what the diff does not compare; see what --auto can and cannot do. Data-destroying statements hit the destructive guard.
turbine migrate create <name>#
Writes turbine/migrations/<timestamp>_<name>.sql with -- UP and -- DOWN sections.
npx turbine migrate create add_users_table # blank
npx turbine migrate create add_email_index --auto # from the schema diff
npx turbine migrate create sync_schema --from-diff # diff, destructive statements flagged
npx turbine migrate create widen_total --recipe backfill-- 20260409143022_add_email_index.sql
-- UP
CREATE UNIQUE INDEX "users_email_idx" ON "users" ("email");
-- DOWN
DROP INDEX "users_email_idx";| Flag | Description |
|---|---|
--auto | Fill both sections from the diff between your schema file and the database. |
--from-diff | Like --auto, and flag each data-destroying statement inline and in a banner. Not combinable with --auto or --recipe. |
--recipe <name> | Scaffold a named pattern. backfill is the two-phase column type change. |
--allow-pending | Diff even while earlier migrations are pending. |
--schema-file <path> | The schema file to diff. |
--auto and --from-diff refuse to run while migrations are pending, because the diff would repeat what those files do.
turbine migrate up#
Applies pending migrations in timestamp order, each in its own transaction.
npx turbine migrate up
npx turbine migrate up --step 1 --dry-run| Flag | Description |
|---|---|
--step, -n <N> | Apply only the first N pending migrations. |
--dry-run | Print the SQL without running it. |
--allow-drift | Accept edited applied files and re-record their checksums. Nothing re-runs. |
--allow-destructive | Skip the confirmation for data-destroying statements. |
--allow-pooler | Run through a transaction pooler anyway. |
- The run holds
pg_try_advisory_lock(), so a concurrent run fails withMigrationError(TURBINE_E006). - An applied file that was edited stops the run.
up,deployanddownrefuse a pooler endpoint (apoolerorpgbouncerhostname, or port 6543 or 6432), because the lock is session-level. Use the direct endpoint.- A file with
-- turbine:no-transactionruns withoutBEGIN/COMMIT, one statement at a time.
Destructive-migration guard#
migrate up, migrate down and push refuse to run SQL that destroys data:
DROP TABLE,DROP SCHEMA,DROP COLUMN,DROP VIEW ... CASCADETRUNCATEandDELETE FROM- an
UPDATEwith noWHERE, or one that is always true (WHERE 1=1) ON CONFLICT DO UPDATEwithout aWHERElo_unlinkALTER COLUMN ... TYPE, unless a-- turbine:wideningmarker declares it lossless- any of these inside a
PREPARE ... ASbody
Comments and string literals are ignored, and DROP INDEX, DROP CONSTRAINT and DROP TRIGGER pass. On a hit, nothing runs. Confirm interactively (type destroy my data, then yes) or pass --allow-destructive. Programmatic migrateUp / migrateDown take allowDestructive: true.
turbine migrate deploy#
Applies pending migrations without prompting, for CI and production.
npx turbine migrate deploy
npx turbine migrate deploy --dry-run # list pending migrations
npx turbine migrate deploy --wait=120 # wait up to 120 s for another deploy's lock| Flag | Description |
|---|---|
--dry-run | List pending migrations and destructive statements without applying. |
--wait[=<seconds>] | Wait for a concurrent deploy's lock (default 300 s), then apply what is still pending. |
--allow-drift | Deploy over edited applied files, re-recording their checksums. |
--allow-pooler | Run through a transaction pooler anyway. |
It uses the same lock as migrate up; without --wait, a held lock exits 1. A checksum mismatch or missing file also exits 1. The destructive guard does not run here: destructive statements print a NOTICE, then run. deploy never generates, seeds or pushes.
turbine migrate down#
Rolls back the most recent migration by running its -- DOWN section.
npx turbine migrate down --step 2It takes --step, -n <N>, --dry-run, --allow-destructive, --allow-drift and --allow-pooler, as migrate up does. A destructive -- DOWN hits the same guard, and one with no SQL is reported with a warning.
turbine migrate status#
Shows which migrations are applied and which are pending.
npx turbine migrate status
npx turbine migrate status --exit-code # CI: exit 2 on pending, modified, missing or misnamed filesi 2 applied, 1 pending
Status | Migration | Applied at
-----------|------------------------------------|-------------------------
v Applied | 20260401120000_create_users.sql | 2026-04-01 12:00:00 UTC
v Applied | 20260402091234_add_posts.sql | 2026-04-02 09:12:34 UTC
. Pending | 20260409143022_add_email_index.sql |
Run npx turbine migrate up to apply pending migrations.An edited applied file shows as ! Drifted, a deleted one as ! Missing file.
turbine seed#
Runs the seed file.
npx turbine seedThe path is seedFile in turbine.config.ts (seed is an alias), else the first that exists of seed.ts, seed.js, seed.sql, turbine/seed.ts, turbine/seed.js, turbine/seed.sql. A .ts file runs with npx tsx, a .js file's default export is called, and a .sql file runs as raw SQL.
// seed.ts
import { defineSeed } from 'turbine-orm';
export default defineSeed(async (db) => {
await db.raw`INSERT INTO orgs (name) VALUES (${'Acme'}) ON CONFLICT DO NOTHING`;
});See Seeding.
turbine status#
Prints the introspected schema. info is an alias.
npx turbine statusIt shows the connection string (password redacted), the schema and table count, then each table's columns, types, primary key and relations, and any enums.
turbine doctor#
Finds relations whose foreign key has no index, ranks each fix, writes the migration with --fix, and flags reads a cached plan could slow down. Every flag, the triage tiers and the JSON report are on its own page.
npx turbine doctor
npx turbine doctor --fixturbine migrate-from-prisma#
Parses a schema.prisma, resolves each name against your database, and writes a report, the typed PRISMA_MAP the prisma-compat adapter runs on, and the generated client. It needs neither prisma nor @prisma/client.
npx turbine migrate-from-prisma --schema prisma/schema.prisma
npx turbine migrate-from-prisma --schema prisma/schema.prisma --no-db # report only| Flag | Description |
|---|---|
--schema <file> | Path to schema.prisma (default prisma/schema.prisma). Here --schema is the Prisma file; the namespace is public. |
--url, -u <url> | Connection string. See Connection string. |
--out, -o <dir> | Output directory (default ./generated/turbine), inside the current directory. |
--no-db | Write the report only, without the map or client. |
--allow-partial | Exit 0 when items are unresolved (default: exit 1). |
--if-db | Exit 0 without doing anything when no connection string resolves. |
--no-timestamp | Omit the generated-at lines. |
An unresolved item never blocks the client. A stale prisma-map.ts fails silently, so run the command in postinstall next to prisma generate, with --if-db; see Keeping the map current. Full workflow: Migrating from Prisma.
The --out directory guard#
migrate-from-prisma refuses to write outside the current working directory:
$ npx turbine migrate-from-prisma --out /tmp/turbine-scratch
Output directory must be within the project root. Got: /tmp/turbine-scratch
# exit code 1A relative path that climbs out (../shared) is refused too. To write into a scratch directory, run the command from inside it with an absolute --schema. turbine generate has no such guard.
turbine studio#
Launches a local web UI with a visual findMany builder, a data browser and a schema view. It is read-only by default and redacts PII-tagged columns on the server.
npx turbine studio
npx turbine studio --demo # seeded sample database, no DATABASE_URLFlags: --port, --host, --no-open, --allow-remote, --write, --show-pii, --demo. Details and the security model: Studio.
turbine mcp#
Starts a read-only Model Context Protocol server over stdio, so an agent can inspect your schema, plans and sample rows. It takes the global flags only.
npx turbine mcpTools and client setup: MCP Server.
turbine skill#
Installs the query-writing skill an agent reads before writing Turbine queries.
npx turbine skill # -> .claude/skills/turbine-orm/SKILL.md
npx turbine skill --print # to stdout
npx turbine skill --agents # a short block for AGENTS.md / CLAUDE.md
npx turbine skill --dir .agent/skillsturbine observe#
Serves a local dashboard over the _turbine_metrics table that db.$observe() writes.
npx turbine observe --metrics-url postgres://...Flags: --metrics-url (default TURBINE_OBSERVE_URL), --port (default 4984), --host, --no-open, --allow-remote. See Observability.
Config resolution#
The CLI loads the first of turbine.config.ts, turbine.config.mts, turbine.config.js and turbine.config.mjs in the current directory that imports successfully.
import type { TurbineCliConfig } from 'turbine-orm/cli';
const config: TurbineCliConfig = {
url: process.env.DATABASE_URL,
out: './generated/turbine',
schema: 'public',
migrationsDir: './turbine/migrations',
seedFile: './turbine/seed.ts',
schemaFile: './turbine/schema.ts',
columnTypes: { 'users.settings': '{ theme: string }' },
};
export default config;A flag beats the config file, which beats the default. migrationsDir, seedFile, columnTypes and relationNames come from the config file only. Every field: TurbineCliConfig.
Connection string#
A command that connects uses the first non-empty value of:
--urlDATABASE_URLurlinturbine.config.tsmigrate-from-prismaonly: the schema'sdatasourceblock, through itsenv("...")(url, thendirectUrl) or a literalurl
The CLI loads .env from the current directory (Node 20.12+), but a variable already set in your shell wins. observe reads --metrics-url, then TURBINE_OBSERVE_URL, never DATABASE_URL.
See also#
- The
turbine-orm/climodule: the config type and loaders, for your own tooling. - Migrations: the migration workflow with examples.
- Quick Start: from
initto a first query.