CoreActionForm connects your form to that action and handles the full lifecycle — validation errors mapped to fields, loading state, confirmation dialogs, and redirects.
There are three ways to render an action’s form:
- Schema auto-form — generated from the action’s
schema(), zero frontend code - Custom form — your own React component, wired to the action
- Embedded card — either of the above, rendered inline on a page
Schema Auto-Form
If your action defines aschema(), the platform auto-generates a form with the correct inputs, labels, and validation. This is the default when no custom form() is defined.
app/Actions/Order/CreateOrderAction.php
CoreActionButton for this action is clicked with missing parameters, a modal opens with auto-generated inputs for customer, ordered_at, and notes. No frontend form code needed.
Pre-Filled and Hidden Parameters
When parameters are pre-filled viaparams() or frontend params/getParams, matching schema fields are hidden automatically. The field is still submitted to the action, but it is not shown in the form. When using $subjectClass, the subject is handled entirely separately and never appears as a schema field.
Custom Form Component
For complex forms that need custom layout, conditional fields, or specialized inputs, provide a React component viaform():
app/Actions/Order/CreateOrderAction.php
form() is a data method — it returns a component path and serializable props, but never renders React itself. The platform calls form() during action serialization and includes the result in CoreActionData. The React component is resolved client-side via dynamic import() and receives the props — no separate AJAX request.
In table contexts,
form() is not called per row — that would be expensive. The form component and props are only serialized when the action is rendered as a standalone button or card on a page. When a user clicks a table row action, the form is loaded on-demand via the modal route.resources/js/pages/orders/create-order-form.tsx
form() is not defined, the schema auto-form is used instead.
Bulk Context
When a custom form component is used by a bulk action,CoreActionForm receives a bulkContext prop. Pass it through to CoreActionForm and the form submission is automatically routed to the bulk endpoint — no separate bulk form needed.
resources/js/pages/orders/set-order-status-form.tsx
bulkContext is present, CoreActionForm posts to /actions/{action}/bulk with the selected IDs and your form values merged. The bulkModelParam (the ObjectProperty that identifies the per-item model) is excluded from the payload — the server injects it per item.
When bulkContext is absent (single-item modal or page form), submission proceeds normally against /actions/{action}.
The CoreActionFormBulkContext type is exported from core-action-form:
Accessing the Action’s Schema
useActionSchema(action) converts the serialized action.schemaProperties array into a keyed record — so schema.customer returns the PropertyMeta for the customer field. This data is already included in CoreActionData whenever the action defines a schema(), no extra props needed.
Controlled Form State with useCoreActionForm
When you need programmatic access to form values, use useCoreActionForm(action, { ... }). It replaces the common useForm() + CoreActionForm pairing by:
- seeding form state from action params, schema values, and schema defaults
- keeping extra bound params separate from editable form values
- reusing the same submit pipeline as
CoreActionForm
CoreActionFormComponent from a parent page (to share state with a map or sibling component), pass the returned form to CoreActionFormComponent and it will be forwarded to your custom component. See CoreActionFormComponent below.
values for editable initial state and params for extra bound values that should stay hidden:
form prop, CoreActionForm creates and manages this hook internally. Reach for useCoreActionForm only when the surrounding UI needs direct access to form.data or form.setData.
When you want full TypeScript autocomplete, type the action prop with the generated action params. If your form has extra frontend-only fields, extend that shape explicitly:
CoreActionData, the hook falls back to a generic record because TypeScript cannot infer the schema from the runtime lookupKey.
Embedded Forms
Embed an action’s form directly on a page — no modal, no button click. There are two components for this:CoreActionCard— renders the form inside aCoreCardwith title and description. Use for standalone create/edit pages.CoreActionFormComponent— renders just the form, no card wrapper. Use when you need to embed the form in a custom layout, or when you need controlled access to the form state from the parent component.
CoreActionCard
resources/js/pages/orders/order-create.tsx
CoreActionCard renders the action’s form (custom or auto-generated) inside a CoreCard. It is the standard way to build simple create and edit pages.
CoreActionFormComponent
UseCoreActionFormComponent when you need to embed the form in your own layout, or when the parent component needs to read or write form state — for example, to drive a map preview, live calculation, or sibling UI that responds to field changes.
resources/js/pages/orders/order-show.tsx
form instance is forwarded to the custom form component declared via form() on the action. Your custom form component receives it as a form prop — and uses it as the controlled CoreActionForm state. If no form is passed, the custom form component manages its own state internally.
How It Works
There is no AJAX fetch to load the form. Everything needed is already in the serializedCoreActionData:
- Your page controller serializes the action as a normal Inertia prop
- During serialization, the platform calls
form()and includes the component path and props inCoreActionData - On the frontend,
CoreActionCard/CoreActionFormComponentchecks if a custom form component is declared:- Custom form: resolves the React component via
import(), renders it with the serialized props - No custom form: renders
CoreActionSchemaFormfrom the schema properties
- Custom form: resolves the React component via
- The result is a normal React component in the page’s render tree — no iframes, no portals
CoreActionFormComponent
CoreActionFormComponent renders the action’s form without a card wrapper — either the custom component declared via form(), or the schema auto-form as a fallback.
Props
| Prop | Type | Description |
|---|---|---|
action | CoreActionData | The action whose form to render |
form? | Inertia form | Optional — from useCoreActionForm() or useForm() for controlled state; forwarded to the custom form component |
schema? | PropertyMeta[] | Override schema properties (defaults to action.schemaProperties) |
onSuccess? | () => void | Called on successful execution |
onError? | (error: any) => void | Called on non-validation errors |
submitLabel? | string | Submit button label for schema auto-forms |
bulkContext? | CoreActionFormBulkContext | When provided, submits as a bulk action; forwarded to the custom component and schema auto-form |
CoreActionForm
CoreActionForm is the form component for all action mutations. Both the auto-form and custom forms use it internally. You use it directly in custom form components.
Props
| Prop | Type | Description |
|---|---|---|
action | CoreActionData | The action to execute on submit |
form? | Inertia form | Optional — from useCoreActionForm() or useForm() for controlled state |
params? | Record<string, any> | Static parameters merged with form data |
getParams? | () => Record<string, any> | Dynamic parameters computed at submit time |
onSuccess? | () => void | Called on successful execution |
onError? | (error: any) => void | Called on non-validation errors |
onValidationError? | (errors: any) => void | Called on validation errors |
bulkContext? | CoreActionFormBulkContext | When provided, submits as a bulk action with the given IDs and filters |
footer? | ReactNode | Submit button area |
disabled? | boolean | Disable all form fields |
className? | string | Additional CSS classes |
CoreForm props are also accepted.
Parameter Priority
Parameters merge in this order (highest wins):getParams()— dynamic, computed at submit timeparamsprop — static- Form field data — from form fields
- Backend defaults — from
action.params()
Passing Context Parameters
UsegetParams for values that aren’t form fields:
CoreActionSchemaForm
The auto-generated form component. Renders onePropertyCoreFormInput per schema property. You rarely use this directly — it’s what the auto-modal renders — but it’s available for custom layouts:
Schema-Driven Fields in Custom Forms
UsePropertyCoreFormInput for fields that exist in the action’s schema, and plain CoreForm inputs for everything else. The schema carries the type, label, required flag, options, and search config — PropertyCoreFormInput resolves the correct input component automatically.
From the Action’s Own Schema
From a Domain Object’s Schema
When editing an existing record, pass property data from the object’s schema instead:Overriding Defaults with inputProps
Pass inputProps to forward props to the underlying CoreForm component. These merge with — or override — the property-derived configuration:
inputProps are spread onto the resolved input component, so they support the same props as the underlying CoreForm field (CoreFormInput, CoreFormSearchCombobox, CoreFormEnumSelect, etc.).
Validation
Server-Side Validation
Validation errors from the action’sschema() (or rules()) are automatically mapped to matching form fields. No manual wiring needed.
Precognition (Live Validation)
Action forms automatically use Laravel Precognition for real-time field-by-field validation. When the user blurs a field, a lightweight validation request runs against the action’s rules without executinghandle().
This works out of the box — no configuration per action.
Confirmation Dialogs
Actions withconfirm() show a confirmation dialog before submitting. Pass the message directly:
Preconditions in Forms
When an action has preconditions, the form’s submit button should reflect them. UseCoreActionSubmitButton instead of CoreFormSubmitButton — it accepts the action and renders a precondition checklist popover on hover when conditions are unmet.
CoreActionSubmitButton behaves exactly like CoreFormSubmitButton. When any condition is unmet, the button is disabled and hovering shows the checklist — green checks for met conditions, red X marks for unmet ones.
Schema auto-forms and auto-modals use
CoreActionSubmitButton automatically. You only need to wire it manually in custom CoreActionForm layouts.Edit / Update Forms
Edit forms are a common use case. The platform supports pre-filling form fields with existing values so the user can review and modify them.Model-Based Auto-Fill
The recommended approach is to declare$subjectClass on the action and derive the schema from the subject model. When a subject is bound, the platform automatically extracts its attribute values and uses them as property defaults — no frontend code and no manual ->default() calls:
status, notes, and priority pre-filled from the order. See Subject-based actions for details.
Manual Defaults
When you need defaults from a source other than the subject model’s attributes, set->default() on individual properties. Since schema() has access to the subject, use $this->subject() to read current values:
$subjectClass, you can also type-hint the model in schema() directly (old style):
Custom Edit Forms
For custom form components, initializeuseCoreActionForm with any extra editable values you need beyond the action’s schema-derived state:
property.default is used to initialize each field.
Embedded Custom Edit Forms (page-owned form state)
When an edit form is embedded on a show page alongside UI that must react to field changes (maps, previews, calculated values), lift theuseCoreActionForm instance up to the page and pass it to CoreActionFormComponent. The form component receives it as a form prop — use it instead of creating an internal one:
resources/js/pages/cities/city-show.tsx
resources/js/pages/cities/city-edit-form.tsx
canEdit (and any other per-request data) via formProps:
Best Practices
- Use
CoreActionFormfor all mutations. Create, update, and delete forms always go through an action. - Define
schema()whenever possible. Let the platform generate the form. Only provideform()when layout or UX demands it. - Mix and match in custom forms. Use
useActionSchema+PropertyCoreFormInputfor schema-backed fields and plainCoreForminputs for custom data. This keeps labels, types, and validation in sync with the backend while retaining full layout control. - Pass context IDs via
getParams. The current record’s ID belongs ingetParams, not as a hidden form field. - Put submit buttons in
footer. Consistent layout across all forms. - Keep actions focused.
CreateOrderActionandUpdateOrderActionare separate classes. - Declare
$subjectClassfor instance actions. Bind withmake($model), access the model via$this->subject(), and read form fields via$this->get()/$this->string()/$this->integer()etc. — no typed params needed inhandle(). - Use model-based schemas for edit forms.
SubjectClass::schemaDefinition()->only([...])gives automatic pre-fill with zero->default()calls.
Next Steps
Form Components
Full reference for CoreFormInput, Select, Combobox, MultiSelect, Textarea, and more.
Property Rendering
Use PropertyCoreFormInput to derive labels, types, and validation from your object schema.