# Studio tour (/docs/using/studio)



Studio is Chelabase's web app. In a local stack it runs at `http://studio.chelabase.localhost:8000` (`just workbench up` starts it). Everything it does goes through the gateway on `:8000` with your member access token, so anything you can click you can also do through the Management API (the reference is in Studio's **API reference** page and under Reference in these docs).

<Callout type="info">
  Open Studio at `studio.chelabase.localhost:8000`, not an IP address: the gateway routes by host name, and the sign-in redirect and the gateway's CORS origin are registered for that host.
</Callout>

## Signing in [#signing-in]

Studio has its own sign-in pages: `/login`, `/register`, `/accept-invite`, `/verify`, `/password-reset`, `/pending` and `/logout`. They call the `auth` service through the gateway (`/auth/v1/*`); the session and the step state live in encrypted HttpOnly cookies, and Studio holds only a short-lived token in memory ([Identity service](/docs/architecture/identity)). The [Quickstart](/docs/using/quickstart) walks through signing in as the bootstrap admin.

* Self-hosted mode (the default, `CHELABASE_MODE=self-hosted`) takes no self-sign-up unless a tenant admin switches it on (`PATCH /v1/instance/settings` with `open_signup`). The first admin is whoever claims the instance with the setup code ([Install](/docs/self-hosting/install#claim-the-instance)); an admin invites everyone else, by an email, or by a link to hand over when no SMTP server is configured (`GET /v1/invites/delivery` says). The sign-in page offers **Create account** only while sign-up is open, and always says "No account? Ask your admin for an invite" otherwise. Platform mode takes no self-sign-up yet.
* With sign-up open, a new person is a pending member who can do nothing until an admin approves them (`approveMember`, with their first grants) or declines them. Sign-ups are created with the email already verified in self-hosted mode; set `CHELABASE_REQUIRE_EMAIL_VERIFICATION=true` to require a code (codes and password-reset emails land in Mailpit at `http://mail.chelabase.localhost:8000`); `=false` turns it off in any mode.
* A sign-up for an email that is taken says so and sends you to sign in.
* Everyone who joins is in the same tenant: a shared team workspace, by design for self-hosting. An admin can give the tenant a display name (`PATCH /v1/tenant`); the invite page and `GET /v1/me` use it, falling back to the slug.

## Projects [#projects]

The dashboard lists your tenant's projects, with **New project**. Opening a project shows up to four tabs:

* **Databases**: the project's databases and a form to create one.
* **Users**: the project's [app users](/docs/using/app-users). It appears once app users are enabled.
* **Access**: who has which role on the project, **Add people**, per-database narrowing and **Access changes**.
* **Settings**: the project name, [App users](/docs/using/app-users) (enable, change or disable) and a danger zone that deletes the project and every database in it.

## Databases [#databases]

The **Databases** tab lists each database with its status and endpoint. Each row has **Reset password** and **Delete** (Admin), and **Branches**, **Merges**, **Connect** and **Open**. **Reset password** sets a new owner password; **Delete** destroys the database with all its branches and data (you type its name to confirm). **Open** goes to the [Table editor](/docs/using/table-editor). The other three open a panel under the row, once the database is `ready`:

* **Branches**: the branch tree with each branch's status and endpoint, a form for a new branch, and the database's settings (history window, branch limit, max rows, statement timeout, max connections). Each branch has **Connection details**, **Reset password**, **Protect** (or, once protected, **Protection** and **Unprotect**), **Reset to parent**, **Restore**, **Delete branch**, **Roles** and, with a parent, **Merge into** and **Update from** it (see [Merging schema](/docs/using/merging-schema#in-studio)). A disabled action says why next to the buttons; while a schema merge holds the branch, a link opens the merge. See [Branches](/docs/using/branches) and [Login roles](/docs/using/login-roles).
* **Merges**: the database's schema merges, newest first, each opening its [merge page](/docs/using/merging-schema#in-studio).
* **Connect**: the owner's connection (Admin), a link to your personal Postgres login, and the Data API endpoint and public key, with **Manage Data API**.

## Data API [#data-api]

The **Data API** page in the left rail has the endpoint and public key (with **Rotate public key**), the Postgres databases it serves, secret keys, row-level security and generated types. See [Data API](/docs/using/data-api).

### RLS warning [#rls-warning]

The Data API page's **Row-level security** card lists every table, view or foreign table that `anon` or `authenticated` can reach without row-level security protecting it, with a button to copy the SQL that fixes it:

* a table with RLS off: `ALTER TABLE … ENABLE ROW LEVEL SECURITY`;
* a view that runs with its owner's rights: `ALTER VIEW … SET (security_invoker = true)`;
* a materialized view or foreign table, which can't have RLS: `REVOKE ALL … FROM anon, authenticated`.

It also names the app users table while its RLS is off, even before any role can reach it, because the table holds your users' emails.

## Table editor [#table-editor]

The **Table editor** in the left rail browses and edits rows and changes table structure. See [Table editor](/docs/using/table-editor). Rows are read and written through the console endpoint below.

Over the API, the console is `POST /v1/databases/{id}/branches/{branch_id}/console/query` with:

```text
{ "role": null | "<role>", "database": null | "<name>", "tree": <a data API request> }
```

`role` is `null` (the owner) or a role name, `database` is `null` (the branch's main database) or a Postgres database name, and `tree` is a data API request, the JSON that `POST /data/v1/query` takes (see [Data API](/docs/using/data-api)). Every operation is allowed, batches included, in one transaction with the data API's `max_rows` and statement timeout. The answer is the data API's.

* **As the owner** (`role: null`, Studio's default): the orchestrator runs the request itself, on a small pool per branch and database (at most 2 connections, closed after 60 seconds idle), with the owner password it holds. Row-level security doesn't apply to the owner. `rest` never sees the owner's password. When the owner's password was set in SQL, Chelabase doesn't know it: the answer is 409 `owner_password_unknown` until you reset it (**Reset password**, or `POST …/branches/{branch_id}/reset-password`), which works at once.
* **As another role** (`anon`, `authenticated`, a plain role you made): the worker checks the role the first time (the result is remembered for five minutes) and grants it to `authenticator`; `rest` then runs the request as that role. An owner-level role (a superuser, or one with `CREATEROLE`, `CREATEDB` or `REPLICATION`, or a `neon_superuser` member), or one that doesn't exist, is refused: 403 `role_refused`, saying why. A role with `BYPASSRLS` alone is allowed and reads past row-level security. A role dropped after its check answers 503 `unavailable` for up to 30 seconds, then 404 `unknown_role`.
* **Errors:** a Postgres database that isn't served is 409 `data_api_disabled` (Studio offers **Enable data API**); one that doesn't exist is 404 `not_found`; everything else is the data API's code. See [Error codes](/docs/reference/errors).
* **Audit:** every write is logged at `info` (platform user, branch, database, role, operation kind), never the values.

## API reference [#api-reference]

The **API reference** item in the left rail opens the Management, Data and Auth API references, each with **Try it out**; only the Management reference fills in your signed-in token. Nothing on that page calls an outside service.

## How Studio behaves [#how-studio-behaves]

* Lists refresh every 2 seconds while a database or branch is pending, provisioning, resetting or deleting, and stop when the status settles.
* A write refreshes exactly the data it changes.
* Secrets (connection details, passwords, created or rotated API keys) never enter Studio's query or mutation cache or any stored state. They live in the open panel and disappear when it closes. Keys are shown once, at creation or rotation.
* The SQL editor keeps its run history, open buffer and timeout in your browser's `localStorage`.
* Studio is English-only and dark-only today.
