Study guide
12.4 LWC Data Access: Wire, Imperative & Events
Salesforce Certified Platform Developer I Exam
Track your progress as you read
Finished sections get a check mark.
1 Introduction & Exam Overview
1.1 Current Exam Facts 1.2 Blueprint Weights & Study Strategy
2 Platform Fundamentals
2.1 Multi-Tenant Architecture & Shared Resources 2.2 MVC Mapping on the Lightning Platform 2.3 Declarative vs Programmatic Decisions 2.4 Agentforce for Developers: Use Cases & Limitations
3 Data Modeling & Schema
3.1 Objects, Fields & Schema Design 3.2 Relationship Types & Implications 3.3 Schema Design Impact on Apex 3.4 Data Import & Export for Development
4 Platform Data Features
4.1 Formula Fields & Roll-Up Summaries 4.2 Core CRM Objects & Standard Schema 4.3 AppExchange Extensions & Platform Boundaries
5 Declarative Process Automation
5.1 Record-Triggered Flows & Declarative Automation 5.2 Approval Processes & Business Rules 5.3 When to Use Apex vs Declarative Tools
6 Apex Language Basics
6.1 Variables, Constants & Apex Data Types 6.2 Lists, Sets & Maps 6.3 Control Flow & Exception Handling 6.4 Classes, Interfaces & OOP Patterns
7 SOQL, SOSL & DML
7.1 SOQL Query Fundamentals 7.2 SOSL Search Fundamentals 7.3 DML Operations & Bulkification 7.4 Governor Limits & Transaction Boundaries
8 Apex Triggers & Order of Execution
8.1 Trigger Structure & Context Variables 8.2 Bulk Trigger Patterns & Best Practices 8.3 Save Order of Execution 8.4 Recursion, Cascading & Transaction Control
9 Asynchronous Apex
9.1 @future Methods 9.2 Queueable Apex 9.3 Batch Apex 9.4 Schedulable Apex & Job Orchestration
10 Security, Events & Schema Access
10.1 Sharing, FLS, CRUD & Injection Prevention 10.2 Platform Events Publish & Subscribe 10.3 Programmatic Schema Access (Describe)
11 Visualforce
11.1 Visualforce Pages & Markup 11.2 Standard, Custom Controllers & Extensions 11.3 Visualforce in Lightning Experience
12 Lightning Components
12.1 Lightning Component Framework Benefits 12.2 Aura Component Structure & Resources 12.3 Lightning Web Components Fundamentals 12.4 LWC Data Access: Wire, Imperative & Events
13 UI Composition & Apex Integration Patterns
13.1 Composing UI in Lightning Apps 13.2 UI Security, Performance & Accessibility Patterns 13.3 Apex Actions for Flow, LWC, and Agentforce
14 Apex Testing
14.1 Testing Framework & Coverage Requirements 14.2 Writing Unit Tests for Triggers & Classes 14.3 Test Data Strategies & Isolation 14.4 Executing Tests & Execute Anonymous Differences
15 Debugging & Developer Tools
15.1 Debug Logs & Monitoring 15.2 Developer Console, CLI, VS Code & Workbench 15.3 Sandboxes, Scratch Orgs & Environment Strategy
16 Deployment & Release
16.1 Change Sets & Metadata Deployment 16.2 Unlocked Packages, Metadata API & Salesforce DX 16.3 Release Readiness & Credential Maintenance
12.4 LWC Data Access: Wire, Imperative & Events
Key Takeaways Explain This Topic AI
@wire can provision data from Apex (@AuraEnabled(cacheable=true)) and from UI API adapters such as getRecord
Imperative Apex calls use imported methods as promises for on-demand, non-cacheable, or user-driven operations including DML
Lightning Data Service / uiRecordApi reduce custom Apex for simple record create, read, update, and delete scenarios
Child-to-parent communication uses CustomEvent; parent-to-child uses @api properties; unrelated components use LMS or pub-sub patterns
- Always handle wire/imperative error states and avoid silent failures—surface messages with toasts or inline UI
- Last updated: August 2026
12.4 LWC Data Access: Wire, Imperative & Events
Quick Answer: Use @wire for declarative, reactive reads from cacheable Apex or UI API adapters. Use imperative Apex (promise-based) for on-demand calls, DML , and non-cacheable operations. Prefer Lightning Data Service / uiRecordApi when standard record access is enough. Communicate parent↔child with @api and CustomEvent ; use Lightning Message Service (LMS) (or a pub-sub pattern) for unrelated components. Always branch on data vs error .
Data access questions combine UI skills with Apex annotations, caching rules, and composition. This section ties LWC to the server and to other components.
@wire to Apex
Expose read-only Apex for wiring with @AuraEnabled(cacheable=true) : public with sharing class ContactController {
- @AuraEnabled(cacheable=true)
- public static List<Contact> getContacts(Id accountId) {
- return [
- SELECT Id, Name, Email
- FROM Contact
- WHERE AccountId = :accountId
- ORDER BY Name
- LIMIT 50
- ];
- }
- }
- LWC import and wire: import { LightningElement, api, wire } from 'lwc';
- import getContacts from '@salesforce/apex/ContactController.getContacts';
- export default class ContactList extends LightningElement {
- @api recordId;
- contacts;
- error;
- @wire(getContacts, { accountId: '$recordId' })
- wiredContacts({ data, error }) {
- if (data) {
- this.contacts = data;
- this.error = undefined;
- } else if (error) {
- this.error = error;
- this.contacts = undefined;
- }
- }
- }
- Wire rules that appear on exams
- Rule Detail
- cacheable=true Required for Apex methods used with @wire
- No DML in cacheable methods Cacheable Apex must be read-only; DML does not belong there
- Reactive params '$recordId' re-invokes the wire when the property changes
- Property vs function form Property form assigns { data, error } ; function form lets you normalize data
- Sharing & FLS Server still enforces security—do not treat the client as trusted
Why wire? Automatic provisioning, reactivity, and platform caching for repeated reads. Ideal for initial page data that depends on recordId or filter fields.
Refreshing wired Apex
When local caches must update after a sibling’s DML (or your own imperative save), use refreshApex on the wired result object you retained from the property form—or re-drive reactive parameters. Exam stems that say “refresh the list after save” often expect imperative save + refreshApex on the wired list.
Imperative Apex Calls
Import the same Apex method and invoke it as a function that returns a Promise : import { LightningElement, api } from 'lwc';
- import getContacts from '@salesforce/apex/ContactController.getContacts';
- import savePriority from '@salesforce/apex/ContactController.savePriority';
- import { ShowToastEvent } from 'lightning/platformShowToastEvent';
- export default class ContactList extends LightningElement {
- @api recordId;
- contacts;
- async handleLoad() {
- try {
- this.contacts = await getContacts({ accountId: this.recordId });
- } catch (error) {
- this.dispatchEvent(
- new ShowToastEvent({
- title: 'Load failed',
- message: this.reduceError(error),
- variant: 'error'
- })
- );
- }
- }
- async handleSave(contactId, priority) {
- try {
- await savePriority({ contactId, priority });
- // then refresh lists / notify parent
- } catch (error) {
- // handle error
- }
- }
- reduceError(error) {
- if (Array.isArray(error?.body)) {
- return error.body.map((e) => e.message).join(', ');
- }
- return error?.body?.message || error?.message || 'Unknown error';
- }
- }
- Apex for DML cannot be cacheable=true : @AuraEnabled
- public static void savePriority(Id contactId, String priority) {
- Contact c = new Contact(Id = contactId, Priority__c = priority);
- update c;
- }
- When to choose wire vs imperative
- Need Prefer
- Load data when inputs change, read-only @wire + cacheable Apex or UI API
- Button click, conditional fetch, dynamic params hard to express Imperative
- Insert/update/delete via Apex Imperative non-cacheable Apex
- Simple standard record CRUD LDS / uiRecordApi / record forms first
Exam trap: Wiring a method that performs DML or lacks cacheable=true is incorrect. Conversely, forcing every read through manual buttons when a reactive wire fits is poor design.
Lightning Data Service & uiRecordApi Overview
Lightning Data Service (LDS) provides client-side caching and shared record state for Lightning UI. In LWC, you typically consume it through lightning/uiRecordApi adapters and through base components that use LDS under the hood.
- getRecord / field imports import { LightningElement, api, wire } from 'lwc';
- import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
- import NAME_FIELD from '@salesforce/schema/Account.Name';
- import PHONE_FIELD from '@salesforce/schema/Account.Phone';
const FIELDS = [NAME_FIELD, PHONE_FIELD];
- export default class AccountHeader extends LightningElement {
- @api recordId;
- @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
- account;
- get name() {
- return getFieldValue(this.account.data, NAME_FIELD);
- }
- get phone() {
- return getFieldValue(this.account.data, PHONE_FIELD);
- }
- }
- Record forms (high productivity)
- Base components can remove entire Apex controllers for standard layouts: <template>
- <lightning-record-edit-form object-api-name="Contact" record-id={recordId} onsuccess={handleSuccess}>
- <lightning-input-field field-name="FirstName"></lightning-input-field>
- <lightning-input-field field-name="LastName"></lightning-input-field>
- <lightning-button type="submit" label="Save"></lightning-button>
- </lightning-record-edit-form>
- </template>
When LDS/UI API is enough: read or edit known standard/custom fields with platform FLS, optimistic UI cache sharing across components on the page, and minimal custom server logic.
When custom Apex is still required: complex SOQL across many objects, custom aggregation, callouts orchestrated with DML, or security/business rules that do not fit form-based CRUD.
Other UI API operations to recognize by name: getRecord , getRecords , updateRecord , createRecord , deleteRecord , getFieldValue , getFieldDisplayValue .
- Component Communication: Events & Beyond
- Parent → child: @api properties
- Parents pass data downward: <c-priority-badge level={priority}></c-priority-badge>
- Child → parent: CustomEvent // child.js
- notifyParent(contactId) {
- this.dispatchEvent(
- new CustomEvent('contactselect', {
- detail: { contactId },
- bubbles: false,
- composed: false
- })
- );
- } <!-- parent.html -->
- <c-contact-list oncontactselect={handleContactSelect}></c-contact-list> // parent.js
- handleContactSelect(event) {
- const { contactId } = event.detail;
- this.selectedId = contactId;
- }
Conventions:
- Event names in markup are lowercase with an on prefix ( oncontactselect )
- Pass payload in event.detail
- Prefer non-bubbling events unless you intentionally design for ancestors further up
- Unrelated components: LMS / pub-sub (high level)
When two components do not share a direct parent-child relationship (for example, utility bar vs record detail body):
Lightning Message Service (LMS) is the modern platform service for publish/subscribe across the DOM tree (and supported containers) using a message channel metadata type
Older materials mention pubsub utility modules as a lightweight pattern for sibling communication on the same page
Exam heuristic:
- Relationship Mechanism
- Parent configures child @api
- Child notifies parent CustomEvent
- Distant / sibling / cross-dom LMS (or pub-sub pattern)
Do not invent application-wide custom DOM hacks to scrape another namespace’s Shadow DOM.
Error Handling Patterns
Robust LWC data code always assumes failure modes: invalid Id, FLS, empty lists, Apex exceptions, and network issues.
- Wire errors @wire(getContacts, { accountId: '$recordId' })
- wiredContacts({ data, error }) {
- if (data) {
- this.contacts = data;
- this.error = undefined;
- } else if (error) {
- this.error = error;
- this.contacts = undefined;
- }
- }
- Template: <template>
- <template lwc:if={error}>
- <p class="slds-text-color_error">Unable to load contacts.</p>
- </template>
- <template lwc:if={contacts}>
- <!-- render list -->
- </template>
- </template>
(Exact conditional directives vary by examples you memorize—focus on branching on error vs data , not only happy path.)
Imperative errors
Use try/catch with async/await , or .then/.catch . Normalize Apex error shapes ( error.body.message , array of field errors) before showing ShowToastEvent or inline messages.
- User feedback
- Toasts for save success/failure
- Spinners while promises are pending
- Inline messages for field-level problems on forms
- Disable submit buttons during in-flight DML to prevent double posts
- Security-related “errors”
If users lack field access, UI API and well-written Apex should fail closed or omit fields—not leak data. Prefer with sharing , user-mode / stripInaccessible patterns where appropriate, and least-privilege design.
- End-to-End Pattern: List + Save + Notify
- Wire cacheable getContacts by recordId
- Child row button imperatively calls non-cacheable Apex update
- On success, refreshApex the wired list (or update local state carefully)
- Dispatch CustomEvent so a parent highlights the saved record
- If a utility component must react, publish on an LMS channel
That storyline stitches every major 12.4 idea into one realistic Lightning page design.
Quick Decision Table
- Scenario Best tool
- Show Account Name on record page getRecord / record view form
- Query contacts with custom filters Cacheable Apex + @wire or imperative
- Update a field on button click Imperative Apex or updateRecord
- Admin-editable layout of standard fields lightning-record-edit-form
- Child tells parent which row was clicked CustomEvent
- Two components on page without hierarchy LMS / pub-sub
- Common Exam Traps
- Using @wire on Apex without cacheable=true
- Putting DML inside cacheable Apex
- Ignoring error on wired results
- Using application-style global hacks instead of CustomEvent for parent-child
- Writing Apex for a form that lightning-record-*-form already handles
- Forgetting that imperative Apex returns a Promise (must handle async)
Bottom line: @wire for reactive, cacheable reads (Apex or UI API); imperative Apex for on-demand and DML; LDS/ uiRecordApi for standard record work; CustomEvent + @api for hierarchy; LMS/pub-sub for unrelated components; and explicit error handling on every data path.