Use Apex to Work with Data

Learning Objectives

After completing this unit, you’ll be able to:

  • Recognize when you need to use Apex to work with Salesforce data.
  • Call Apex in two different ways.
  • Work with lists of records using Apex and lightning-datatable.

Apex in Lightning Web Components

We’ve covered the benefits of Lightning Data Service and how to use it, but sometimes, neither the lightning-record-*-form components nor the LDS wire adapters and functions suit a particular use case. For example, when you want to customize a single-record data transaction, or perform multi-record operations on a single transaction, Apex is the best choice.

Use Apex Methods with Lightning Web Components

An Apex method used in a Lightning web component must be static, public or global, and annotated with @AuraEnabled immediately before the method definition. The @AuraEnabled annotation makes the Apex method available to Lightning components (both Lightning web components and Aura components).

Allowing the framework to cache data eliminates repeated server calls making future read operations run faster. We mark a method as cacheable by setting cacheable = true in the @AuraEnabled annotation. When an @AuraEnabled method is cacheable, Data Manipulation Language (DML) operations are not allowed. In line 2 of this example, we make the getRelatedContacts method cacheable.


public with sharing class AccountController {
    public static List<Contact> getRelatedContacts(Id accountId) {
        return [
            SELECT Name, Title, Email, Phone
            FROM Contact
            WHERE AccountId = :accountId

When a method is cacheable, newly added or changed versions of records may not be returned until the cache is refreshed. Learn how to manually refresh the cache in the next section.

Call Apex Methods from LWC

There are two ways to interact with Apex methods from Lightning web components: either wire the method or call the method imperatively. Let’s consider both approaches.

Call Apex Using @wire

To wire an Apex method, the method must be cacheable. To wire a cacheable Apex method, use the @wire decorator (the same way you use an LDS wire adapter). Calling Apex this way delegates control to the Lightning Web Components engine and creates a reactive service. Every time the value of a parameter that’s passed to the Apex method changes, the Apex method provisions the new value to the decorated property or function. Because wired methods must be cacheable, data can come from the LDS cache or the server. To refresh the data that was cached by an Apex method, call the refreshApex function.

Note: Lightning Data Service is unaware of the data that's cached by Apex methods. When an LDS function updates a record, that update has no impact on data that was cached by an Apex method.

Here’s an example of using @wire to call Apex. This code gets related contacts for an account.


import { LightningElement, api, wire } from 'lwc';
import getRelatedContacts from '@salesforce/apex/AccountController.getRelatedContacts';
export default class WireApexProperty extends LightningElement {
    @api recordId;
    @wire(getRelatedContacts, { accountId: '$recordId' })

Code highlights:

  • Line 2: We import the getRelatedContacts function from the AccountController Apex class. This points to the corresponding Apex method.
  • Line 4: We define an @api recordId property, so that the FlexiPage passes the Id of the current record to the component.
  • Line 5: The @wire decorator receives two parameters: the Apex method that we want to call (getRelatedContacts), and the parameter that the adapter needs (accountId). We pass $recordId as a reactive variable (starting with $).
  • Line 6: The result is stored in the contacts property.
  • Lines 5–6: Initially, the Apex method provisions data to the contacts property, and stores that data in the LDS cache. Because $recordId is reactive, every time its value changes, the Apex method provisions new data, from either the cache or the server.

Call Apex Imperatively

An alternative to calling Apex with @wire is to call Apex imperatively. Call Apex imperatively when you need to control the invocation of read operations, and when you modify records. To call Apex imperatively, you invoke Apex one time from a component’s JavaScript file instead of delegating control to the Lightning Web Components engine. In return, you get a JavaScript promise (as you do when you call an LDS function imperatively).

You can call both cacheable and non-cacheable Apex methods imperatively. To refresh the cache for a cacheable method, call the method again.

In the callApexImperative.js example, when a user clicks a lightning-button in the .html file (not shown), handleButtonClick calls the getRelatedContacts Apex method imperatively.


import { LightningElement, api, wire } from 'lwc';
import getRelatedContacts from '@salesforce/apex/AccountController.getRelatedContacts';
export default class CallApexImperative extends LightningElement {
    @api recordId;
    handleButtonClick() {
        getRelatedContacts({ //imperative Apex call
            accountId: this.recordId
            .then(contacts => {
                //code to execute if related contacts are returned successfully
            .catch(error => {
                //code to execute if related contacts are not returned successfully

Code highlights:

  • Line 2: We import the getRelatedContacts function from the AccountController class.
  • Line 4: We define a public recordId property, so that the FlexiPage passes the Id of the account that this component is showing related contacts for.
  • Lines 6-7: When the handleButtonClick method is invoked by the framework, we invoke the getRelatedContacts Apex method imperatively, passing the accountId that the method needs to get related contacts for the right account.
  • Lines 9–14: The imperative Apex call in line 6 returned a promise. If the Apex method call is successful, the promise is fulfilled and the then method runs. Otherwise, the promise is rejected and the catch method runs.

The preferred way to work with lists of records in Lightning web components is to use the lightning-datatable base component. Use lightning-datatable to create tables of data with features such as infinite scrolling, inline editing, header and row-level actions, resizing, and more. This UI component needs to be fed data. The most common way to generate that data is to call Apex in any of the ways explained earlier in this module.

Deploy a Lightning Web Component That Lists Records in a Table

Let’s work with an example that displays a list of existing Accounts in a lightning-datatable. We’ll use Apex and @wire to retrieve the records.

  1. Create an Apex class named AccountController:
    1. In the Explorer pane, right-click the classes folder and select SFDX: Create Apex Class.
    2. For the class name, enter AccountController and press Enter.
    3. Press Enter again to accept the default directory.
  2. Replace the contents of your AccountController class with this code:
    public with sharing class AccountController {
        public static List<Account> getAccounts() {
            return [
                SELECT Name, AnnualRevenue, Industry
                FROM Account
                ORDER BY Name
    Code highlights:
    • Line 2: We annotate the method with @AuraEnabled(cacheable=true) so that the results are cached.
    • Line 3: We define the getAccounts method in Apex, to perform a read operation and retrieve existing accounts.
  3. Create a Lightning web component named accountList.
  4. Replace the contents of your accountList.js file with this code:
    import { LightningElement, wire } from 'lwc';
    import NAME_FIELD from '@salesforce/schema/Account.Name';
    import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
    import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
    import getAccounts from '@salesforce/apex/AccountController.getAccounts';
    const COLUMNS = [
        { label: 'Account Name', fieldName: NAME_FIELD.fieldApiName, type: 'text' },
        { label: 'Annual Revenue', fieldName: REVENUE_FIELD.fieldApiName, type: 'currency' },
        { label: 'Industry', fieldName: INDUSTRY_FIELD.fieldApiName, type: 'text' }
    export default class AccountList extends LightningElement {
        columns = COLUMNS;

    Code highlights:
    • Lines 2–4: We import field references, same as in the previous examples.
    • Line 5: We import the getAccounts function from the AccountController class.
    • Line 13: We use @wire with the getAccounts function to retrieve the data.
    • Line 14: We store the result in the accounts property. If the operation succeeds, the records are accessible on If it fails, the error surfaces in accounts.error.
  5. Replace the contents of your accountList.html file with this code:
            <template if:true={}>
    Code highlights:
    • Lines 4–9: We define the lightning-datatable base component to use and columns that are populated in the JavaScript file.
  6. Save your AccountController class.
  7. Replace the contents of your accountList.js-meta.xml with this code so that the component is available on app pages:
    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="">
  8. Save all three component files.
  9. Deploy the force-app/main/default folder to your Trailhead Playground.
  10. In your Trailhead Playground, navigate to the Lightning App Builder and open the Working with Data page.
  11. Drag the accountList component to the main region of the page.
  12. Save the page.
  13. Return to the Working with Data page to see your new component.


Some things to consider when using the lightning-datatable component.

  • Some data types are not supported in the lightning-datatable component today. However, you can use custom types. To learn more, scroll to Create Custom Data Types in the documentation.
  • The lightning-datatable component doesn’t work on mobile today. If you need to support mobile, create a custom table instead.

Now you know a few ways to interact with Salesforce data in your Lightning web components. Next, you learn how to handle server errors when they occur.


Keep learning for
Sign up for an account to continue.
What’s in it for you?
  • Get personalized recommendations for your career goals
  • Practice your skills with hands-on challenges and quizzes
  • Track and share your progress with employers
  • Connect to mentorship and career opportunities