Home / lwc / lwc-error-boundaries
lwc-error-boundaries
Isolate component errors so one failure does not blank an entire page using errorCallback and graceful fallbacks. NOT for server-side Apex exception design — use apex/exception-handling. Also covers: normalising error.body across UI API read, UI API write, Apex and network shapes; fallback UI; retry by remount; telemetry hand-off from the boundary.
Open the skill folder on GitHub
LWC Error Boundaries
errorCallback(error, stack) on any ancestor "captures errors in all the descendent components in its tree" — errors "that occur in lifecycle hooks or during an event handler declared in an HTML template" (lwc_guide create-lifecycle-hooks-error L4158). Wrapping each widget in a reusable boundary keeps one failure from removing the whole page.
This skill covers the boundary component: where to place it, what its fallback may safely depend on, how to reduce the four documented error shapes to one structure the fallback and the telemetry record can both use, how to offer a retry that actually re-mounts the child, and how to prove all of it with Jest. The hook's own contract and its position in the lifecycle belong to lwc/lifecycle-hooks.
Before Starting
Check for salesforce-context.md in the project root. If present, read it first — particularly whether any of these components land on an Experience Cloud site, which changes which toast module is legal (references/gotchas.md, Gotcha 10).
Gather if not available:
- Which subtrees are independently useful, and which would leave the page pointless if they
vanished.
- Which error producers each subtree uses: UI API wire adapters, imperative Apex, plain
fetch, or a mix. Each has a differenterror.bodyshape. - Where client errors are recorded today, if anywhere.
- Whether users can meaningfully retry, or whether the failure is deterministic.
Questions to Ask Before Configuring
Ask these before writing the wrapper. An assistant that skips them produces a grey box that looks correct in review and reports nothing in production.
| Ask | Why it matters | What a good answer adds |
|---|---|---|
| "If this subtree disappears, can the user still do something useful on this page?" | The guide leaves placement to you — "You can wrap the entire app, or every individual component" (L4160) — but the throwing subtree is unmounted and removed from the DOM (L4162) | The boundary count and their exact positions in the markup |
| "Which of these components read from a UI API wire, and which call Apex imperatively?" | UI API reads return error.body as an array; UI API writes, Apex and network errors return an object (L6568–L6571) | Which normaliser branches are actually exercised, and which fixtures the Jest suite needs |
| "Does the failing operation have any chance of succeeding on a second attempt?" | A Retry button on a deterministic failure is a loop with a friendly label | Whether the fallback gets a Retry button, and what maxRetries should be |
| "Where do client-side errors go today — a custom object, an external logger, or nowhere?" | A silent boundary removes the user's only reason to report the problem while removing none of the problem | The telemetry target, the payload fields, and who reads it |
| "Is this component ever placed on an Experience Cloud page?" | lightning/platformShowToastEvent "isn't supported in environments like LWR sites for Experience Cloud or standalone apps" (L4773) | The toast module choice, and whether lightningCommunity__Default belongs in the .js-meta.xml |
"Does the Apex behind this throw AuraHandledException, or does it throw uncaught?" | An uncaught Apex exception surfaces the Apex class name in body.stackTrace at the client (L7518); AuraHandledException omits it (L7521) | Whether the fallback may show the message at all, and a work item for apex/exception-handling |
| "Are any event handlers in this subtree attached in JavaScript rather than in the template?" | Errors from programmatically assigned handlers are not caught by errorCallback (L4167) | The list of handlers that need their own try/catch, since the boundary will never see them |
What a proper boundary adds over just implementing errorCallback: the failure stops at a subtree the user can afford to lose, the message shown comes from the right level of the error object regardless of which API produced it, the failure is recorded before the fallback renders, and a recoverable failure gets one bounded retry instead of a page reload.
Recommended Workflow
- Place the boundaries. Mark each independently useful subtree in the parent template.
Wrap each one — not the page — using the placement test from the Questions table above. Record the count; it is the number of
boundary-namevalues you will see in telemetry. - Deploy the normaliser first. Copy the
errorUtilsmodule fromreferences/code-examples.md(Bundle 1). It is the only place in the bundle set that knowserror.bodyis an array for a UI API read and an object for everything else. Every branch in it carries its guide line; keep the citations when you copy it. - Build the boundary. Copy
errorBoundary(Bundle 2):errorCallback→ normalise →hasError→ fallback withlwc:if/lwc:else→ optional Retry → telemetry inside its owntry/catch. Usetemplates/lwc/component-skeleton/for the bundle shell. Do not add a wire adapter or an imperative call to the boundary; it must have nothing of its own that can throw. - Handle what the boundary cannot see, in the child. Copy the
revenueTilepattern(Bundle 3) for wire
errorbranches, imperativecatchblocks and programmatic handlers, routing all three into the same normaliser. Cross-check againsttemplates/lwc/patterns/imperativeApexPattern.jsandtemplates/lwc/patterns/wireServicePattern.js. - Run the checker.
python3 skills/lwc/lwc-error-boundaries/scripts/check_lwc_error_boundaries.py --manifest-dir force-app/main/default/lwcFix every ERROR (EB001 swallowederrorCallback, EB002 rethrow). Add--strictin CI to fail on the WARN rules too (EB003 unguardederror.body.message, EB004 console-only error branch, EB005platformShowToastEventon an Experience Builder target). - Test both halves. Port the Jest suites from
references/code-examples.md: one caseper normaliser branch with the fixture payloads from
references/examples.md, and the boundary suite with a throwing child, the telemetry hand-off, the no-rethrow assertion and the retry re-mount. - Verify in the org. Work the seven-row verification table at the end of
references/code-examples.md— deploy, force a failure, confirm one tile degrades, and confirm a telemetry row exists for it.
The four error shapes, once
| Producer | error.body is | Guide line |
|---|---|---|
UI API read (getRecord, related lists) | array of objects | data-error L6568 |
UI API write (createRecord, updateRecord) | object, often with object- and field-level errors | data-error L6569 |
| Apex read and write | object | data-error L6570 |
| Network, such as offline | object | data-error L6571 |
errorCallback(error, stack) | no body at all — a native Error, and stack is a string | create-lifecycle-hooks-error L4164 |
The wrapper around the first four is a FetchResponse: body, ok (always false, status 400–599), status, statusText (data-error L6545–L6551). Before a wire fires, data and error are both undefined — not an error state (L6572).
Division of labour
| Failure | Where it surfaces |
|---|---|
| Throw in a descendant's lifecycle hook | Ancestor errorCallback (L4158) |
| Throw in a descendant's template-declared handler | Ancestor errorCallback (L4158) |
| Throw in a programmatically attached handler | Nowhere — local try/catch (L4167) |
| Rejected promise from imperative Apex | Nowhere — .catch / try/await (L6513, L6524) |
| Wire adapter failure | The wired property's error member (L6542) |
| Nothing catches it at all | The app shell's "A Component Error has occurred!" modal (L7732) |
Adoption Signals
Dashboards with multiple independent widgets; record home pages with many components; any page where a single tile calls Apex and a second tile is the reason users opened the page.
Key Considerations
- The boundary owns no business logic, no wire adapter and no imperative call — anything it
owns is a way for the error handler itself to fail.
- The fallback must not depend on data that may be the reason the boundary fired.
lwc:iftakes a property or a getter, never an expression:lwc:if={!hasError}is notsupported (
reference-directives L19510). Uselwc:else.- Cap retries. A deterministic failure will happily re-mount forever.
- Keep
body.stackTraceand thestackstring in telemetry, never in the rendered message.
Worked Examples (see references/examples.md)
- Dashboard tile isolation — six-tile sales dashboard, one boundary per tile
- The failures the boundary does not catch — wire error, promise rejection, programmatic handler
- The four error payloads, as fixtures — one JSON block the Jest suite asserts against
Common Gotchas (see references/gotchas.md)
error.bodyis an array for UI API reads —error.body.messagereturnsundefined.- A legal app-level boundary still costs the whole app — the unmount takes everything below it.
- A fallback with dependencies — fails inside the failure handler, with nothing above to catch it.
- Toggling the retry flag twice in one block — both writes land in the same microtask, so nothing happens.
Top LLM Anti-Patterns (full list in references/llm-anti-patterns.md)
- Reading
error.body.messagewithout anArray.isArrayguard - Rethrowing from
errorCallbackto "let something upstream handle it" - Catching silently, so production failures are invisible
- A fallback that renders the raw error object, Apex class name and all
Official Sources Used
Full list with the claim each supports in references/well-architected.md § Official Sources Used.
- errorCallback() — https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks-error.html
- Handle Errors in Lightning Data Service — https://developer.salesforce.com/docs/platform/lwc/guide/data-error.html
- Work with Errors — https://developer.salesforce.com/docs/platform/lwc/guide/data-error-types.html
- Handle Errors from Apex — https://developer.salesforce.com/docs/platform/lwc/guide/apex-error-handling.html
- Toast Notifications — https://developer.salesforce.com/docs/platform/lwc/guide/use-toast.html
Reference Files
| File | Read it when |
|---|---|
references/code-examples.md | You are writing the components: the errorUtils module, the errorBoundary bundle, the revenueTile bundle, .js-meta.xml, package.xml, deploy order, both Jest suites, and the verification table |
references/examples.md | You want the worked scenarios and the four fixture payloads to test the normaliser against |
references/gotchas.md | Something behaves unexpectedly — the array body, the silent retry, the invisible toast, the boundary that cannot query its child |
references/well-architected.md | You are justifying boundary placement, normalisation or telemetry in a design review, and need the source list |
references/llm-anti-patterns.md | You are reviewing generated boundary code, or about to generate some |
Related Skills
- lwc/lifecycle-hooks: owns the
errorCallbackcontract, hook ordering, and the minimal boundary example. Read it first; this skill assumes it. - lwc/wire-service-patterns: the wire
{ data, error }contract and refresh behaviour behind the wire branch here. - lwc/lwc-toast-and-notifications: toast copy, variants, containers, and which module works in which container.
- lwc/common-lwc-runtime-errors: the catalogue of runtime error messages you will be normalising.
- lwc/lwc-testing: broader Jest patterns; this skill carries only the boundary and normaliser suites.
- apex/exception-handling: the server side — throwing
AuraHandledExceptionso the client never sees an Apex class name. - apex/debug-and-logging: where the telemetry payload from
errorCallbackshould land, and how to read it back.