Skip to main content

Salesforce Lightning Data Service (LDS) in LWC: The Complete Code Guide

In plain words: Lightning Data Service (LDS) is the standard way to interact with Salesforce data in Lightning Web Components (LWC). Instead of writing custom Apex controllers to fetch or update records, LDS uses standard UI API adapters to handle database operations directly from JavaScript. It automatically manages caching, respects field-level security, and keeps data in sync across your entire page.

If you are building custom Lightning Web Components, you should always try to use Lightning Data Service before writing a single line of Apex. It makes your code cleaner, faster, and much easier to maintain.

Let's dive into the core, modern ways to implement LDS in your components using the official lightning/uiRecordApi module.

Core Takeaway: Always import schema references (e.g., @salesforce/schema/Account.Name) instead of using hardcoded strings like 'Account.Name'. This ensures Salesforce tracks your field dependencies and prevents admins from accidentally deleting a field your code relies on!

1. Retrieve a Single Record (getRecord)

The most common use case for LDS is fetching a record when a component loads on a record page.

import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';

export default class AccountViewer extends LightningElement {
    @api recordId; // Automatically populated on a Record Page

    @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME_FIELD] })
    account;

    get accountName() {
        return this.account.data ? this.account.data.fields.Name.value : '';
    }
}

2. Retrieve Related Record Fields Safely (getFieldValue)

Extracting nested data (like an Account Owner's name) from a raw JSON response can cause nasty "undefined" errors if you aren't careful. Salesforce provides getFieldValue to safely pull this data.

import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import OWNER_NAME_FIELD from '@salesforce/schema/Account.Owner.Name';

export default class AccountOwnerViewer extends LightningElement {
    @api recordId;

    @wire(getRecord, { recordId: '$recordId', fields: [OWNER_NAME_FIELD] })
    account;

    get ownerName() {
        // Automatically handles null checks!
        return getFieldValue(this.account.data, OWNER_NAME_FIELD);
    }
}

3. Create a New Record (createRecord)

You can create new records without Apex by passing an object containing the API name of the object and the desired field values.

import { LightningElement } from 'lwc';
import { createRecord } from 'lightning/uiRecordApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import NAME_FIELD from '@salesforce/schema/Account.Name';

export default class CreateAccount extends LightningElement {
    accountId;

    async handleCreate() {
        const fields = {};
        fields[NAME_FIELD.fieldApiName] = 'New Acme Corp';

        const recordInput = { apiName: ACCOUNT_OBJECT.objectApiName, fields };

        try {
            const account = await createRecord(recordInput);
            this.accountId = account.id;
            console.log('Success! Record ID: ', this.accountId);
        } catch (error) {
            console.error('Error creating record', error);
        }
    }
}

4. Update an Existing Record (updateRecord)

To modify an existing record, pass a fields object that includes the record's Id.

import { LightningElement, api } from 'lwc';
import { updateRecord } from 'lightning/uiRecordApi';
import ID_FIELD from '@salesforce/schema/Account.Id';
import NAME_FIELD from '@salesforce/schema/Account.Name';

export default class UpdateAccount extends LightningElement {
    @api recordId;

    handleUpdate() {
        const fields = {};
        fields[ID_FIELD.fieldApiName] = this.recordId;
        fields[NAME_FIELD.fieldApiName] = 'Updated Acme Corp';

        const recordInput = { fields };

        updateRecord(recordInput)
            .then(() => {
                console.log('Record updated successfully!');
            })
            .catch(error => {
                console.error('Update failed', error);
            });
    }
}

5. Delete a Record (deleteRecord)

Deleting a record requires nothing more than the record ID.

import { LightningElement, api } from 'lwc';
import { deleteRecord } from 'lightning/uiRecordApi';

export default class DeleteAccount extends LightningElement {
    @api recordId;

    handleDelete() {
        deleteRecord(this.recordId)
            .then(() => {
                console.log('Record deleted successfully');
            })
            .catch(error => {
                console.error('Error deleting record', error);
            });
    }
}

6. Fetch Object Metadata (getObjectInfo)

Sometimes you need to know about an object's structure before querying data—such as fetching all the fields available on the Account object or grabbing a specific Record Type ID.

import { LightningElement, wire } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';

export default class AccountMetadata extends LightningElement {
    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    objectInfo;

    get isAccountCreatable() {
        // Check if the current user has permission to create an Account
        return this.objectInfo.data ? this.objectInfo.data.createable : false;
    }
}

7. Refresh the LDS Cache (refreshRecord)

If a record is updated via an external process or an imperative Apex call, LDS might still show the old cached data. Use refreshRecord to force the cache to pull the latest version from the server.

Modern Update: Salesforce recently introduced refreshRecord as the standard replacement for the old getRecordNotifyChange function.
import { LightningElement, api } from 'lwc';
import { refreshRecord } from 'lightning/uiRecordApi';

export default class RefreshExample extends LightningElement {
    @api recordId;

    async handleRefresh() {
        try {
            await refreshRecord(this.recordId);
            console.log('Cache cleared and data refreshed!');
        } catch (error) {
            console.error('Refresh failed', error);
        }
    }
}
Developer Trap: The Deprecated List View API
In older tutorials, you might see examples using getListUi from lightning/uiListApi to fetch lists of records. Do not use this! Salesforce has officially deprecated getListUi. If you need to fetch multiple records in LWC today, you should use the new GraphQL Wire Adapter or write a standard Apex controller using @AuraEnabled(cacheable=true).
360 Card: Why Use LDS Over Apex?
1. Shared Cache: If Component A updates an Account, Component B automatically shows the new name instantly without a page refresh.
2. Security: LDS automatically respects Field-Level Security (FLS) and sharing rules. You don't have to write manual checks.
3. Performance: It reduces server trips by relying heavily on client-side browser caching.

Conclusion

By leveraging Lightning Data Service (LDS) using the UI API, you can dramatically simplify your LWC codebase. Whether you are wiring data directly to your HTML templates or imperatively pushing updates to the database, LDS provides a secure, reactive, and highly optimized experience for your Salesforce users.