Embedded forms
Embedded forms let you include one form as a component inside another form. In FormEngine Core, you use the EmbeddedForm component
together with the getForm callback to load and display a nested form at runtime.
In this guide, you'll learn:
- How the
EmbeddedFormcomponent works withgetForm - When to use embedded forms versus other composition techniques
- How to control data storage with
storeDataInParentForm - Practical patterns for reusable form sections and multi-step workflows
Overview
An embedded form is a complete FormEngine form that renders inside a parent form. The parent form supplies the nested form's JSON definition
through the getForm function, and the two forms share the same React context, event system, and validation lifecycle.
Embedded forms are useful for:
- Reusable form sections (address blocks, contact information, payment details)
- Conditional form parts that load only when needed
- Multi-step workflows where each step is a separate form definition
- Dynamic form composition based on user choices or external data
The EmbeddedForm component is the container. It doesn't define the nested form itself, instead, it asks your getForm function for the
form JSON, using either a formName string or an options object you provide.
How embedded forms work
The getForm function
getForm is a callback that receives a formName and options and returns a form JSON string. When you place an
EmbeddedForm component in your form, FormEngine Core calls getForm with the embedded form's formName and
options props, expecting back the JSON for the nested form.
type GetForm = (formName?: string, options?: any) => string | Promise<string>
Basic flow
- The parent form renders and encounters an
EmbeddedFormcomponent. - FormEngine Core extracts the
formNameand/oroptionsfrom the component's props. - It calls
getForm(formName, options). - Your implementation returns the JSON string for the nested form.
- FormEngine Core parses that JSON and renders the nested form inside the
EmbeddedFormcontainer. - Validation, events, and data changes in the nested form bubble up to the parent form according to the
storeDataInParentFormsetting.
Data storage options
Embedded forms can store their data in two ways, controlled by the storeDataInParentForm property:
storeDataInParentForm: true(default): The nested form's field values appear as top‑level keys in the parent form's data object. This is useful when you want all fields in a single flat structure.storeDataInParentForm: false: The nested form's data is kept as a nested object under the embedded form'sdataKey. This keeps the nested form's data separate and is easier to extract as a unit.
EmbeddedFormProps properties
The EmbeddedForm component accepts the following props:
| Prop | Type | Default | Description |
|---|---|---|---|
storeDataInParentForm | boolean | true | If false, the nested form's data is stored as a nested object under the embedded form's dataKey. If true, the nested form's fields appear as top‑level keys in the parent form's data. |
formName | string | — | The name passed to getForm to identify which form to load. You have to provide at least one of formName or options. |
options | any | — | An arbitrary object passed to getForm. Useful when you need to parameterize the nested form (for example, load different fields based on user type). |
disabled | boolean | false | If true, the entire nested form is disabled. |
readOnly | boolean | false | If true, the entire nested form is read‑only. |
You have to specify either formName or options (or both). If neither is provided, the EmbeddedForm renders nothing.
Examples
Basic embedded form
A parent form that includes a reusable address block as an embedded form.
Parent form JSON:
{
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "name",
"type": "MuiTextField",
"props": {
"label": {
"value": "Full name"
}
}
},
{
"key": "addressBlock",
"type": "EmbeddedForm",
"props": {
"formName": {
"value": "address-form"
},
"storeDataInParentForm": {
"value": false
}
}
}
]
}
}
Implementation of getForm:
const getForm = useCallback((formName?: string) => {
if (formName === 'address-form') {
return JSON.stringify({
form: {
key: 'Screen',
type: 'Screen',
children: [
{
key: 'street',
type: 'MuiTextField',
props: {label: {value: 'Street'}}
},
{
key: 'city',
type: 'MuiTextField',
props: {label: {value: 'City'}}
},
{
key: 'zip',
type: 'MuiTextField',
props: {label: {value: 'ZIP code'}}
}
]
}
})
}
// Return parent form JSON for the default case
return JSON.stringify(parentFormJson)
}, [])
Live examples
Simple address block
A form with an embedded address section. The address fields are defined separately and loaded via getForm.
Live example
function App() { const parentForm = useMemo(() => ({ tooltipType: 'MuiTooltip', errorType: 'MuiErrorWrapper', form: { key: 'Screen', type: 'Screen', children: [ { key: 'fullName', type: 'MuiTextField', props: { label: {value: 'Full name'} } }, { key: 'address', type: 'EmbeddedForm', props: { formName: {value: 'address-form'}, storeDataInParentForm: {value: false} } } ] } }), []) const addressForm = useMemo(() => ({ errorType: 'MuiErrorWrapper', form: { key: 'Screen', type: 'Screen', children: [ { key: 'street', type: 'MuiTextField', props: { label: {value: 'Street address'} } }, { key: 'city', type: 'MuiTextField', props: { label: {value: 'City'} } }, { key: 'state', type: 'MuiTextField', props: { label: {value: 'State'} } }, { key: 'zipCode', type: 'MuiTextField', props: { label: {value: 'ZIP code'} } } ] } }), []) const getForm = useCallback((formName) => { if (formName === 'address-form') { return JSON.stringify(addressForm) } return JSON.stringify(parentForm) }, [parentForm, addressForm]) return <FormViewer view={muiView} getForm={getForm}/> }
Slot component
Slot is a placeholder component that allows you to define dynamic areas within an embedded form. When you use an EmbeddedForm, you can
include Slot components in its definition, and then fill those slots with content from the parent form.
How Slot works
The Slot component displays child components for a nested form. Each slot is identified by its own key. Parent form you can embed child
components to a nested form by specifying slot keys.
Example: Form with slot
Embedded form definition (with slot):
{
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "header",
"type": "MuiTypography",
"props": {
"children": {
"value": "Form header"
}
}
},
{
"key": "contentSlot",
"type": "Slot",
"props": {}
},
{
"key": "footer",
"type": "MuiTypography",
"props": {
"children": {
"value": "Form footer"
}
}
}
]
}
}
Parent form using the embedded form:
{
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "mainForm",
"type": "EmbeddedForm",
"props": {
"formName": {
"value": "form-with-slot"
}
},
"children": [
{
"key": "muiTypography1",
"type": "MuiTypography",
"props": {
"children": {
"value": "Form content"
},
"variant": {
"value": "h2"
}
},
"slot": "contentSlot"
}
]
}
]
}
}
Slot use cases
- Template systems: Create reusable form templates with customizable areas.
- Dynamic layouts: Allow different content in the same layout structure.
- Component injection: Inject specific components based on business logic.
Slot properties
The Slot component has no configurable properties. It serves as a pure placeholder that renders content from the parent form.
Slots only work within EmbeddedForm components. The parent form have to provide content for slots through the embedded form's childrens.
Best practices
- Keep
getFormfast: ThegetFormfunction runs during rendering. Avoid heavy computations or synchronous network requests inside it. If you need to load form definitions from an API, cache them or load them upfront. - Use meaningful
formNamevalues: ChooseformNamestrings that clearly identify the nested form's purpose (e.g.,'billing-address','emergency-contact'). This makes debugging and maintenance easier. - Choose the right data storage mode:
- Use
storeDataInParentForm: true(default) when you want all fields flattened into the parent form's data object. - Use
storeDataInParentForm: falsewhen the nested form's data should be kept together as a logical unit.
- Use
- Parameterize with
options: When a nested form needs to vary based on runtime conditions, pass those conditions via theoptionsobject rather than creating many separate form definitions. - Reuse form definitions: Embedded forms excel at reusing the same form JSON in multiple places. Define common sections (address, contact info) once and embed them wherever needed.
- Test both modes: Verify that your embedded forms work correctly with both
storeDataInParentForm: trueandfalse, because the data structure changes affect validation, submission, and data retrieval.
Troubleshooting
The embedded form doesn't appear
- Check
getForm: Ensure yourgetFormfunction returns valid JSON for the requestedformNameoroptions. Add console logging to verify it's being called with the expected arguments. - Verify props: The
EmbeddedFormcomponent requires eitherformNameoroptions(or both). If neither is provided, it renders nothing in viewer mode. - Inspect errors: Open the browser's developer console. FormEngine Core logs warnings when
getFormreturns invalid JSON or when the nested form fails to parse.
Nested form data doesn't appear in parent form data
- Check
storeDataInParentForm: WhenstoreDataInParentForm: true, nested fields appear as top‑level keys inform.data. Whenfalse, they're nested under the embedded form'sdataKey. - Verify
dataKey: If usingstoreDataInParentForm: false, ensure the embedded form component has akey/dataKeyproperty set, or the nested data won't have a place to live.
Validation doesn't cross the embedded boundary
- Embedded forms validate independently, but their validation errors bubble up to the parent form.
- If a nested field has a validation error, the parent form's
form.hasErrorswill betrue. - To see nested errors, inspect
form.errors, they will be keyed by the full path (either the field's ownkey/dataKeyor the nested object path).
Performance issues with many embedded forms
- Each
EmbeddedFormcreates a separate FormEngine instance. If you have dozens of embedded forms, consider whether you truly need separate instances or could use repeaters, conditional rendering, or a different composition pattern. - Use
disabledandreadOnlyprops to prevent unnecessary re‑renders when the nested form isn't interactive.
Summary
- Embedded forms let you nest complete FormEngine forms inside other forms using the
EmbeddedFormcomponent. - The nested form's JSON is supplied via the
getFormcallback, which receives aformNameandoptions. - Control data storage with
storeDataInParentForm:trueflattens fields into the parent's data;falsekeeps them as a nested object. - Embedded forms share the parent's context, events, and validation lifecycle while maintaining their own definition and rendering.
- Use embedded forms for reusable sections, conditional form parts, multi‑step workflows, and dynamic form composition.
For more information: