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 studio

Studio 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 tables

The connection string resolves the same way as every other CLI command: --url flag, then DATABASE_URL, then turbine.config.ts. See CLI — config resolution.

FlagDescription
--port <n>HTTP port (default: 4983).
--host <addr>Bind address (default: 127.0.0.1). Non-loopback values trigger a loud warning.
--no-openDon'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:

  • Fieldsselect / omit column picks.
  • Filters — the full where operator set (equals, not, in, gt/gte/lt/lte, contains, startsWith, endsWith, case-insensitive mode) with AND / OR / NOT grouping.
  • Order + limitorderBy on any column, limit on the result.
  • Relations — drill into with recursively, 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:

  1. Loopback binding. Binds 127.0.0.1 by default and warns loudly if you override --host to anything non-loopback — Studio is a single-user local tool, not a deployable service.
  2. 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 a SameSite=Strict, HttpOnly cookie.
  3. 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 $N parameters.
  4. BEGIN READ ONLY on 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.
  5. Statement timeout. A 30-second transaction-local statement_timeout (parameterized set_config, never interpolated) bounds runaway queries, and search_path is pinned to the configured schema the same way.
  6. 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 findMany surface the builder compiles to.
  • Observabilitynpx turbine observe, the metrics dashboard with the same security model.