How to Use Salesforce Change Data Capture (CDC) in LWC for Real-Time UI Sync — Without SOQL

Hannah Jane
Tags - Change - Data - Capture
"Hi I'm a freelance Salesforce blogger sharing insights, trends, and practical tips to help businesses grow, innovate, and thrive.”

1) CDC in one minute

Change Data Capture (CDC) streams row-level changes (create, update, delete, undelete) as events on special channels like /data/AccountChangeEvent. In LWC, you can subscribe to these channels with lightning/empApi and update the UI instantly—no polling, no SOQL, no refresh button slamming.

CDC events flowing into LWC via empApi

2) Set up: enable & permission

• In Setup → Change Data Capture, select the objects (e.g., Account, Opportunity).
• Give users Subscribe to Platform Events (and object perms).
• Note the channel naming: /data/<ObjectApiName>ChangeEvent.

Enable CDC for objects "I've read all the books! Honest! That's why I've hiden them with some beautiful Crysaaaanthzz ! :) Bonus: use ChangeEventHeader to see who changed what and why, and to filter only the fields you care about.

3) LWC: subscribe to a CDC channel

Minimal example that listens to AccountChangeEvent and updates an in-memory list—no SOQL round trips.

// cdcAccountList.js
import { LightningElement, track } from 'lwc';
import { subscribe, unsubscribe, onError, setDebugFlag } from 'lightning/empApi';

export default class CdcAccountList extends LightningElement {
  channelName = '/data/AccountChangeEvent';
  subscription = null;
  @track accounts = []; // [{Id, Name, Industry, LastModifiedDate}]

  connectedCallback() {
    setDebugFlag && setDebugFlag(true);
    onError(error => console.error('EMP API error', JSON.stringify(error)));
    this.subscribeToCdc();
  }

  disconnectedCallback() {
    if (this.subscription) {
      unsubscribe(this.subscription, () => {});
    }
  }

  subscribeToCdc() {
    subscribe(this.channelName, -1, (message) => {
      const payload = message.data.payload;
      const changeType = payload.ChangeEventHeader.changeType; // CREATE/UPDATE/DELETE/UNDELETE
      const id = payload.ChangeEventHeader.recordIds[0];

      if (changeType === 'DELETE') {
        this.accounts = this.accounts.filter(a => a.Id !== id);
        return;
      }

      // Build a lightweight projection (no SOQL)
      const updated = {
        Id: id,
        Name: payload.Name,
        Industry: payload.Industry,
        LastModifiedDate: payload.LastModifiedDate
      };

      const idx = this.accounts.findIndex(a => a.Id === id);
      if (idx > -1) {
        // Update in place
        this.accounts = [
          ...this.accounts.slice(0, idx),
          { ...this.accounts[idx], ...updated },
          ...this.accounts.slice(idx + 1)
        ];
      } else {
        // Insert at the top
        this.accounts = [updated, ...this.accounts];
      }
    }).then(resp => { this.subscription = resp; });
  }
}
<!-- cdcAccountList.html -->
<template>
  <lightning-card title="Accounts (live via CDC)">
    <template if:true={accounts}>
      <lightning-layout multiple-rows>
        <template for:each={accounts} for:item="acc">
          <lightning-layout-item key={acc.Id} padding="around-small" size="12">
            <div class="slds-box slds-box_x-small">
              <div class="slds-text-heading_small">{acc.Name}</div>
              <div class="slds-text-color_weak">{acc.Industry} • {acc.LastModifiedDate}</div>
            </div>
          </lightning-layout-item>
        </template>
      </lightning-layout>
    </template>
  </lightning-card>
</template>

4) Filter and route events (keep UI calm)

• Use ChangeEventHeader.changedFields to react only when relevant columns change.
• For large lists, route by record owner/queue to avoid repainting the world.
• Use separate channels per object to keep handlers small and predictable.

5) Replay & resilience

• The subscribe third parameter supports a replayId: -1 = new events only; store a last-seen ID if your component must “catch up.”
• Network blips happen—resubscribe on error and throttle UI updates (debounce).

6) When to still use SOQL

CDC sends changed fields, not the entire record graph. If you need joins/aggregates, load once with SOQL and then maintain freshness via CDC deltas.

7) Common pitfalls

• Forgetting to enable CDC on the object (no events = no fun).
• Mutating arrays without reassigning (LWC reactivity won’t fire).
• Over-subscribing (one mega-handler for five objects).
• Treating CDC as a data warehouse—keep payloads lean and UI-focused.

“Real-time beats refresh-time. Let events drive your UI, not queries.”
— Brian Edwards

“If your users notice a delay, your UI is polling. CDC is how you make it feel psychic.”

“Small handlers, clear filters, happy frames per second.”
— Sharon Fitzpatrick

Next step: we can enable CDC, scaffold LWC subscribers, and add replay-safe resubscription so your UI always feels live.