resource-form
Create and edit form for any Quickback resource, driven by guards, column kinds, masking, and validation from the schema registry.
npx shadcn add @quickback/resource-formimport { ResourceForm } from "@/components/resource-form"
// Create
<ResourceForm table="jobs" onSuccess={(row) => navigate(`/jobs/${row.id}`)} />
// Edit, with a confirmed Delete
<ResourceForm table="jobs" id={jobId} onSuccess={close} onCancel={close} onDeleted={close} />Props
| Prop | Type | Description |
|---|---|---|
table | string | Schema-registry table name. Required. |
id | string | Record id. Set it to edit that record (the form loads it first). Leave it out to create a new one. |
onSuccess | (row) => void | Called with the saved record. The API's bare record, not an envelope. |
onCancel | () => void | Shows a Cancel button when set. |
onDeleted | (id) => void | Edit mode. Shows a Delete button that asks for confirmation inline, then calls this. See Delete for when the button is hidden. |
Which fields appear
The form shows exactly the fields the API accepts:
- Create shows
guards.createable. Edit showsguards.updatable. - These fields are read-only, with persistent helper text saying why:
guards.immutablefields on edit,guards.protectedfields ("Updated via actions only"), and masked fields the caller's roles can't see unmasked. - The form needs the table's
create(create mode) orupdate(edit mode) route. Without it, submitting fails with"<table>" has no create route.
Controls
| Column | Control |
|---|---|
inputHints entry textarea / richtext | Textarea |
| Enum validation | Select of the allowed values |
FK (.references()) | Searchable combobox over the target table (see below) |
bool | Checkbox |
timestamp / date | Date-time / date input |
decimal | Text input. q.decimal() is an exact decimal string, and a number input would round it. |
json or array | Textarea (JSON) |
int, bigint, smallint, real | Number input, with min / max from validation |
url | URL input |
email validation | Email input |
| anything else | Text input |
Constraints show as persistent helper text under the field
(Up to 200 characters · Optional), never as placeholder text.
FK lookups query the target table as you type (?search=, debounced)
and load more rows by cursor on demand, so there is no first-page cap. When
the target declares named views, it must set read.defaultView. Without it
the field is disabled with <Target> needs read.defaultView to be looked up.
Validation and errors
- Before submit, the form checks the registry's
validationblock: required (NOT NULL without a default on create, NOT NULL on edit),minLength/maxLength,min/max, whole numbers for integer columns, enum membership, URL, email, andpattern. - On submit, create sends every non-blank value. Edit sends only the
fields you changed. A field you cleared is sent as
null, which clears it. It is never sent as"". - Server errors show the problem's
detailabove the form. Per-field messages fromQuickbackError.fieldsare shown under their fields.
Delete
The Delete button renders only when all of these hold: onDeleted is
set, the table has a delete route, and the caller's roles don't plainly
fail its access.roles. It asks for confirmation before calling the API.