Tip of the Day : Set up email alerts for high-priority cases to improve response times.

How to Build Dynamic Salesforce LWC Forms Using JSON and Apex?

January 14, 2026
288 Views
How to Build Dynamic Salesforce LWC Forms Using JSON and Apex?
Summarize this blog post with:

Introduction

Salesforce LWC UI plays a key role in building modern and flexible user experiences. One of the most effective ways to achieve this is through dynamic forms in Salesforce LWC that adapt at runtime instead of relying on hardcoded layouts. This article explores a practical approach to building dynamic Lightning Web Component (LWC) forms using external configuration.

By controlling form structure, sections, and fields through JSON configuration, administrators can modify layouts, reorder sections, or add new fields without code changes or redeployments. This configuration-driven approach reduces development effort, improves maintainability, and aligns with Salesforce best practices for scalable UI design.

The Problem: Static Forms vs. Dynamic Requirements

Traditional Salesforce forms usually have hardcoded field arrangements, which means they are static and complicated to manage. Each modification needs developer attention, deployment procedures, and the risk of system unavailability. Our solution addresses this by offering flexibility and best practices for handling dynamic UI in Salesforce:
  1. Separating configuration from code
  2. Enabling runtime form customisation
  3. Reducing deployment dependencies
  4. Improving maintainability
  5. The Architecture: Three-Tier Dynamic UI System

 Dynamic Salesforce LWC Form Architecture Explained

  1. 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"} 
                ] 
        	} 
     
    	] 
    } 
    
    						
  2. 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' 
                    })); 
                }); 
    	} 
    } 
    
    							
  3. 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

  1. Dynamic Layout Generation
    • Forms render based on JSON configuration
    • Easy addition/removal of fields and sections
    • Responsive 2-column grid layout
  2. Separation of Concerns
    • Configuration: JSON in Static Resources
    • Presentation: LWC components
    • Business Logic: Apex controllers
  3. Administrator Empowerment

    Non-technical users can:

    • Reorder fields without code changes
    • Add new sections on-the-fly
    • Modify field groupings
  4. Performance Optimization
    • Cached configuration retrieval
    • Efficient field mapping
    • Batch field processing

Implementation Steps

  • Step 1: Create the Static Resource
    1. Navigate to Setup → Static Resources
    2. Click “New”
    3. Name: CustomerFormConfig
    4. File: Upload your JSON configuration
    5. 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:

  1. Maintainable: Changes without deployments
  2. Scalable: Easy to extend with new functionality
  3. User-Centric: Administrators control the experience
  4. 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.

Salesforce LWC UI

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 1

No votes so far! Be the first to rate this post.

Written by

Khumed Khatib

8x Salesforce certified || Senior Engineering Lead Salesforce at Persistent Systems || 2x Ranger || Blogger || LWC || Aura||integration

Get the latest tips, news, updates, advice, inspiration, and more….

Contributor of the month
contributor
Khumed Khatib

8x Salesforce certified || Blogger || LWC || Aura||

...
Categories
...
Boost Your Brand's Visibility

Want to promote your products/services in front of more customers?

...

Leave a Reply

Your email address will not be published. Required fields are marked *