Build an internal tool with Codex Sites (a dashboard)
A start-to-finish tutorial that builds an ops request dashboard in Codex Sites from one prompt, then walks save, review, deploy, and workspace access.
If you want an internal tool fast, Codex Sites is built for exactly this: you describe the tool in plain language, name @Sites, and Codex turns the description into a hosted app for your team — sign-in, storage, and a working UI included. In this post we build the ops "project request dashboard" from the docs' own canonical prompt, decompose that one sentence into the features it triggers, then walk save, review the source and database migrations, deploy to a production URL, and set workspace access. It is in preview, so we flag what is and is not documented as we go.
What we're building
The goal is a small operations tool with four jobs: team members submit project requests, everyone can see who owns each request, owners update a request's status, and anyone can filter the list. It should require a workspace sign-in so only colleagues get in, and the request data has to stick around between visits. That is a textbook internal app — the same shape as the Pulse Dashboard, Sparkboard, and Event Planning Hub in the official showcase. Nothing exotic, which is the point: this is the kind of tool Codex Sites is meant to spin up in one thread.
The one prompt that starts it
Here is the canonical build prompt, verbatim. Read it as a brief, not as code — it describes outcomes and lets Codex pick the machinery.
@Sites Build a project request dashboard for my operations team. Let team members submit requests, see who owns each one, update the status, and filter the list. Require people to sign in with their workspace account, and keep the request data saved between visits.
That single sentence is doing more than it looks. It names an audience, four interactions, an identity requirement, and a persistence requirement. The trick to building good internal tools on Codex Sites is learning to hear those implications before you type — because a vague brief produces a vague tool, and on Codex Sites every deploy is real. (If you want to get sharper at writing these, we have a whole walkthrough on how to prompt Codex Sites.)
What that prompt implies
Decompose the brief and each clause maps to a concrete Sites capability. This is the table we keep in our head while writing the prompt — requirement on the left, the feature Codex provisions on the right.
| What the prompt says | What Codex Sites provisions |
|---|---|
| "Require people to sign in with their workspace account" | A workspace-authenticated user identity — only signed-in workspace members get in, and the app knows who each user is |
| "Keep the request data saved between visits" | D1, a relational database, for the durable request records (owner, status, fields) |
| "Submit requests" | A create form writing a new row to D1 |
| "See who owns each one" | A read view listing requests with their owner field |
| "Update the status" | An update action on an existing record |
| "Filter the list" | UI state for filtering — not stored, because it is temporary presentation state |
Two of those rows are worth dwelling on. The sign-in clause is what makes this an internal tool: a workspace-authenticated identity is the default identity shape for something only your team should use, and it is decided at build time. The persistence clause earns a database because request records are data a person would be upset to lose — exactly what D1 is for. The filter, by contrast, is not something to persist; which filter is active is UI state, and the docs are firm that storage is for real product data only. If you want the longer version of that decision, see adding a database and file uploads to a Codex Sites app.
Save and review before anyone sees it
Once Codex builds the dashboard, do not deploy yet. The QA step on an internal tool is the same one as on anything else in Codex Sites: ask Codex to save a reviewable version. Saving builds a candidate tied to your current commit without putting anything in front of users — and since there is no throwaway preview link, saving is the staging environment.
@Sites Save a reviewable version of this dashboard so I can review the source changes and any database migrations before deploying.
Then open the review pane and actually read two things. First, the source changes — the form, the list view, the update action, the auth wiring. Second, the database migrations — because this app provisions D1, saving surfaces the schema Codex generated for the request records. This is your chance to catch a missing field or a wrong type before it becomes a live table. Treat the migration diff like a pull request: if the status column is a free-text field where you wanted a fixed set, that is a one-line follow-up prompt now, not a data-cleanup job later.
Deploy and set workspace access
When the saved version reads right, deploy it. Deploying returns the production URL — and that URL is live the moment it exists, which is why we reviewed first.
@Sites Deploy the saved version of the dashboard and give me the production URL.
A freshly deployed site is not open to your team yet. By default it stays limited to the owner and workspace admins until you widen it. That default is a feature, not a hurdle: it gives you a real production environment with a tiny audience so you can click through the deployed tool before colleagues can. When it checks out, widen the access mode to Workspace so every active member of your org can open it — or to Custom if only one team should. Set the audience deliberately, after review, never before sharing the link. The full decision tree, including the verbatim prompt for changing access, lives in controlling who can see your Codex Sites site.
One preview-era caveat: on Enterprise, an admin has to enable Sites via role-based access control before anyone can use it; Business has it on by default. If @Sites does nothing in an Enterprise workspace, that switch is usually why.
Make it yours (iterate)
The first deploy is rarely the last. Iteration is the same loop, scoped smaller: describe the change, save, review the diff, deploy. Want a comment thread on each request, or an exported CSV? Say so, save the new version, read the migration, ship it. Secrets and environment values — a Slack webhook for new-request alerts, say — go in the Sites panel, never committed; the project linkage and binding names live in .openai/hosting.json. Keep that split straight and the tool grows without leaking anything.
More patterns
The ops dashboard is one shape among many. The official showcase is the fastest way to see the category fit, and most of those apps decompose the same way our brief did:
- Onboarding Hub — progress, meetings, tasks (records that persist per person → D1).
- Pulse Dashboard — trends, targets, metric health (structured data, filtered views).
- Sparkboard — proposals with voting, filtering, and ranking (create + read + update, again).
- Launch Cal and Event Planning Hub — planning filters, risk signals, request templates, dates.
Read any of those as "what does this brief imply," and you will see the same map: identity for who gets in, D1 for what persists, UI for what is merely shown.
Where this fits
The build is rarely the weak link — the brief is. Before we type @Sites, we draft and pressure-test the spec the way we would any plan: which entities persist, what each status can be, who the audience is. We do that on oran.chat, handing the same brief to GPT, Claude, and Gemini and branching the conversation instead of overwriting it, so the prompt Codex receives is the one that already survived three sets of objections. If you want one instruction set to behave the same across those models while you do, see the system prompt that works across GPT, Claude, and Gemini.
The authoritative reference is the Codex Sites documentation, and because the feature is in preview, treat those pages as the live source of truth over anything here. For more like this, see Playbooks.