LearnFrenzy
Sign in Take a free test

Lightning Data Service · Lesson 16 of 25

Create, Update and Delete Records in LWC

5 min read Updated Nov 8, 2026 Saurabh Samir

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

  • updateRecord from a button
  • createRecord from custom inputs, with validation
  • deleteRecord
  • 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;
        }
    }
}

Result and the call made to LDS

[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

Result when a validation rule blocks the save

[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 = '';
    }
}

Result and the call made to LDS

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:

SituationDo
A list loaded with @wire to Apexawait refreshApex(this.wiredResult)
You changed records through Apex, and LDS components show themnotifyRecordUpdateAvailable([{ recordId }]) so LDS reloads them
The standard record page should reloadcovered automatically when you save through LDS

Practice

Try it yourself

  1. 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';
        }
    }

    Result

    [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.

Next lesson

Search LearnFrenzy

Esc

Quick links