Parent-Child Modules (inline_items)
A form where a parent record (an Order) is created/edited together with a list of child rows (its Order Items) in the same UI, in one submit — not two separate modules a user navigates between. See Related-record tabs for the other, different, related-modules mechanism this engine has.
Reference fixture:orders-suite — Orders/OrderItems — the canonical, fully-verified example.
inline_items is not documented on the main Module Config reference page's top-level table — it's entirely hand-authored, nothing about it is introspected, so it's covered here instead.
Building it
Follow the generate → hand-edit → regenerate loop:
- Generate the child module first — this order is required, not just convenient: the parent's
inline_itemsconfig (step 3) references the child's already-registered namespace, so the child module must exist before the parent's config can resolve it. It's an ordinary module generation on its own otherwise:bashSee the pitfall below — this exact ordering has one known side effect worth knowing about before you hit it.php artisan make:module Custom/OrderItems - Generate the parent module:bash
php artisan make:module Custom/Orders - Hand-edit the parent's
module.json, adding aninline_itemsentry:json"inline_items": [{ "key": "order_items", "label": "Order Items", "child_module": "OrderItems", "child_group": "Custom", "child_has_creator_updater": true, "parent_fk": "order_id", "primary_field": "product_name", "fields": [ {"key": "product_name", "label": "Product", "type": "text", "required": true}, {"key": "quantity", "label": "Qty", "type": "number", "required": true}, {"key": "unit_price", "label": "Unit Price", "type": "number", "required": true, "decimals": 4} ], "inject_from_parent": [ {"child_field": "currency", "parent_field": "currency"} ] }]inline_itemsis a flat list — not a map keyed by a group/label name.child_has_creator_updater: trueis required if — as is the project's default convention — the child table hascreated_by_id/updated_by_idcolumns; omitting it when the child needs it fatal-errors on the first real create (found and fixed while building this fixture — see Gotchas).inject_from_parentis optional — it copies a value straight from the parent model onto every child row at save time (here, the currency the order was placed in). If the child module itself lives under a nested sub-group (e.g.Modules\System\Inventory\StockTransferItems, not a bare top-levelModules\Custom\{Child}), also setchild_group_name(StudlyCase or plain,Str::studly()-normalized) to that sub-group — e.g."child_group_name": "Inventory". Omitting it when the child needs it generates a namespace reference missing that segment, which fatal-errors the same way a missingchild_has_creator_updaterdoes. - Regenerate the parent with
--forceto pick up the hand-edited config:bashphp artisan make:module Custom/Orders --force
What you get
The Orders create form gets an "Order Items" section embedded below its own fields, starting empty:

Clicking "Add Item" opens a modal for one child row — filled in and submitted without ever leaving the parent Order's own create/edit form:

...and the row lands right back in the parent form, ready for the next one or for the whole Order (parent + every child row) to be submitted together, in one request:

- The Orders create/edit form gets an embedded, hand-edit-protected wrapper component (
OrdersOrderItemsInlineItems.vue) rendering the child rows inline — written once, never touched by future regeneration, so you can freely add per-field logic (dynamicDisabled,showField, cross-field totals on@item-change) without it being clobbered by a later--force. OrdersCreateServicesaves every child row on create.OrdersEditServicesyncs on edit: rows removed from the form are deleted, rows matched byuuidare updated in place, new rows (nouuidyet) are inserted.OrdersViewServiceloadsorder_itemsalongside the parent record.OrdersDeleteServicecascade-deletes everyorder_itemsrow when the parent Order is deleted (respecting the child's own soft/hard delete mode automatically) — an inline_items child has no independent lifecycle by design, so cascading on parent delete is the same ownership rule applied once more.OrdersDeleteCheckService's dependent-count check already flagsorder_itemsas a blocking dependent too — for free, via the same generic FK-graph naming-convention detection that covers any*_idcolumn, entirely independent of theinline_itemsconfig above.
Known limitation — the child's own FK relation can misresolve its namespace
Because the child (OrderItems) is scaffolded in step 1 while the parent (Orders) doesn't exist anywhere yet, OrderItemsModel's own generated order(): BelongsTo relation has nothing to resolve Orders' real namespace/module-type against, and silently guesses wrong (e.g. guesses Core\Orders\OrdersModel when Orders is actually Custom-grouped) — a different bug from anything else on this page, in the ordinary FK-relation namespace resolver (PathManager::resolveBackendModuleNamespace()), not the inline_items-specific namespace handling. Found live 2026-08-08.
There is no way to resolve this correctly at the moment OrderItems is generated — Orders genuinely doesn't exist yet. Fix: regenerate the child once more, after the parent has been scaffolded:
php artisan make:module Custom/OrderItems --forceA misresolved namespace does print a warning at generation time (routed through PathManager::reportIssue() → the console's $this->warn() output) — watch for a "not found in project or shell registry" message after step 1 as the signal you'll need this extra regenerate.
See the fixture's own README for the full verification steps this was actually tested against, including confirming a --force regenerate preserves both inline_items itself and a hand-edited wrapper component.