Studio
Browse forms, preview them on every device, read responses and adjust the schedule.
Studio is the admin UI that ships with Open Form. Start the dev server and open it in the browser:
npm run dev # Studio at http://localhost:3000/studioThe sidebar lists Forms and Themes with their counts, then your
folders. Its Search button, or Cmd / Ctrl + K, opens the command
palette: jump to any form, a folder or a theme, or switch the appearance or
Studio's language. Type to also find each form's
responses (Responses — Developer Meetup). Each form opens fresh: a search,
ticked responses or an Edit mode selection stay with the form they were made on.
Forms list
Every folder in forms/ is a form. The list shows each one as a card with a
live thumbnail (the real form in its own theme and source language, rendered at
desktop width and scaled down) and its status: Open, Scheduled,
Closed, Full, Unpublished or Error.
- Search matches the title, id and description.
- Status narrows the list to Open, Scheduled, Closed, Full or Unpublished, with a count for each.
- Sort by Recently edited, Latest response, Most responses or Name A–Z.
- Click anywhere on a card, thumbnail included, to open the form's Preview in Studio. The response count opens its Responses, and the arrow button opens the live form in a new tab (unpublished forms have no live form, so no arrow).
- Under
open-form dev, the trash button on a card deletes the form: its folderforms/<id>/and every response. Studio asks you to type the id first (click it to fill it in), and links Excel, CSV and JSON exports when there are responses to keep. The same action is at the bottom of the form's Settings tab. Seeopen-form deletefor exactly what goes.
Folders
Folders group forms in the sidebar. Add one with New folder (the + beside Folders, or the row shown while you have none). Drag a form onto a folder, or use Move to folder on its card. A folder's menu has Rename, Change icon (an emoji or a color) and Delete folder, and you can drag folders to reorder them. Forms without a folder appear under Unfiled. Delete folder asks first, then moves its forms to Unfiled. No form or response is deleted.
Folders are stored in forms/.folders.json, so they live in git with the forms:
{
"folders": [{ "id": "f-3a9c2e1b", "name": "Events", "icon": { "type": "color", "value": "#0091ff" } }],
"assignments": { "meetup": "f-3a9c2e1b" }
}Folders are only an organizing layer. Forms stay flat in forms/<id>/, and their
ids, URLs and responses don't change when you move them. Folders are editable
under open-form dev. A production build takes a snapshot and shows it read-only,
only to people signed in to Studio, or to everyone in a
public read-only Studio.
Preferences
Appearance (System, Light, Dark), Studio's Language (English, 繁體中文,
简体中文, 日本語), the sort order and the preview's device frame are saved in
your browser. Studio's language is separate from a form's locale, which is
what respondents see.
Preview
Open a form to land on Preview. On every tab, the header has Copy link, which copies the form's public URL, and Open form, which opens the live form in a new tab. An unpublished form has neither, but its Preview still works.
The toolbar switches the device frame (Desktop, Tablet, Mobile) and,
on a translated form, the Preview language: any language the form is
translated into, or Respondent’s language to pick one as the respondent's
browser would; switching keeps the preview on the step it shows. Desktop renders the form 1280 pixels wide and scales it to fit, so wide layouts look as they do on a laptop. Under open-form dev it also switches between Preview,
Edit (see Edit mode), which shows how many
notes the form has, and Adjust. Reload preview starts the form again
from the top.
Adjust opens a panel beside the preview for the form's own look in
forms/<id>/theme/index.ts: primary and background colors, body and heading
fonts (including Add from Google Fonts…), radius, color scheme, every color
in the light and dark palettes, button radius, width, spacing and frame, with a
contrast check. The preview follows each change before you save, showing the
palette you're editing, and Save to source writes the tokens into the form's
theme file. A form without a theme/ folder gets Create look file, which
writes theme/index.ts and passes it to the form's <Form>; if the <Form>
already gets another theme, it says so and changes nothing. For anything
bigger, describe the change and copy the prompt to your agent. See
Themes.
- Steps lists every page with its title, or the form's title on an
untitled single-page form, and its question count: a range such as 2–4
questions when some only appear for certain answers. Pages tagged
If only appear for certain answers. Click any step to open it directly. While
you jump around, hidden questions are shown with their conditions, Next and Back
don't validate, and nothing is sent. Start over returns to normal filling.
Collapse steps (the button beside the heading, or
Cmd/Ctrl+B) narrows the list to step numbers, with each step's details on hover, to give the preview more room; Studio remembers the choice. In Edit mode the list is always collapsed. Studio's shortcuts work while you're filling in the preview too. - Show hidden questions opens that same view on a single-step form with conditions: every question with its conditions, nothing sent.
- Done screen shows what respondents see after they submit.
- When closed shows each closed screen: Closed always; Not open yet
while
opensAtis still ahead; Full when the form has a response limit, including one set in Studio; Already responded withuniqueBy: 'device'. The live form doesn't change.
Submitting from Preview stores a real response. Delete test responses under Responses.
Responses
The Responses tab is a searchable, filterable, sortable table of everything submitted, with a detail view, copy, delete and Excel / CSV / JSON export. See Responses & export.
Schema
The Schema tab is a table of every field the form collects, grouped by step: its label, data key, type, validation (required, value and length ranges, how many choices, whole numbers, steps, a pattern, custom) and the condition that shows it. A step's own condition sits on its heading row. Answers are stored under the data key, and JSON exports and webhooks use it too.
Settings
The Settings tab has two cards: Submissions, which you can edit, and
Set in code, which you can only read because what it shows lives in code.
Under open-form dev, Delete this form sits below them: the same action as
the trash button on the forms list.
Submissions has Published (takes the form offline when off: its link
shows "Form not found"), Accepting responses (pauses the form when off),
Opens and Closes (a date, optionally with a time), the Time zone
those dates are read in, and Response limit. These map to the form's
published, submissions.opensAt,
closesAt, maxResponses, closed and the form's timeZone.
- Under
open-form dev, Save writes the values intoforms/<id>/index.tsx. The code stays the only source. - On a deployed server, Save stores them in the database as overrides. They take effect right away, without a deploy. Studio lists what was Changed in Studio, not in code, and Use code values removes the overrides.
Set in code shows one response per email or device, drafts, protection, languages and translation coverage, webhooks, and the embed snippet. See Submissions & protection.
Themes
/studio/themes is a read-only gallery of the reference themes in themes/:
what you and your agent start new forms from. Each theme's page previews it on a
sample form (light or dark, desktop or phone, any step), lays out the tokens a
new form copies (colors with their contrast checks, type and shape), and shows
its THEME.md with an outline of its sections. Beside it are the
open-form create <form> --theme command and an agent prompt for using the
theme, and a box to describe a change and copy it as a prompt for your agent.
Studio never writes a reference theme. Your agent changes one (the
create-theme skill), and New theme copies a
prompt for a new one or points to open-form theme add <starter>. A form's own
look lives in forms/<id>/theme/; change it with Adjust in its preview, by
hand or with your agent. The Themes pages are part of the production build too,
so a deployed Studio shows them. See Themes.
Routes
| Route | Shows |
|---|---|
/studio | Forms list |
/studio/:formId | Preview |
/studio/:formId/responses | Responses table; /responses/:responseId opens one response |
/studio/:formId/schema | Fields by step: data keys, types, validation, conditions |
/studio/:formId/settings | Schedule and settings in code |
/studio/themes | Reference theme gallery |
/studio/themes/:themeId | Theme page: preview, tokens and THEME.md |
/f/:formId | The public form; an unknown id shows Form not found |
Who can open Studio
open-form devwithout a password answers only direct requests from the machine running it — not pages served from other origins, even otherlocalhostports. WithOPEN_FORM_ADMIN_PASSWORDset, everyone signs in, including you. Set it before you expose dev in any way: a reverse proxy or a raw TCP tunnel (ssh -R,socat) can make a remote request look local.open-form startenables Studio only whenOPEN_FORM_ADMIN_PASSWORDis set (at least 16 characters with at least 8 different ones, not one word or pattern repeated; a passphrase works). Without it, Studio and the admin API don't exist and the server only serves public forms. Withstudio: { public: 'read-only' }, anyone can open Studio and read it without signing in; the sidebar says read-only, and deleting responses or changing the schedule still takes the password, through the Sign in to make changes button at the bottom of the sidebar.
Set OPEN_FORM_ADMIN_PASSWORD in the environment or in the workspace's .env,
which every open-form command except init loads; a variable already set in
the environment wins.
If Studio can't check the session (the server is down or answers with an error), it says so, with the HTTP status, and offers Try again.
Signing in gives a session cookie that lasts 7 days by default. Signing out ends
that session on the server, so a copy of the cookie stops working too. Sign-in
attempts are limited to 10 per IP (per /64 for IPv6) every 10 minutes; behind a
proxy, set trustProxy so each
visitor counts separately (open-form start also reads OPEN_FORM_TRUST_PROXY).
Changing the password signs everyone out. When you mount the handler yourself
(for example with Next.js), pass adminAuth to use a
password, your own authorize(request) check such as an SSO session, or a
signInUrl, and sessionMaxAge to change how long a sign-in lasts. See
Admin access.