You're rethrowing errors and losing context. Error.cause fixes that.
Every time you catch an error and rethrow a new one without forwarding the original, you lose the stack trace, the error type, and everything useful about what actually went wrong. ES2022 added `Error.cause` to fix exactly this.

Error handling has a quiet problem. You catch an error deep in a call stack, wrap it in something more descriptive, and rethrow. The caller gets a meaningful message — but the original error, with its stack trace and details, is gone.
Your logs now say Failed to load user 42 and nothing else. Was it a network timeout? A 403? A JSON parse error? You don't know unless you explicitly log before rethrowing — which most people remember only after the third unexplained production incident.
ES2022 added the fix: the cause option on Error.
Every Error constructor accepts an optional second argument: an options object. Set cause to the original error and it's preserved on the new error:
Now the new error carries the original as err.cause. When the caller catches it, both levels are available:
The original error isn't gone — it's attached. One property to read when you need it, invisible when you don't.
Runs right in your browser — poke at it and watch the concept react live.
The old workaround was to concatenate messages:
This preserves the message text — but only the text. The stack trace of the original error disappears. The error type disappears. If the original error had a cause of its own, that disappears too.
With Error.cause, you get a chain, not a flattened string:
Every layer adds context. None of them destroy what came before.
The clearest use case is a service layer wrapping raw API calls:
The caller gets a domain error (UserService.getUser(42) failed) without losing the infrastructure detail (HTTP 403, ECONNREFUSED, SyntaxError: Unexpected token). A logging layer can walk the .cause chain to emit structured logs at each level. Error monitoring tools that understand cause chains — Sentry does — can render the full tree as a linked sequence rather than a flattened string.
TypeScript added the ErrorOptions type in 4.6. The cause field is typed as unknown, which is correct — any value can be a cause, not just Error instances:
Custom error classes work the same way — pass options through to super and the base Error constructor populates this.cause automatically:
cause accepts any value. If what went wrong was a failed validation, a non-Error rejected promise, or a raw HTTP response object, attach it directly:
err.cause holds the original object — not a stringified version of it. That's more useful than trying to serialize context into a message string and more structured than console-logging separately before rethrowing.
Error options including cause are Baseline 2022: Chrome 93, Firefox 91, Safari 15.4, Node.js 16.9. Every actively maintained browser and runtime ships it. There is nothing to install and nothing to polyfill; the only thing to change is the habit.
Think it clicked? Take the 6-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
Search your codebase for catch (err) { throw new Error( and look at each one. Where the catch clause doesn't forward err, it's swallowing context someone will want the next time that error appears in production.
Pass { cause: err } as the second argument to Error() and the original error stops disappearing. The message is what you put in the error. The cause is what actually went wrong underneath. They belong in the same object.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 💬 Discord — join the frontend best-practices community: discord.gg/d9KRhuAwQ
- 📸 Instagram — frontend best practices, daily: @bestpractice___
- 💼 LinkedIn — linkedin.com/in/parsa-jiravand
- ✉️ Email (work & contract inquiries): bestpractice2026@gmail.com
Originally published on dev.to