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 typeComponentNotes
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 typeComponentBehavior
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.
ValidationApplies toEffect
requiredAll typesField cannot be empty. Label shows required indicator.
min / maxTextMinimum / maximum character length
min / maxInteger, decimalMinimum / maximum numeric value
min / maxDate, datetimeEarliest / latest allowed date
scaleDecimalNumber of decimal places (controls input step)
uniqueText, numbersServer-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.

  • CreatePOST /api/{entity-name}. All required fields must be filled.
  • EditPUT /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

FilePurpose
src/features/{entity}/components/{Entity}Form.tsxMain form component with all field inputs
src/features/{entity}/components/{Entity}FormSheet.tsxSheet wrapper managing open/close state
src/shared/components/form/Reusable form input components