Resource shape
The form resource is the public wire contract between Laravel and @inertify/form-vue. Field-specific properties are additive; consumers should read the keys they understand and ignore unknown keys.
Use the exported TypeScript types rather than duplicating the contract:
import type {
FormField,
FormFieldInstance,
FormFieldset,
FormResource,
FormUploadDescriptor,
WizardResource,
} from '@inertify/form-vue'
Top-level form
Every authorized form has these top-level keys:
{
"action": "/profiles/1",
"method": "PATCH",
"fieldsets": [],
"data": {},
"dataAttributes": null,
"meta": null,
"unsavedWarning": true,
"scrollToFirstError": true,
"wizard": null
}
| Key | Shape | Meaning |
|---|---|---|
action | string | null | Submission URL. A form without an action can still be used for local state. |
method | GET | POST | PUT | PATCH | DELETE | Uppercase submission method. |
fieldsets | FormFieldset[] | Authorized schema groups in declaration order. |
data | object | Authorized initial values, normalized defaults, and existing-file descriptors. An empty value serializes as {}, not []. |
dataAttributes | object or null | Application-owned form data attributes with normalized data-* keys. |
meta | object or null | Application metadata plus generated query-backed combobox option pages when needed. |
unsavedWarning | boolean | Enables the browser navigation confirmation while dirty. |
scrollToFirstError | boolean | Enables first-error scrolling when an application input registered its element. |
wizard | object or null | Serialized wizard behavior and visible steps. |
An unauthorized form returns the same keys with action: null, method: "POST", empty fieldsets/data, both flags false, and nullable values set to null. When authorization.throw_on_unauthorized is enabled, serialization throws instead.
Fieldsets
{
"id": "identity",
"legend": "Identity",
"description": "Account details",
"fields": [],
"visibility": null,
"clearWhenHidden": false,
"dataAttributes": null,
"meta": null
}
Fieldsets do not create a value path. They may contain fields or nested fieldsets. Unauthorized children are omitted. A null ID is resolved by Vue to a deterministic index-based identifier.
Base field
All authorized fields begin with this shape and add their behavior-specific options:
{
"component": "Text",
"name": "email",
"label": "Email",
"default": null,
"help": null,
"placeholder": null,
"rules": ["required", "string", "email"],
"required": true,
"nullable": false,
"precognitive": true,
"disabled": false,
"readonly": false,
"autofocus": false,
"modelBinding": true,
"visibility": null,
"clearWhenHidden": false,
"dataAttributes": null,
"meta": null,
"inputType": "email",
"clearable": false,
"copyable": false,
"viewable": false
}
| Key | Contract |
|---|---|
component | Exact semantic renderer discriminator. It is a frontend protocol key, not an HTML element or PHP class name. |
name | Canonical schema name. Nested schemas keep their relative name here. |
label, help, placeholder | Resolved functional content. |
default | Resolved field default, or null when no default was declared. Initial current data lives in top-level data. |
rules | JSON-safe descriptions of Laravel rules. Object rules are represented by class name. |
required, nullable, precognitive, disabled, readonly, autofocus | Resolved semantic state for the current resource request. |
modelBinding | Whether server initial-value binding is enabled for the field. |
visibility | A condition, group, literal boolean, or null. |
clearWhenHidden | Whether Vue clears the value on a visible-to-hidden transition. |
dataAttributes, meta | Consumer-owned serializable values. |
There is no authorized property in a normal PHP resource: unauthorized fields are absent entirely. The TypeScript shape accepts compatibility keys from compatible producers, but applications should not depend on them being emitted here.
Qualified field instances
The server schema stores relative child names. Vue resolves one FormFieldInstance for every concrete row:
interface FormFieldInstance extends FormField {
path: string // "projects.0.title"
schemaName: string // "title"
schemaField: FormField // original serialized schema object
rowPath: string | null // "projects.0"
fieldsetIndex: number
ancestorVisibility: FormVisibilityCondition[]
ancestorClearWhenHidden: boolean
}
Use path for input names, errors, slots, element registration, and composable calls. Use schemaField to inspect the original relative declaration.
Visibility conditions
A simple condition serializes as:
{
"field": "is_employed",
"operator": "=",
"value": true,
"dependsOn": ["is_employed"]
}
Unary operators (empty, not_empty, truthy, and falsy) omit value.
Groups serialize recursively:
{
"mode": "and",
"conditions": [
{
"field": "kind",
"operator": "=",
"value": "business",
"dependsOn": ["kind"]
},
{
"mode": "not",
"conditions": [
{
"field": "archived",
"operator": "truthy",
"dependsOn": ["archived"]
}
],
"dependsOn": ["archived"]
}
],
"dependsOn": ["kind", "archived"]
}
Group modes emitted by PHP are and, or, and not. A path beginning with $. is always root-relative; otherwise Vue and PHP first allow a current collection row to satisfy the dependency.
Visibility state is still client-observable and client-modifiable. Never use it in place of authorization.
Nested schemas
Repeater
{
"component": "Repeater",
"name": "projects",
"schema": [
{
"component": "Text",
"name": "title",
"label": "Title"
}
],
"defaultItem": {
"title": null
},
"minItems": 1,
"reorderable": true
}
The abbreviated child above also contains every base field key. Current rows live at data.projects; defaultItem is the application-ready template used when appending.
Blocks
{
"component": "Blocks",
"name": "content",
"sets": [
{
"type": "hero",
"label": "Hero",
"description": "Page hero",
"maxItems": 1,
"defaultData": {
"heading": "Welcome"
},
"schema": [],
"dataAttributes": null,
"meta": null
}
]
}
Current rows use one canonical data shape:
[
{
"type": "hero",
"data": {
"heading": "Welcome"
}
}
]
Laravel validates type against authorized block sets and validates data against the selected set's schema.
Key-value
KeyValue emits mode, keyRules, and valueRules, plus configured labels and limits. Keyed mode uses an object; single mode uses a list. Vue renderers may project either shape into editable rows, but must submit the canonical configured shape.
Choices
Finite Radio, CheckboxGroup, and Combobox options use normalized records:
{
"value": 1,
"label": "Administrator",
"description": "Full access",
"disabled": false,
"disabledReason": null
}
Combobox records may additionally contain group, image, avatar, badge, url, metadata, and selectedSuffix.
An application JSON endpoint may return a plain option array, a paginator-shaped object whose data contains options, or a supported continuation shape. Record creation may return one option directly or as { "item": option }.
Query-backed option metadata
Request-aware Eloquent/query choices do not embed the full result set in the field. The field points to its page under form metadata:
{
"meta": {
"options": {
"assignee_id": {
"data": [
{ "value": 1, "label": "Ada" }
],
"current_page": 1,
"per_page": 25,
"total": 80,
"last_page": 4,
"next_page_url": "/profiles?page=2"
}
}
}
}
The corresponding field includes:
{
"options": [],
"optionsKey": "assignee_id",
"optionsMode": "inertia",
"selected": [
{ "value": 42, "label": "Selected person" }
]
}
Nested option keys use schema paths with * placeholders. The Vue combobox controller owns targeted partial reloads and resolves each concrete field against this metadata.
Wizard
{
"enabled": true,
"allowSkip": false,
"validateOnStep": true,
"steps": [
{
"fieldset": 0,
"title": "Identity",
"description": "Account details"
}
],
"nextLabel": "Continue",
"prevLabel": "Back",
"submitLabel": "Save"
}
fieldset is the original fieldset index. Hidden or unauthorized fieldsets and fieldsets without a visible authorized child are omitted from steps. Labels may be null; Vue supplies its normal Next, Previous, and Submit fallbacks.
Upload descriptor
Package-managed file fields expose both flat compatibility properties and a nested descriptor. New renderers should prefer upload:
{
"strategy": "chunked",
"endpoints": {
"destroy": {
"method": "DELETE",
"url": "/_inertia-forms/file-upload"
},
"chunked": {
"start": {
"method": "POST",
"url": "/_inertia-forms/file-upload/chunked/start"
},
"status": {
"method": "GET",
"url": "/_inertia-forms/file-upload/chunked/status"
},
"append": {
"method": "POST",
"url": "/_inertia-forms/file-upload/chunked/chunk"
},
"complete": {
"method": "POST",
"url": "/_inertia-forms/file-upload/chunked/complete"
},
"abort": {
"method": "DELETE",
"url": "/_inertia-forms/file-upload/chunked/abort"
}
}
},
"limits": {
"maxSizeKiB": 10240,
"chunkSizeBytes": 5242880,
"directMaxSizeKiB": 5242880,
"partSizeBytes": 16777216,
"multipartThresholdBytes": 104857600
},
"disk": null,
"rulesToken": "encrypted-expiring-token",
"requiresRulesToken": true
}
strategy is temporary, form, chunked, or direct. Endpoint sets vary by strategy. form has no package endpoints.
The same field may include storeWithForm, temporaryUploadUrl, temporaryUploadDeleteUrl, chunked, chunkSize, chunkedUrls, directToStorage, uploadDisk, uploadPartSize, uploadMultipartThreshold, directUploadUrls, uploadRulesToken, and requiresUploadRulesToken for compatibility.
Uploaded and existing files
Upload responses and existing-file values contain a stable client key plus display metadata. Existing files include compatibility aliases:
{
"key": "encrypted-expiring-token",
"id": "private:documents/report.pdf",
"identifier": "private:documents/report.pdf",
"filename": "report.pdf",
"name": "report",
"previewUrl": "https://example.test/temporary-preview",
"preview_url": "https://example.test/temporary-preview",
"mimeType": "application/pdf",
"mime_type": "application/pdf",
"size": 12345,
"size_in_bytes": 12345,
"metadata": {}
}
The encrypted key, not the display metadata, is the submitted reference resolved by Laravel. Applications must still authorize the operation against their own model.
Composer and rich text
Without attachments, Composer data is a string or null. With allowAttachments(), it is:
{
"text": "Message body",
"attachments": ["encrypted-upload-token"]
}
RichText data remains an HTML string. Image uploads add a separate sibling list:
{
"body": "<p>Hello</p><img data-inertia-forms-upload=\"encrypted-upload-token\">",
"body_images": ["encrypted-upload-token"]
}
The HTML marker list and companion list must contain the same unique token set; order and duplicate entries are ignored. The PHP rich-text helper verifies correspondence before storage, but HTML sanitation remains the application's responsibility.
Compatibility guidance
- Switch on
component, not a PHP class name guessed from a label or value. - Treat unknown field properties and unknown option-record properties as additive.
- Use generated qualified
pathvalues in Vue rather than concatenating nested names yourself. - Use public package types and entry points; do not couple renderers to package implementation modules.
- Never infer server authorization from the presence, visibility, disabled state, metadata, or client rules of a field.