Generated record screens and forms
View as MarkdownThis page traces table metadata into generated list, create, detail, and edit screens. You will create a project and related task without adding React code, then identify which schema choices control each field. The same surfaces can provide the first usable record workflow for asset registers, team directories, cases, and approval queues.
Set up generated project and task screens. I want named project lookups, status and priority selects, search, and a Tasks section on each project.Predict the screen from the schema
Section titled “Predict the screen from the schema”Registered table metadata drives the ordinary CRUD experience:
import { sqliteTable } from "drizzle-orm/sqlite-core";import { sapportaTable, select } from "@sapporta/server/table";
export const tasksTable = sqliteTable("tasks", { // Other columns omitted for focus. status: select("status", ["open", "in_progress", "completed"] as const) .notNull() .default("open"), priority: select("priority", ["low", "medium", "high"] as const) .notNull() .default("medium"),});
export const tasks = sapportaTable({ drizzle: tasksTable, meta: { label: "Tasks", rowScope: "workspaceGlobal", rowLabelColumns: ["title"], search: { columns: ["title", "description"] }, columns: { project_id: { label: "Project" }, description: { textDisplay: "multiLine" }, }, },});Column kinds choose default inputs and formatting. Each select() option tuple
constrains the stored string and supplies a searchable, clearable combobox. The
text typed into that combobox filters its declared options; it is not stored as
the field value. A Drizzle foreign key plus the project rowLabelColumns turns
project_id into a scoped lookup that stores an ID and displays a name.
System-managed scope fields, generated primary keys, and columns with
apiWritable: false stay out of ordinary forms.
The project definition supplies the reverse navigation:
children: [ { table: "tasks", foreignKey: "project_id", label: "Tasks", columns: ["title", "status", "due_date"], defaultSort: "due_date", },];Run the record workflow
Section titled “Run the record workflow”- Start the app with
pnpm devand open/tables/projects. - Create a project named Website Relaunch.
- Open
/tables/tasks/newand create Publish launch checklist. - Choose Website Relaunch in the Project lookup. Set status to open, priority to high, and add a due date.
- Edit the saved task, then open its project record and find the task in the Tasks child section.
Lookup results contain only rows visible to the current user. Generated form validation and generated HTTP validation use the same table definition. A hidden field or a fixed client filter is presentation, not an authorization rule.
Keep draft text until submit
Section titled “Keep draft text until submit”Numeric, money, percentage, date, and timestamp inputs keep raw strings while a
record is being edited. Intermediate numeric text such as - or 12. therefore
stays visible. The form decodes the full draft once on submit. Finite numeric
text becomes a JSON number, dates and timestamps become canonical strings, and
invalid input produces an issue beside its field without rewriting the draft.
Create presence rules are applied during that decode. Empty optional non-text
controls are omitted so database defaults and nullable insert rules still apply.
Empty text remains "", which is distinct from null. Required empty controls
produce local issues. The server then applies API write policy, adds trusted
scope values, checks reference visibility, and performs authoritative structural
and application validation immediately before the Drizzle write.
The project and task schemas now provide a complete ordinary record workflow: create, edit, search, lookup, detail, child navigation, copy, and export. Start with metadata when the behavior is still table-shaped. Add a custom React screen only when the workflow needs a different composition, temporary state, or domain interaction model. The next guide compares those Grid layers.