Entity forms
Every entity gets a create and edit form rendered inside a slide-over sheet. Forms are built with React Hook Form, validated by Zod schemas, and support all field types out of the box.
Form inputs by field type
| Field type | Component | Notes |
|---|---|---|
| Text | <Input> or <Textarea> | Textarea when textHtmlType is set to textarea |
| Integer | <Input type="number" step="1"> | Converts empty to blank, not NaN |
| Decimal | <Input type="number"> | Step calculated from scale (e.g., 0.01 for scale 2) |
| Boolean | <Switch> | Toggle, defaults to false |
| Enumerator | <SelectInput> | Single-select dropdown with search |
| Enumerator multiple | <SelectMultipleInput> | Multi-select with badge chips |
| Tags | <TagsInput> | Free-text tags with Enter to confirm |
| Date | <DatePickerInput> | Returns ISO date string |
| Datetime | <DateTimePickerInput> | Uses datetime-local HTML input |
| Files | <FilesUploadDropzone> | Drag-and-drop with format/count limits |
| Images | <ImagesUploadDropzone> | Same as files, image-specific |
Relationships in forms
| Relationship type | Component | Behavior |
|---|---|---|
| One-to-one / many-to-one | <AutocompleteInput> | Single entity selector with search |
| One-to-many / many-to-many | <AutocompleteMultipleInput> | Multi-select with badge display |
Autocomplete inputs support two loading modes:
- Memory — loads all options on open (for small datasets)
- Async — searches the server with debounce (for large datasets)
Validation
Validation is defined per field via the validations property in your entity schema and enforced at both layers:
- Frontend — Zod schemas with React Hook Form. Errors appear below each field.
- Backend — Same Zod schemas re-validated on the server. Unique constraint violations return localized error messages.
| Validation | Applies to | Effect |
|---|---|---|
required | All types | Field cannot be empty. Label shows required indicator. |
min / max | Text | Minimum / maximum character length |
min / max | Integer, decimal | Minimum / maximum numeric value |
min / max | Date, datetime | Earliest / latest allowed date |
scale | Decimal | Number of decimal places (controls input step) |
unique | Text, numbers | Server-side uniqueness check within the organization |
Create vs edit
Both forms use the same component — the mode is determined by whether an existing entity is passed as a prop.
- Create —
POST /api/{entity-name}. All required fields must be filled. - Edit —
PUT /api/{entity-name}/{id}. Partial schema allows submitting only changed fields.
The first field in sort order receives autoFocus.
Optimistic concurrency control
Edit forms include a hidden updatedAt field. On save, the backend compares this timestamp against the current database record. If another user modified the entity in the meantime, the request is rejected with a stale data error — preventing silent overwrites.
Unsaved changes protection
Forms track dirty state via React Hook Form's isDirty flag. If a user tries to navigate away with unsaved changes, TanStack Router's useBlocker hook intercepts the navigation and shows a confirmation dialog.
Field and relationship ordering
Fields and relationships are rendered in the order defined by their sortOrder (fields) and orderA / orderB (relationships) properties. The template uses utils.getFieldsAndRelationships() to merge both into a single sorted list.
Key files
| File | Purpose |
|---|---|
src/features/{entity}/components/{Entity}Form.tsx | Main form component with all field inputs |
src/features/{entity}/components/{Entity}FormSheet.tsx | Sheet wrapper managing open/close state |
src/shared/components/form/ | Reusable form input components |