The Power of Dynamic Forms in Salesforce Today we are going to a more complex Salesforce solution that shows us how to create dynamic LWC forms based on external configuration. This provides a massive amount of flexibility, enabling admins to make changes to the form layouts without ever having to be concerned with code. Let’s explore this “Customer Information Form” that dynamically renders sections and fields based on a JSON configuration.
Introduction
The Problem: Static Forms vs. Dynamic Requirements
-
Separating configuration from code
-
Enabling runtime form customisation
-
Reducing deployment dependencies
-
Improving maintainability
-
The Architecture: Three-Tier Dynamic UI System
Dynamic Salesforce LWC Form Architecture Explained
-
Configuration Layer JSON (Static Resource)
{ "title": "Contact Information Form", "sections": [ { "heading": "Personal Details", "fields": [ {"name": "firstName", "label": "First Name", "type": "text", "required": true}, {"name": "lastName", "label": "Last Name", "type": "text", "required": true}, {"name": "email", "label": "Email", "type": "email"}, {"name": "mobile", "label": "Mobile Number", "type": "phone"} ] }, { "heading": "Permanent Address", "fields": [ {"name": "Address Line 1", "label": "Address Line 1", "type": "textarea"}, {"name": "Address Line 2", "label": "Address Line 2", "type": "textarea"}, {"name": "city", "label": "City", "type": "text"}, {"name": "state", "label": "state", "type": "text"} ] }, { "heading": "Identification Details", "fields": [ {"name": "idtype", "label": "Id Type", "type": "Picklist"}, {"name": "idNumber", "label": "ID Number", "type": "tex"} ] }, { "heading": "Obligation Details", "fields": [ {"name": "idtype", "label": "Id Type", "type": "Picklist"}, {"name": "idNumber", "label": "ID Number", "type": "tex"} ] } ] } -
Presentation Layer (LWC Component)
HTML Template: Dynamic Form Rendering
<template> <lightning-card title="Customer Information" icon-name="standard:contact"> <div class="slds-p-around_medium"> <lightning-record-edit-form object-api-name="Customer_Info__c" class="customerForm" onsubmit={handleFormSubmit}> <template if:true={sections}> <template for:each={sections} for:item="section"> <div key={section.title} class="slds-section slds-is-open"> <h3 class="slds-section__title slds-theme_shade"> <span class="slds-truncate">{section.title}</span> </h3> <div class="slds-section__content"> <div class="slds-grid slds-wrap"> <template for:each={section.fields} for:item="fieldName"> <div key={fieldName} class="slds-col slds-size_1-of-2 slds-p-horizontal_small"> <lightning-input-field field-name={fieldName}> </lightning-input-field> </div> </template> </div> </div> </div> </template> </template> <div class="slds-m-top_medium"> <lightning-button type="submit" variant="brand" label="Save Info"> </lightning-button> </div> </lightning-record-edit-form> </div> </lightning-card> </template>JavaScript Controller: Data Handling
import { LightningElement, wire } from 'lwc'; import getFormConfig from '@salesforce/apex/CustomerInfoController.getFormConfig'; import saveCustomer from '@salesforce/apex/CustomerInfoController.saveCustomer'; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; import { RefreshEvent } from 'lightning/refresh'; export default class CustomerForm extends LightningElement { sections; // Wire method to fetch form configuration @wire(getFormConfig) wiredConfig({ error, data }) { if (data) { const config = JSON.parse(data); this.sections = config.sections; } else if (error) { console.error('Configuration loading error:', error); } } // Form submission handler handleFormSubmit(event) { event.preventDefault(); // Prevent default form submission const fields = event.detail.fields; saveCustomer({ fieldData: fields }) .then(() => { // Success notification this.dispatchEvent(new ShowToastEvent({ title: 'Success', message: 'Customer Record Created Successfully', variant: 'success' })); // Refresh the view this.dispatchEvent(new RefreshEvent()); }) .catch(error => { console.error('Save error:', error); this.dispatchEvent(new ShowToastEvent({ title: 'Error', message: 'Failed to save record: ' + error.body.message, variant: 'error' })); }); } } -
Backend Layer (Apex Controller)
public with sharing class CustomerInfoController { @AuraEnabled(cacheable=true) public static String getFormConfig() { try { // Retrieve configuration from Static Resource StaticResource sr = [SELECT Body FROM StaticResource WHERE Name = 'CustomerFormConfig' LIMIT 1]; return sr.Body.toString(); // Convert Blob to String } catch (Exception e) { throw new AuraHandledException( 'Error loading form configuration: ' + e.getMessage() ); } } @AuraEnabled public static void saveCustomer(Map<String, Object> fieldData) { try { // Dynamically map fields to the custom object Customer_Info__c newCust = new Customer_Info__c(); for(String fieldName : fieldData.keySet()) { newCust.put(fieldName, fieldData.get(fieldName)); } insert newCust; } catch (Exception e) { throw new AuraHandledException( 'Error saving record: ' + e.getMessage() ); } } }
Key Features and Benefits
-
Dynamic Layout Generation
- Forms render based on JSON configuration
- Easy addition/removal of fields and sections
- Responsive 2-column grid layout
-
Separation of Concerns
- Configuration: JSON in Static Resources
- Presentation: LWC components
- Business Logic: Apex controllers
-
Administrator Empowerment
Non-technical users can:
- Reorder fields without code changes
- Add new sections on-the-fly
- Modify field groupings
-
Performance Optimization
- Cached configuration retrieval
- Efficient field mapping
- Batch field processing
Implementation Steps
-
Step 1: Create the Static Resource
- Navigate to Setup → Static Resources
- Click “New”
- Name: CustomerFormConfig
- File: Upload your JSON configuration
- Cache Control: Public
-
Step 2: Deploy the Apex Class
bash
sfdx force:source:deploy -p force-app/main/default/classes/CustomerInfoController.cls
-
Step 3: Create LWC Components
bash
sfdx force:lightning:component:create -n customerForm -d force-app/main/default/lwc
-
Step 4: Configure Object Permissions
Ensure the running user has:
- Read access to StaticResource object
- Create/Edit access to Customer_Info__c object
- Field-level security for all referenced fields
Best Practices Implemented
-
Error Handling
- Comprehensive try-catch blocks
- User-friendly error messages
- Console logging for debugging
-
User Experience
- Toast notifications for feedback
- Form validation (inherited from base components)
- Section-based organization
- Responsive design
-
Security
- With sharing context
- Field-level security compliance
- Protected against SOQL injection
Conclusion: The Future of Salesforce Forms
This dynamic form solution represents a paradigm shift in how we build Salesforce interfaces. By separating configuration from implementation, we create systems that are:
- Maintainable: Changes without deployments
- Scalable: Easy to extend with new functionality
- User-Centric: Administrators control the experience
- Future-Proof: Ready for evolving business needs
The pattern shown here can be applied to almost any data input scenario in Salesforce, be it a complex onboarding process or a multi-page wizard, while still providing clean separation of your code and maximum flexibility.






