Implementing CRUD in Lightning Web Components with Lightning Data Service
Learn how to perform create, read, update, and delete operations in a Lightning Web Component using Salesforce Lightning Data Service (LDS). This guide covers prerequisites, wiring, DML functions, error handling, and best‑practice fallbacks.
02 Dec 2025, 03:47 UTC

Problem Statement
Salesforce developers often need to read, edit, or delete a record from a Lightning Web Component (LWC). Writing Apex controllers for simple CRUD can add unnecessary boilerplate, increase deployment size, and risk security mis‑configurations. Lightning Data Service (LDS) offers a declarative, secure, and efficient way to perform these operations.
Desired Outcome
Build an LWC that:
- Displays a record’s fields.
- Allows inline editing and saves changes with
updateRecord(). - Provides a delete button that removes the record via
deleteRecord(). - Handles permission, validation, and optimistic‑locking errors gracefully.
Prerequisites
- Salesforce org with Lightning Experience enabled.
- Developer or sandbox org with API access.
- Basic knowledge of LWC, ES6 syntax, and Salesforce data model.
- Record type or object that the running user can read/write.
- Component placed on a record page or an app page with
recordIdcontext.
Step 1 – Create the LWC Skeleton
sfdx force:lightning:component:create -n CrudDemo -d force-app/main/default/lwc
Open crudDemo.js and import the necessary LDS helpers:
import { LightningElement, api, wire } from 'lwc';
import { getRecord, updateRecord, deleteRecord } from 'lightning/uiRecordApi';
Step 2 – Wire the Record
Use the @wire adapter getRecord to fetch the record. The component must receive recordId from the page context.
@wire(getRecord, { recordId: '$recordId', fields: [FIELD1, FIELD2] })
record;
Replace FIELD1 and FIELD2 with API names, e.g., 'Account.Name'. The wire service automatically applies field‑level security.
Step 3 – Display and Edit Fields
Render the fields and bind two‑way input elements. Use this.record.data.fields to access field values.
<template if:true={record}>
<lightning-input label="Name" value={name} onchange={handleChange} data-field="Name"></lightning-input>
</template>
In the JS file, map the wire data to component properties:
get name() {
return this.record?.data?.fields?.Name?.value ?? '';
}
Handle input changes by storing values in a local object.
Step 4 – Update the Record
When the user clicks “Save”, call updateRecord() with the recordId and the fields object.
handleSave() {
const fields = { Id: this.recordId, Name: this.nameInput };
updateRecord({ fields })
.then(() => {
this.dispatchEvent(new ShowToastEvent({ title: 'Success', message: 'Record updated', variant: 'success' }));
})
.catch(error => this.handleError(error));
}
Note: updateRecord returns a promise; errors are caught in the catch block.
Step 5 – Delete the Record
Provide a “Delete” button that calls deleteRecord():
handleDelete() {
deleteRecord(this.recordId)
.then(() => {
this.dispatchEvent(new ShowToastEvent({ title: 'Deleted', message: 'Record removed', variant: 'success' }));
})
.catch(error => this.handleError(error));
}
Step 6 – Error Handling
All LDS operations expose an error object or promise rejection. Common error scenarios:
- Permission denied – attempt to write a read‑only field or a field the user cannot edit.
- Optimistic locking – record was modified between fetch and update.
- Record not found – invalid or deleted
recordId.
Implement a generic handler:
handleError(error) {
const message = error.body?.message ?? error.body?.errors?.[0]?.message ?? 'Unknown error';
this.dispatchEvent(new ShowToastEvent({ title: 'Error', message, variant: 'error' }));
}
Step 7 – Validation Checks
- Ensure
recordIdis non‑null before wiring. - Check field‑level security by attempting a dummy write and catching permission errors.
- Validate user input before calling
updateRecord()(e.g., required fields, length limits).
Step 8 – Fallback Options
When LDS cannot meet a requirement (e.g., bulk updates), fall back to Apex:
- Create a minimal Apex controller with
@AuraEnabledmethods that callDatabase.updateorDatabase.deletewithallOrNone = false. - Expose these methods to the LWC via
import { LightningElement, wire } from 'lwc'; import { updateBatch } from '@salesforce/apex/CrudController';. - Use
Promise.allfor batch operations, handling individual failures.
Step 9 – Verification
- Deploy the component to a sandbox or dev org.
- Add it to an Account record page.
- Open the record, edit a field, and click Save. Verify the change persists and a success toast appears.
- Open the same record in two tabs. Edit in Tab 1 and save. In Tab 2, attempt to save a different change. Confirm an optimistic‑locking error toast displays.
- Remove the record’s edit permission for a test user. Try to edit a field and verify the permission error toast.
- Delete the record via the component and confirm the record is removed from the database.
Limitations & Practical Checks
- LDS does not support bulk DML. For large‑scale updates, use Apex or the Bulk API.
- If the page context does not supply
recordId, the wire returnsnull– add a guard clause to display an informative message. - Optimistic‑locking errors surface only when another user updates the record after it was fetched; handle by retrying or notifying the user to refresh.
- Always test with the target user’s permission set to avoid unexpected failures in production.
Conclusion
Lightning Data Service simplifies CRUD in LWCs while respecting Salesforce security. By wiring record data, using updateRecord and deleteRecord, and handling errors thoughtfully, developers can deliver robust, declarative components without Apex boilerplate. Use the outlined fallback strategy when bulk or complex logic is required.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.