How to Use DML When Using Lightning Web Component for Quick Action
Lightning Web Components (LWC) aren't just for record pages and app screens — they can also power Quick Actions, giving users a fast, focused way to create or update records without leaving the page they're on. But since LWC itself can't talk to the database directly, any DML (Data Manipulation Language) operation — insert, update, delete, or upsert — has to go through Apex.
This post walks through exactly how that wiring works: building an LWC-based Quick Action, calling an Apex method to perform DML, and handling the response so the action closes cleanly and refreshes the page.
Why Use LWC for a Quick Action?
Salesforce Quick Actions can be built a few different ways — standard record-creation actions, Flow-based actions, or a custom Lightning Web Component quick action. The LWC route is worth reaching for when you need:
- Custom validation logic before saving
- A tailored layout that doesn't match the standard object layout
- Multiple related records created or updated in a single click
- Conditional UI (showing/hiding fields based on other values) that a standard action can't easily do
Step 1: Build the LWC Component
Any LWC used as a Quick Action needs to implement lightning__RecordAction in its target configuration, and its JavaScript class needs access to the current record's ID (passed in automatically as recordId).
HTML Template
<template>
<lightning-card title="Log a Follow-Up Task">
<div class="slds-p-around_medium">
<lightning-input
label="Subject"
value={subject}
onchange={handleSubjectChange}>
</lightning-input>
<lightning-input
type="date"
label="Due Date"
value={dueDate}
onchange={handleDueDateChange}>
</lightning-input>
<lightning-button
variant="brand"
label="Save"
onclick={handleSave}
class="slds-m-top_medium">
</lightning-button>
</div>
</lightning-card>
</template>
JavaScript Controller
import { LightningElement, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { CloseActionScreenEvent } from 'lightning/actions';
import { updateRecord } from 'lightning/uiRecordApi';
import createFollowUpTask from '@salesforce/apex/QuickActionController.createFollowUpTask';
export default class LogFollowUpTask extends LightningElement {
@api recordId; // Automatically populated with the current record's Id
subject = '';
dueDate = '';
handleSubjectChange(event) {
this.subject = event.target.value;
}
handleDueDateChange(event) {
this.dueDate = event.target.value;
}
handleSave() {
createFollowUpTask({
whatId: this.recordId,
subject: this.subject,
dueDate: this.dueDate
})
.then(() => {
this.showToast('Success', 'Follow-up task created.', 'success');
this.dispatchEvent(new CloseActionScreenEvent());
})
.catch((error) => {
this.showToast('Error creating task', error.body.message, 'error');
});
}
showToast(title, message, variant) {
this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
}
}
A few details worth calling out:
@api recordIdis what makes this component aware of which record launched the action — Salesforce populates it automatically when the component is used as a Quick Action.CloseActionScreenEventis what closes the Quick Action panel after a successful save. Without dispatching it, the modal stays open even after the DML succeeds.ShowToastEventgives the user immediate feedback, whether the save succeeded or failed.
Step 2: Write the Apex Controller That Performs the DML
The actual insert/update logic lives in Apex, exposed to the LWC through an @AuraEnabled method:
public with sharing class QuickActionController {
@AuraEnabled
public static void createFollowUpTask(Id whatId, String subject, Date dueDate) {
try {
Task t = new Task();
t.WhatId = whatId;
t.Subject = subject;
t.ActivityDate = dueDate;
t.Status = 'Not Started';
insert t;
} catch (DmlException e) {
throw new AuraHandledException('Unable to save task: ' + e.getMessage());
}
}
}
A few best practices baked into this pattern:
with sharingensures the DML respects the running user's record-level access, rather than silently bypassing it.- Wrapping the DML in a try/catch and re-throwing as
AuraHandledExceptionis essential — a plainDmlExceptionwon't surface a readable message to the LWC's.catch()block.AuraHandledExceptionis specifically designed to safely pass error details back to client-side JavaScript. - Keep the method focused on a single responsibility. If you need to create multiple related records, build the full list of
sObjectsin Apex and perform one bulk DML call rather than several separate inserts.
Step 3: Handling Bulk or Related DML Safely
If your Quick Action needs to create or update more than one record — say, a Task and a related Note — always batch the DML into a single call per object type rather than looping inserts one at a time:
@AuraEnabled
public static void createTaskWithNote(Id whatId, String subject, Date dueDate, String noteBody) {
try {
Task t = new Task(
WhatId = whatId,
Subject = subject,
ActivityDate = dueDate,
Status = 'Not Started'
);
insert t;
ContentNote note = new ContentNote();
note.Title = subject + ' - Notes';
note.Content = Blob.valueOf(noteBody);
insert note;
ContentDocumentLink link = new ContentDocumentLink();
link.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :note.Id].ContentDocumentId;
link.LinkedEntityId = t.Id;
link.ShareType = 'V';
insert link;
} catch (Exception e) {
throw new AuraHandledException('Unable to save task and note: ' + e.getMessage());
}
}
This keeps all the DML server-side in one transaction, so either everything saves together or nothing does — avoiding a half-completed state if one insert fails partway through.
Step 4: Refreshing the UI After a Quick Action
Because the DML happens in Apex and not through Lightning Data Service, the record page won't automatically reflect the change once the action closes. Two common approaches:
Option A: Refresh the view using RefreshEvent
import { RefreshEvent } from 'lightning/refresh';
// ...
this.dispatchEvent(new RefreshEvent());
Option B: Use getRecordNotifyChange from Lightning Data Service
import { getRecordNotifyChange } from 'lightning/uiRecordApi';
// ...
getRecordNotifyChange([{ recordId: this.recordId }]);
Either approach tells the platform's caching layer that the underlying record has changed, so any related lists, related record fields, or other components on the page pick up the fresh data without a full page reload.
Step 5: Configuring the Quick Action in Setup
Once the component is deployed, expose it as a Quick Action from Setup:
- Go to Object Manager → select your object → Buttons, Links, and Actions.
- Click New Action.
- Set Action Type to Lightning Web Component.
- Choose your LWC from the Lightning Web Component picklist (only components with
lightning__RecordActionin theirjs-meta.xmlwill appear here). - Give the action a Label and Name, then save.
- Add the new Quick Action to the object's page layout so it appears as a button.
Here's the required js-meta.xml configuration for the component to be selectable as a Quick Action:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Log Follow-Up Task</masterLabel>
<targets>
<target>lightning__RecordAction</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordAction">
<actionType>ScreenAction</actionType>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Common Pitfalls to Avoid
- Forgetting
CloseActionScreenEvent. Without it, users are left staring at a modal that appears to do nothing after clicking Save, even though the record saved successfully. - Not wrapping DML in a try/catch. Uncaught Apex exceptions return a generic, unhelpful error to the LWC — always catch and re-throw as
AuraHandledExceptionwith a clear message. - Looping DML statements inside Apex. Even in a Quick Action context, build lists and insert/update them once outside of loops to respect governor limits and avoid hitting DML row limits on records with many related children.
- Skipping the refresh step. Users expect the page to reflect their change immediately — dispatching
RefreshEventor callinggetRecordNotifyChangecloses that gap. - Missing
with sharing. Leaving the controller class aswithout sharing(the Apex default) means the DML runs with elevated access regardless of the user's actual permissions — a subtle but serious security gap.
Key Takeaways
- LWC Quick Actions can't perform DML directly — all inserts, updates, or deletes must go through an
@AuraEnabledApex method. - Use
with sharingon your Apex controller and wrap DML in try/catch, re-throwing asAuraHandledExceptionso errors surface cleanly in the LWC. - Dispatch
CloseActionScreenEventafter a successful save so the action panel closes automatically. - Use
RefreshEventorgetRecordNotifyChangeto make sure the record page reflects the change immediately. - For multi-object DML, batch everything into one Apex transaction rather than multiple round trips, so the whole operation succeeds or fails together.
With this pattern, you get the flexibility of a fully custom UI for your Quick Action, backed by clean, governor-limit-friendly Apex DML — giving users a fast, reliable way to create or update records without ever leaving the record page.