
Forms that write themselves from the same schema
19 Jul 2026
A form is usually the most tedious part of building an admin: the right input per field, client and server validation kept in sync, and a create form and an edit form that quietly drift apart from each other over time. In Phlo, the create form, the edit form, the list view and the REST API all read the exact same schema, so there is only one place any of that can drift from.
The input follows the type
static schema => arr(
status: field(type: 'select', options: ['draft', 'sent', 'paid']),
notes: field(type: 'text', multiline: true),
cover: field(type: 'image'),
sent_at: field(type: 'datetime'),
)
A select renders a dropdown constrained to its declared options, text with multiline: true renders a textarea instead of a single-line input, image renders an upload control with a thumbnail preview, datetime renders the matching picker. None of this is written per form; each field type resource (fields/select, fields/image, and so on) owns its own input() rendering, and the CMS just asks the schema which field type owns a given column.
Validation before it ever gets to the database
Opting a model into static objValidate = true runs the matching objValidate($value) for every field in the schema before create() commits anything, a pattern: or enum: constraint declared on the field is the same rule the create form and the create API enforce, because both call into the same field object:
if (!user::create($args)) return apply(errors: user::objErrors())
required: true goes further and is checked uniformly on both create and edit; the rest of objValidate is a create-time gate today. Either way, there is no separate client-side validation library to keep in sync with a server-side rule that was written once and then quietly forgotten about on the frontend.
CSRF, by default
Every write, POST, PUT, PATCH, DELETE, is CSRF-protected without extra configuration on the developer's part. That is not a form-specific feature so much as a consequence of the write path being one shared mechanism instead of one per model: secure the mechanism once, and every model that goes through it inherits the protection automatically.
The actual saving
None of this is about any single form looking nice. It is that a schema change, adding a field, tightening a validation rule, changing an input type, updates the list view, both forms and the API at once, because there was only ever one description of the model to change.