Custom pages¶
Conjure's promise is you own the output. In codegen mode every screen is plain React in your repo. This guide covers editing generated pages, preserving your edits across regeneration, and ejecting a runtime page into code.
Anatomy of a generated page¶
Each model in codegen mode produces a small, predictable page set under
web/src/pages/{model-kebab}/:
| File | Responsibility |
|---|---|
schema.ts |
The model's field metadata (from the schema snapshot). |
columns.tsx |
Column definitions for the list DataTable. |
index.tsx |
The list page (search / filter / sort / bulk). |
form.tsx |
The create / edit form (zod-validated). |
detail.tsx |
The detail view (+ inline child editors). |
Read-only models get a 3-file variant (no form.tsx; detail is a dialog).
Editing a page¶
These are ordinary .tsx files — change them freely. Common edits:
- Replace a cell. In
columns.tsx, swap a column'scellrenderer (e.g. tint an order total with a custom cell instead ofMoneyCell). - Add a header button. Add to the list
PageHeader actionsor the bulk bar. - Tweak a form field. Reorder, add help text, or wire a custom widget in
form.tsx.
// @custom: total column tinted red when refunded (1)
{
accessorKey: "total",
header: "Total",
cell: ({ row }) => (
<MoneyCell value={row.original.total} tone={row.original.refunded ? "loss" : "default"} />
),
}
- The
// @custom:marker is the contract — see below.
Preserving edits across regeneration¶
When a model's fields change, you re-dump the schema and regenerate the page. To keep your hand edits, mark them:
Put the marker at the top of a file (whole-file ownership) or above a block. On
regeneration the codegen step performs a diff merge and preserves anything tagged
// @custom.
Commit before regenerating
The merge is diff-based. Always git commit your custom edits before re-running
codegen so you have a clean rollback.
Ejecting a runtime page¶
The hybrid workflow: let runtime mode render most models, then eject the few that need to be special.
graph LR
A["Runtime GenericModelPage 📋"] -->|eject| B["Generate pages/{model}/*.tsx"]
B --> C["Edit + own the code"]
Eject = generate the codegen page set for that one model and route to it instead of the generic renderer. From then on it's your code. The rest of the admin keeps running on the runtime renderer untouched. See Customization → eject.
New composed components¶
The compact UI kit under components/ui/ is frozen — don't edit it. Build variants in
components/composed/ and register them on the /style-guide page (this is a project
convention so the catalog stays complete).
components/ui/ ← frozen primitives (compact, 32px density)
components/composed/ ← DataTable, FkCombobox, InlineTable, your additions
Dashboard widgets¶
To add a dashboard card or chart:
- Write a function in the backend
widgets.pyand register it (@register_widget). - Add the card/chart to
pages/dashboard/index.tsx, callingadminApi.widget("name").
Widgets use Recharts. See Extension points for the registry contract.