“Real-time beats refresh-time. Let events drive your UI, not queries.”
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.
• 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.
"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.
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>
• 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.
• 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).
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.
• 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.”
“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.”
Next step: we can enable CDC, scaffold LWC subscribers, and add replay-safe resubscription so your UI always feels live.