Lightning Data Service · Lesson 16 of 25
Create, Update and Delete Records in LWC
Record forms cover standard field-by-field editing. When your UI is different – a one-click "Mark as hot" button, a slider that sets a discount, a wizard that creates a record at the end – you call Lightning Data Service directly with createRecord, updateRecord and deleteRecord. They still go through LDS, so security is enforced and every component showing the record updates.
In this lesson
updateRecordfrom a buttoncreateRecordfrom custom inputs, with validationdeleteRecord- Keeping Apex-loaded data fresh
updateRecord
<!-- leadQuickActions.html -->
<template>
<lightning-button label="Mark as Hot" onclick={markHot} disabled={isSaving}></lightning-button>
<p>{status}</p>
</template>
// leadQuickActions.js
import { LightningElement, api } from 'lwc';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import ID from '@salesforce/schema/Lead.Id';
import RATING from '@salesforce/schema/Lead.Rating';
export default class LeadQuickActions extends LightningElement {
@api recordId;
isSaving = false;
status = '';
async markHot() {
this.isSaving = true;
const fields = { [ID.fieldApiName]: this.recordId, [RATING.fieldApiName]: 'Hot' };
try {
await updateRecord({ fields }); // every component showing this lead updates
this.status = 'Rating set to Hot';
this.dispatchEvent(new ShowToastEvent({ title: 'Lead updated', variant: 'success' }));
} catch (error) {
this.status = '';
this.dispatchEvent(new ShowToastEvent({ title: 'Update failed', message: error.body?.message, variant: 'error' }));
} finally {
this.isSaving = false;
}
}
}
[Mark as Hot]
› click “Mark as Hot”
[Mark as Hot]
Rating set to Hot
toast (success): Lead updated
updateRecord called 1× with {"fields":{"Id":"00QXX0000001Lw","Rating":"Hot"}}The record input is { fields: { Id, …changed fields } }. Using ID.fieldApiName from schema imports instead of the string 'Rating' keeps deploy-time checking. The try/catch/finally with a saving flag is the pattern for every save: disable the button, report success or the server's error, re-enable.
A failed save
Validation rules, required fields and permission errors come back as a rejected promise with error.body.message:
// leadQuickActions.js – same component
[Mark as Hot] › click “Mark as Hot” [Mark as Hot] toast (error): Update failed – Rating can only be changed by the lead owner.
createRecord from custom inputs
<!-- quickTask.html -->
<template>
<lightning-input label="Subject" required value={subject} onchange={handleSubject}></lightning-input>
<lightning-input type="date" label="Due date" value={dueDate} onchange={handleDue}></lightning-input>
<lightning-button label="Create task" variant="brand" onclick={create}></lightning-button>
<p>{message}</p>
</template>
// quickTask.js
import { LightningElement, api } from 'lwc';
import { createRecord } from 'lightning/uiRecordApi';
import TASK from '@salesforce/schema/Task';
import SUBJECT from '@salesforce/schema/Task.Subject';
import DUE from '@salesforce/schema/Task.ActivityDate';
import WHAT_ID from '@salesforce/schema/Task.WhatId';
export default class QuickTask extends LightningElement {
@api recordId;
subject = '';
dueDate = '';
message = '';
handleSubject(event) {
this.subject = event.detail.value;
}
handleDue(event) {
this.dueDate = event.detail.value;
}
async create() {
const inputs = [...this.template.querySelectorAll('lightning-input')];
const valid = inputs.every((input) => input.reportValidity()); // shows field errors
if (!valid) {
this.message = 'Fix the errors above';
return;
}
const recordInput = {
apiName: TASK.objectApiName,
fields: {
[SUBJECT.fieldApiName]: this.subject,
[DUE.fieldApiName]: this.dueDate || null,
[WHAT_ID.fieldApiName]: this.recordId,
},
};
const task = await createRecord(recordInput);
this.message = `Task created: ${task.id}`;
this.subject = '';
}
}
Subject [] Due date [] [Create task]
› click “Create task”
Subject [] ⚠ Complete this field. Due date [] [Create task]
Fix the errors above
› type “Send renewal quote” into Subject
Subject [Send renewal quote] Due date [] [Create task]
Fix the errors above
› clear Due date and type “2026-12-01”
Subject [Send renewal quote] Due date [2026-12-01] [Create task]
Fix the errors above
› click “Create task”
Subject [] Due date [2026-12-01] [Create task]
Task created: 00TXX0000009Tsk
createRecord called 1× with {"apiName":"Task","fields":{"Subject":"Send renewal quote","ActivityDate":"2026-12-01","WhatId":"001XX000003DGb2"}}The first click shows the built-in "Complete this field." error because reportValidity() checks required; only valid input reaches createRecord.
deleteRecord
import { deleteRecord } from 'lightning/uiRecordApi';
async handleDelete() {
await deleteRecord(this.recordId);
// the record is removed from the LDS cache; navigate away or tell the parent
}
Keeping other data fresh
LDS updates every component that loaded the record through LDS. Data loaded through Apex isn't tracked by LDS, so after a change, refresh it:
| Situation | Do |
|---|---|
A list loaded with @wire to Apex | await refreshApex(this.wiredResult) |
| You changed records through Apex, and LDS components show them | notifyRecordUpdateAvailable([{ recordId }]) so LDS reloads them |
| The standard record page should reload | covered automatically when you save through LDS |
Practice
Try it yourself
Add a "Close case" button that sets Status to "Closed" with updateRecord and shows "Case closed".
Show solution
<!-- closeCase.html --> <template> <lightning-button label="Close case" onclick={closeCase}></lightning-button> <p>{message}</p> </template>// closeCase.js import { LightningElement, api } from 'lwc'; import { updateRecord } from 'lightning/uiRecordApi'; export default class CloseCase extends LightningElement { @api recordId; message = ''; async closeCase() { await updateRecord({ fields: { Id: this.recordId, Status: 'Closed' } }); this.message = 'Case closed'; } }[Close case] › click “Close case” [Close case] Case closed updateRecord called 1× with {"fields":{"Id":"500XX0000002Cs","Status":"Closed"}}
Quick check
What does updateRecord need?
An object { fields: { Id, …fields to change } }.
Where's the message when a validation rule blocks a save?
In the rejected error's body.message.
Are Apex-loaded lists refreshed when LDS saves a record?
No – call refreshApex (or notifyRecordUpdateAvailable after Apex changes).
Summary
- createRecord, updateRecord and deleteRecord save through LDS without Apex.
- Validate with reportValidity, wrap saves in try/catch/finally, and show toasts.
- Refresh Apex data with refreshApex; tell LDS about Apex changes with notifyRecordUpdateAvailable.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.