Studio
npx turbine studio launches a local web UI for browsing your database and composing queries visually. It is read-only by design — there is no row editing, no DDL, no raw-SQL input surface, and every query executes inside a BEGIN READ ONLY transaction. It's the database UI you can run against production data without a change-control conversation.
DATABASE_URL=postgres://user:pass@localhost:5432/mydb npx turbine studioStudio introspects the schema, starts an HTTP server on 127.0.0.1:4983 (Node's built-in http module — no extra dependencies), and opens your browser with a per-process session token in the URL.
Launching
npx turbine studio # default: 127.0.0.1:4983, opens browser
npx turbine studio --port 5173 # custom port
npx turbine studio --no-open # don't auto-open the browser
npx turbine studio --schema inventory # non-public Postgres schema
npx turbine studio --include users,posts # only these tables
npx turbine studio --exclude _migrations # hide tablesThe connection string resolves the same way as every other CLI command: --url flag, then DATABASE_URL, then turbine.config.ts. See CLI — config resolution.
| Flag | Description |
|---|---|
--port <n> | HTTP port (default: 4983). |
--host <addr> | Bind address (default: 127.0.0.1). Non-loopback values trigger a loud warning. |
--no-open | Don't open the browser automatically. |
--include <a,b> / --exclude <a,b> | Limit which tables Studio sees. |
--schema <name> | Postgres schema (default: public). Pinned via set_config('search_path', ...) on every query. |
The Query tab — a visual findMany builder
The Query tab (the default) composes real Turbine queries — not SQL. You pick a table and build up findMany arguments interactively:
- Fields —
select/omitcolumn picks. - Filters — the full
whereoperator set (equals,not,in,gt/gte/lt/lte,contains,startsWith,endsWith, case-insensitive mode) withAND/OR/NOTgrouping. - Order + limit —
orderByon any column,limiton the result. - Relations — drill into
withrecursively, to any depth, choosing fields, filters, ordering, and limits at every level.
A live preview shows the exact db.table.findMany({ ... }) call your builder state compiles to, and the Copy TS button puts it on your clipboard — so a query you prototype in Studio pastes straight into your codebase, types and all. What you build is what you ship.
Because the builder speaks ORM args instead of SQL, the server can validate everything: POST /api/builder checks every identifier (table, relation, field, orderBy column) against the introspected schema and compiles the query with the same QueryInterface.buildFindMany the library uses, with every value bound as a $N parameter. There is no string of SQL anywhere in the request for an attacker to tamper with.
Data and Schema tabs
- Data — browse table rows with column sorting, search across every text column, and a modal viewer for JSON/JSONB cells.
- Schema — inspect tables, columns, types, indexes, and relations as introspected.
Both tabs go through the same read-only, parameterized query path as the builder.
Saved queries and the command palette
Queries you save persist to .turbine/studio-queries.json in your project (commit it to share a query library with your team). Saved queries appear in the sidebar and in the Cmd+K command palette, which also jumps to any table or tab in one keystroke.
Saved queries are builder-state only. Entries saved by pre-0.19 Studio versions as raw SQL are ignored on load (with a console notice) — Studio has no way to execute them anymore.
Security model
Studio's hardening is the point. Every layer assumes the layer above it failed:
- Loopback binding. Binds
127.0.0.1by default and warns loudly if you override--hostto anything non-loopback — Studio is a single-user local tool, not a deployable service. - Per-process token auth. A random 24-byte (192-bit) hex token is generated at startup and required on every
/api/*request, compared in constant time. The token lives in the launch URL and aSameSite=Strict,HttpOnlycookie. - No raw-SQL surface. Since v0.19 there is no endpoint that accepts SQL text. Builder requests are ORM args, validated identifier-by-identifier against the introspected schema; all values are
$Nparameters. BEGIN READ ONLYon every query. Even if every check above were bypassed, Postgres itself rejects writes — the transaction is read-only at the database level. Belt and suspenders.- Statement timeout. A 30-second transaction-local
statement_timeout(parameterizedset_config, never interpolated) bounds runaway queries, andsearch_pathis pinned to the configured schema the same way. - Request hygiene. Per-session rate limiting (100 requests / 60 s), cross-origin requests refused, and security headers on every response: CSP,
X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referrer.
Deliberately not implemented: row editing, DDL, destructive operations of any kind. Studio is for inspection — use the CLI, migrations, or your own code for writes.
See also
- CLI — every command, flag, and the config resolution order.
- API Reference — the
findManysurface the builder compiles to. - Observability —
npx turbine observe, the metrics dashboard with the same security model.