Studio
npx turbine studio opens a local web UI for browsing your database and building queries visually. It is read-only by default, has no SQL input, and redacts PII-tagged columns on the server.
DATABASE_URL=postgres://user:pass@localhost:5432/mydb npx turbine studioStudio serves 127.0.0.1:4983 and opens your browser at a URL that carries a one-time login ticket. The connection string resolves as for every CLI command.
Try it without a database#
npx turbine-orm@latest studio --demo--demo runs against a seeded in-memory sample (users, posts, comments and orgs, with two PII-tagged columns) on Node's built-in SQLite. Requires Node 22.5+. A switcher in the banner toggles read-only, PII and write modes. Nothing is saved when the process ends.
Flags#
| Flag | Description |
|---|---|
--port <n> | HTTP port (default 4983). |
--host <addr> | Bind address (default 127.0.0.1). A non-loopback address needs --allow-remote. |
--allow-remote | Allow a non-loopback --host, with a startup warning. |
--write | Enable inserts, edits and deletes, each addressed by primary key. See Write mode. |
--show-pii | Show PII-tagged values, with a startup warning and a page banner. |
--demo | Use the seeded in-memory sample. |
--no-open | Don't open the browser. |
--include <a,b> / --exclude <a,b> | Limit the tables Studio shows. |
--schema <name> | Postgres schema (default public). |
--out <dir> | Generated client directory, where Studio reads PII tags (default ./generated/turbine). |
The Query tab, a visual findMany builder#
Pick a table and build findMany arguments:
- Fields:
select/omit. - Filters: the
whereoperators, withAND/OR/NOTgrouping and case-insensitive mode. - Order and limit:
orderByon any column, andlimit. - Relations:
withat any depth, with fields, filters, order and limit at each level.
A live preview shows the db.table.findMany({ ... }) call, and Copy TS copies it into your code. After a run, View SQL shows the single statement it compiled to.
The server checks every table, relation and field against the schema, then compiles with Turbine's own query builder and binds every value as a parameter. A run returns at most 1,000 rows. A lock option is refused, since reads run in a read-only transaction.
Data and Schema tabs#
- Data: sort, search every text column, stack per-column filters, and open JSON cells in a viewer. Select rows to copy or export as JSON or CSV. Pages hold up to 500 rows.
- Schema: tables, columns, types, indexes and relations, with a pii badge on tagged columns.
Zone-less timestamp and date cells render as UTC, the same instant your application reads. See Zone-less columns.
Write mode (--write)#
Without --write, the write endpoints do not exist. With it, the Data tab adds Insert row, Edit, Delete, Delete selected and Paste rows (TSV or CSV with a header row, or a JSON array), under a red WRITE MODE banner.
- Every write names its row by full primary key. A batch of up to 500 rows runs in one transaction, and any failure or unmatched key rolls back the whole batch.
- There is no bulk update, predicate-based write, DDL or SQL input.
- The editor is typed: dropdowns for enums and booleans, JSON validated before submit.
- Tables without a primary key are not editable. Views are not shown.
Write mode is for fixing a few rows without opening psql, not for data entry.
PII redaction#
Columns tagged pii: true in defineSchema show a placeholder in every tab, nested relation rows and post-write echoes included. Redaction happens on the server, so the browser never receives the value.
- Redacted columns are excluded from search, filters (
IS NULLtoo) and sorting, so a value cannot be inferred. - Studio reads the tags from the generated
metadata.tsin--out, so runturbine generateafter tagging. If that file cannot be parsed, Studio treats every column as PII. --show-piiturns redaction off for one launch. The row echoed after a write still omits PII values.
See PII fields for the same tags in application queries.
Saved queries and shortcuts#
Saved queries go to .turbine/studio-queries.json (commit it to share them), up to 500. In --demo they stay in memory. They appear in the sidebar and in the Cmd+K palette, which also jumps to any table.
| Keys | Action |
|---|---|
| Cmd+Enter | Run the query |
| Cmd+S | Save the query |
| G, then Q / D / S | Switch tab |
| R / Shift+R | Refresh data / reload the schema |
| / | Focus the sidebar filter |
Security model#
Studio is a single-user local tool, not a service to deploy.
- Loopback only. It binds
127.0.0.1and refuses another--hostwithout--allow-remote. - One-time ticket. The printed URL's ticket is exchanged for an
HttpOnly,SameSite=Strictsession cookie, checked in constant time on every API request. Reusing the URL gets403. Hostcheck. A request whoseHostis not loopback or the bound host gets403, which blocks DNS rebinding.- No SQL surface. Requests carry query arguments, never SQL. Every name is checked against the schema and every value is bound.
- Read-only transactions. On PostgreSQL every read runs in
BEGIN READ ONLY, with a 30-secondstatement_timeoutandsearch_pathpinned to--schema, even in write mode. - Write gates. Writes exist only with
--write, need a full primary key, and need a matchingOriginheader. - Request limits. 100 requests per minute per session, cross-origin requests refused, bodies over 1 MiB refused, and a nonce-based CSP with
X-Frame-Options: DENY,nosniffandno-referreron every response.
See also#
- CLI: every command and flag.
- Reading and writing: the
findManysurface the builder compiles to, andincludePii, which takes theUNSAFEsymbol. - Observability:
npx turbine observe, with the same security model.