A fully reusable Lightning Web Component that renders any form at runtime from a JSON/Custom Metadata configuration — no code changes needed to add, remove, or reconfigure fields.
Use these 3 properties in Lightning App Builder to get started fast:
- Form Config Metadata Name:
Contact_Intake_Form - Form Config JSON: leave blank
- Target Object API Name:
Contact
Then click Save and Activate.
If you want inline JSON instead:
- Fill Form Config JSON with your JSON payload.
- Leave Form Config Metadata Name blank.
- Keep Target Object API Name as
Contact(or your target object).
dynamicFormBuilder (parent — orchestrator)
│ ├─ Loads config from Custom Metadata OR inline JSON
│ ├─ Manages form state, validation, submission
│ └─ Fires public events: fieldchange, formsubmit, formreset, formcancel
│
└── dynamicFormField (child — field renderer)
├─ Renders the correct lightning-* component per field type
└─ Reports validity up to parent via validate event
Apex: DynamicFormBuilderController
├─ getFormConfig() — reads FormConfig__mdt + FormFieldConfig__mdt
└─ submitFormData() — dynamic SObject insert via Schema API
Custom Metadata:
├─ FormConfig__mdt — one record per form
└─ FormFieldConfig__mdt — child records per field (or use Config_JSON__c blob)
| Type | Rendered As |
|---|---|
text |
lightning-input type="text" |
email |
lightning-input type="email" |
phone |
lightning-input type="tel" |
number |
lightning-input type="number" |
currency |
lightning-input formatter="currency" |
percent |
lightning-input formatter="percent-fixed" |
date |
lightning-input type="date" |
datetime |
lightning-input type="datetime" |
checkbox |
lightning-input type="checkbox" |
toggle |
lightning-input type="toggle" |
textarea |
lightning-textarea |
picklist |
lightning-combobox |
multipicklist |
lightning-dual-listbox |
radio |
lightning-radio-group |
checkboxgroup |
lightning-checkbox-group |
url |
lightning-input type="url" |
password |
lightning-input type="password" |
lookup |
lightning-record-picker |
displaytext |
Static read-only text block |
separator |
Section header divider |
{
"title": "My Form",
"description": "Optional subtitle shown below the card header.",
"columns": 2,
"fields": [
{
"fieldName": "LastName",
"label": "Last Name",
"type": "text",
"required": true,
"placeholder": "Enter last name",
"helpText": "Shown in the field tooltip.",
"defaultValue": "",
"maxLength": 80,
"minLength": 2,
"pattern": "[A-Za-z]+",
"patternMessage": "Letters only.",
"requiredMessage": "Last name is required.",
"disabled": false,
"readOnly": false,
"order": 1
},
{
"fieldName": "Department",
"label": "Department",
"type": "picklist",
"required": false,
"options": [
{ "label": "Engineering", "value": "Engineering" },
{ "label": "Sales", "value": "Sales" }
],
"order": 2
},
{
"fieldName": "AccountId",
"label": "Account",
"type": "lookup",
"lookupObject": "Account",
"required": true,
"order": 3
}
]
}Use these values when you drag dynamicFormBuilder onto a Home page or App page:
| App Builder Property | Recommended Value | Notes |
|---|---|---|
| Form Config Metadata Name | Contact_Intake_Form |
Use this for metadata-driven config from FormConfig__mdt. |
| Form Config JSON | (leave blank) | Only fill this when you want inline JSON. |
| Target Object API Name | Contact |
Required if you want submit to create records. |
| Submit Button Label | Submit |
Optional UI label. |
| Cancel Button Label | Cancel |
Optional UI label. |
| Success Message | Form submitted successfully! |
Toast and success state message. |
| Show Reset Button | true or false |
Optional behavior toggle. |
| Show Cancel Button | true or false |
Optional behavior toggle. |
Rules:
- Provide either
formConfigNameorformConfigJson. - If both are provided, inline JSON (
formConfigJson) takes precedence. - If
targetObjectApiNameis blank, submit will not insert a Salesforce record.
- Deploy the metadata types.
- Create a
FormConfig__mdtrecord withDeveloperName = Contact_Intake_Formand paste JSON intoConfig_JSON__c. - Drop the component on a page:
<c-dynamic-form-builder
form-config-name="Contact_Intake_Form"
target-object-api-name="Contact"
submit-label="Create Contact"
show-reset-button>
</c-dynamic-form-builder>App Builder configuration for inline JSON:
- Set Form Config JSON to your full JSON payload.
- Leave Form Config Metadata Name blank.
- Set Target Object API Name (for example
Contact) if you want record creation.
Example inline JSON payload:
{
"title": "Contact Intake Form",
"description": "Please fill in all required fields.",
"columns": 2,
"fields": [
{
"fieldName": "FirstName",
"label": "First Name",
"type": "text",
"required": false,
"placeholder": "Enter first name",
"maxLength": 80,
"order": 1
},
{
"fieldName": "LastName",
"label": "Last Name",
"type": "text",
"required": true,
"placeholder": "Enter last name",
"maxLength": 80,
"order": 2
},
{
"fieldName": "Email",
"label": "Email Address",
"type": "email",
"required": true,
"placeholder": "email@example.com",
"order": 3
}
]
}<c-dynamic-form-builder
form-config-json={myJsonString}
onformsubmit={handleFormSubmit}>
</c-dynamic-form-builder>Use this when you want form structure assembled from FormFieldConfig__mdt child records instead of the JSON blob on FormConfig__mdt.
- Set Form Config Metadata Name to
Contact_Metadata_Form. - Leave Form Config JSON blank.
- Set Target Object API Name to
Contact.
Add the component as a Flow Screen element. It appears in the component palette because lightning__FlowScreen is declared in the meta XML.
| Property | Type | Default | Description |
|---|---|---|---|
formConfigName |
String | — | DeveloperName of FormConfig__mdt |
formConfigJson |
String | — | Inline JSON (overrides metadata) |
targetObjectApiName |
String | — | Salesforce object for record creation |
recordData |
Object | {} |
Pre-populate with existing values |
submitLabel |
String | "Submit" |
Submit button text |
cancelLabel |
String | "Cancel" |
Cancel button text |
successMessage |
String | "Form submitted successfully!" |
Toast / success state message |
showResetButton |
Boolean | false |
Show Reset button |
showCancelButton |
Boolean | false |
Show Cancel button |
| Method | Description |
|---|---|
submit() |
Programmatically submit the form |
reset() |
Reset all fields to defaults |
getFormValues() |
Returns {fieldName: value} map |
setFieldValue(name, val) |
Set a single field value |
| Event | event.detail |
|---|---|
fieldchange |
{ fieldName, value, allValues } |
formsubmit |
{ formData, success, error? } |
formreset |
— |
formcancel |
— |
# Deploy everything
sf project deploy start --source-dir force-app
# Run Apex tests
sf apex run test --class-names DynamicFormBuilderControllerTest --result-format humanwith sharingon Apex — respects record-level security.@AuraEnabled(cacheable=true)on read-only method — enables client-side caching.- Schema API for dynamic DML — avoids hardcoded field maps, respects FLS (
isCreateable()). lwc:if/lwc:elseif— uses modern conditional rendering (no deprecatedif:true).@api+@trackseparation — public API vs internal reactive state.- Child
reportValidity()— delegates validation to native lightning components, no custom regex duplication. composed: trueevents — allows events to cross shadow DOM boundaries.isExposed: falseon child — prevents accidental direct page placement.- Target configs in meta XML — correct App Builder property panel per surface.