Forms Overview ​
Modularous forms are schema-driven. The backend defines inputs; Hydrates transform them into a frontend schema; Vue components consume and render them.
What Are Hydrates? ​
Hydrates are PHP classes that sit between your module config and the frontend form. Each input type has its own Hydrate class that transforms a raw config array into a well-defined schema object the Vue side can render.
Module config → InputHydrator → XxxHydrate → schema → Vue component
{ type: 'checklist', ... } ↓ { type: 'input-checklist', items: [...], ... }
resolves class nameWhat they do? ​
- Set defaults — fill in missing keys (e.g.
itemValue: 'id',itemTitle: 'name') - Change the type — convert
'checklist'(config alias) →'input-checklist'(frontend type) - Load records — call a repository or connector to populate
itemsfor selectable inputs - Apply rules — parse
rulesstring and add CSS classes (e.g.required) - Strip backend keys — remove
route,model,repository,cascades,connectorbefore the schema reaches the frontend
Why they exist? ​
The Hydrate layer decouples module config syntax from frontend schema syntax. You write concise config in PHP; the Hydrate handles enrichment, fetching, and normalization. The frontend only ever sees a clean, complete schema object.
Resolution rule ​
InputHydrator resolves the class by: studlyCase($input['type']) . 'Hydrate'
Config type | Resolved class |
|---|---|
checklist | ChecklistHydrate |
select-scroll | SelectScrollHydrate |
filepond-avatar | FilepondAvatarHydrate |
All Hydrate classes live in src/Hydrates/Inputs/.
Render Pipeline ​
Every Hydrate runs the same pipeline when render() is called:
setDefaults() — apply $requirements defaults
hydrate() — set output type, enrich schema
hydrateRecords() — load items via repository/connector
hydrateRules() — parse rules string, add CSS classes
Arr::except() — strip backend-only keysForm Rendering Flow ​
- Module config — define inputs in your module's
config.php - Controller —
setupFormSchema()callsInputHydratorbefore create/edit - Inertia — hydrated schema + model are passed to the page
- Form.vue — receives
schemaandmodelValue, usesuseForm - FormBase — flattens schema + model into
flatCombinedArraySorted, iterates over each field - FormBaseField — renders each field by
obj.schema.typeviamapTypeToComponent() - Input components — receive schema props via
bindSchema(obj)
Key Components ​
| Component | Purpose |
|---|---|
| Form.vue | Top-level form; validation, submit, schema/model sync |
| FormBase | Iterates over flattened schema; grid layout, slots |
| FormBaseField | Renders a single field; resolves type → component |
| CustomFormBase | Wrapper with app-specific behavior |
Schema Structure ​
Each field in the schema has:
type— Resolved to Vue component (e.g.input-checklist,text,select)name— Field name (binds to model)label— Display labelcol— Grid column spanrules— Validation rulesdefault— Default value
Backend-only keys (stripped before frontend): route, model, repository, cascades, connector
Config → Component Reference ​
| Config type | Hydrate class | Output type | Vue component |
|---|---|---|---|
| assignment | AssignmentHydrate | input-assignment | VInputAssignment |
| autocomplete | AutocompleteHydrate | select / input-select-scroll | v-autocomplete |
| browser | BrowserHydrate | input-browser | VInputBrowser |
| chat | ChatHydrate | input-chat | VInputChat |
| checkbox | CheckboxHydrate | checkbox | v-checkbox |
| checklist | ChecklistHydrate | input-checklist | VInputChecklist |
| checklist-group | ChecklistGroupHydrate | input-checklist-group | VInputChecklistGroup |
| combobox | ComboboxHydrate | combobox / input-select-scroll | v-combobox |
| comparison-table | ComparisonTableHydrate | input-comparison-table | VInputComparisonTable |
| date | DateHydrate | input-date | VInputDate |
| file | FileHydrate | input-file | VInputFile |
| filepond-avatar | FilepondAvatarHydrate | input-filepond-avatar | VInputFilepondAvatar |
| form-tabs | FormTabsHydrate | input-form-tabs | VInputFormTabs |
| image | ImageHydrate | input-image | VInputImage |
| json | JsonHydrate | group | (group layout) |
| json-repeater | JsonRepeaterHydrate | input-repeater | VInputRepeater |
| payment-service | PaymentServiceHydrate | input-payment-service | VInputPaymentService |
| price | PriceHydrate | input-price | VInputPrice |
| process | ProcessHydrate | input-process | VInputProcess |
| radio-group | RadioGroupHydrate | input-radio-group | VInputRadioGroup |
| repeater | RepeaterHydrate | input-repeater | VInputRepeater |
| select-scroll | SelectScrollHydrate | input-select-scroll | VInputSelectScroll |
| spread | SpreadHydrate | input-spread | VInputSpread |
| switch | SwitchHydrate | input-switch | VInputSwitch |
| tag | TagHydrate | input-tag | VInputTag |
| tagger | TaggerHydrate | input-tagger | VInputTagger |
FormBase Slots ​
FormBase provides slots for customization:
form-top,form-bottom— Form-level{type}-top,{type}-bottom— By schema type (e.g.input-checklist-top){key}-top,{key}-bottom— By field name{type}-item,{key}-item— Override field rendering
Adding a New Input ​
- PHP — Create
src/Hydrates/Inputs/{Studly}Hydrate.phpextendingInputHydrate- Set
$input['type'] = 'input-{kebab}'inhydrate() - Define
$requirementsfor default schema keys
- Set
- Vue — Create
vue/src/js/components/inputs/{Studly}.vue- Use
useInput,makeInputProps,makeInputEmitsfrom@/hooks - Component auto-registers as
VInput{Studly}viaincludeFormInputsglob
- Use
- Registry (optional) — Add to
hydrateTypeMapinregistry.jsfor explicit mapping
See the create-input-hydrate and create-vue-input commands.