cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Stack traces & the stack trace API

Learn when error.stack is captured, how V8's stack trace API customizes frames, how source maps rewrite locations, and what TC39 is standardizing.

By the end, you can
  • 01
    Separate capture from formattingExplain why V8 captures stack frames when an Error is constructed, then formats the stack lazily on first access.
  • 02
    Use V8 stack APIs carefullyControl frame count with Error.stackTraceLimit, hide helper frames with Error.captureStackTrace, and inspect CallSite objects with Error.prepareStackTrace.
  • 03
    Read mapped and future stacks honestlyDescribe how source maps change displayed file locations, and how the Error stack accessor proposal differs from older stack APIs.

A stack trace is a captured route

A stack trace is a list of active calls that led to a point in your program. When an error says at renderInvoice above at checkout, it is showing the path through function calls, newest frame first.

Definition

The stack trace API is the collection of mostly non-standard hooks around error.stack. In V8, an Error captures stack frames when it is constructed, formats them lazily when stack is first read, and lets controlled environments customize the frame count and formatter.

This lesson is not the everyday “how do I read an error?” path. For that, use Reading errors, Custom errors, try/catch, and the debugging lessons. Here we look at how the stack is built and where tool authors can hook into it.

  1. Create an error object near the interesting moment.
  2. The engine records frames, with engine-specific limits.
  3. Later code reads error.stack, so the engine formats those frames.
  4. Tooling may remap generated locations back to original source files.

We will stay synchronous in this lesson. The next lesson, Async stack traces, explains how await and promises add missing asynchronous history.

Captured when the Error is made

In V8, new Error() captures the stack when the object is constructed, not when it is thrown. That detail surprises people who create one error early, keep it around, and throw it somewhere else later.

Real-life analogyA photo of a stack of plates

A plate falls from a stack, so you take a photo immediately. When you show that photo later, it still shows the stack as it looked then. An Error stack works the same way: it points to where the Error was made, not where you later throw it.

In real life: A plate falls from the stack
In JavaScript: new Error() is constructed
In real life: You take a photo right away
In JavaScript: V8 captures the current stack frames
In real life: You show the photo later
In JavaScript: The same error object is thrown later
In real life: The photo shows the stack at that moment
In JavaScript: The stack still points at construction

Where the analogy stops: A photo cannot explain why a plate fell. An error stack is also limited diagnostic evidence, so create a fresh Error for a fresh stack.

Step through capture time versus throw time
Step 0 of 8Ready
Your turn: follow the blue line

Follow the moment a stack is captured. The Error is made on line 2, then thrown on line 8 without changing its original stack.

Running in
  1. script
Next: line 14
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function makeError() {  const error = new Error("plate fell");  return error;} function throwLater(error) {  try {    throw error;  } catch (caught) {    return caught.stack.split("\n").slice(0, 4);  }} const firstLines = throwLater(capturedError);console.log(firstLines[1].includes("makeError"));console.log(firstLines.some((line) => line.includes("throwLater")));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Line 2 constructs the error. Line 8 throws the same object later. The first printed value is true because the captured stack includes makeError. The second printed value is false because throwLater was not part of the original capture.

Capture and formatting are different

V8 keeps stack information internally, then formats the string on demand the first time error.stack is read. That is why custom formatters can still change what the first read returns.

This also explains a practical habit: create the error at the failure boundary. If you create it long before the failure, your logs will point at the factory, not the place where the decision to fail happened.

captureStackTrace and stackTraceLimit

Error.captureStackTrace(target, hiddenFunction) installs stack information on any object. The second argument asks V8 and supporting engines to omit frames above that function, including the function itself. Error helpers use this to hide plumbing.

Hide the helper frame with Error.captureStackTraceJavaScript
function publicApi() {  const marker = { name: "StackMarker" };  Error.captureStackTrace(marker, publicApi);  return marker.stack.split("\n").slice(0, 4).map((line) => line.trim());} function userCode() {  return publicApi();} const lines = userCode();console.log(typeof Error.captureStackTrace);console.log(lines[0]);console.log(lines[1].includes("userCode"));console.log(lines.some((line) => line.includes("publicApi")));

Line 3 captures a stack on marker and hides publicApi. Line 14 proves the first useful frame is userCode. Line 15 proves the helper frame is gone. MDN's browser-compat data now lists modern Firefox and Safari support, but old browsers and smaller engines still need a feature check.

Real-life analogyKeeping only the top ten plates

Take a photo of a stack of plates, but keep only the top ten in the picture. You can keep fewer, more, or every visible plate. The stack did not change; only the amount you kept changed.

In real life: A stack holds plates in order
In JavaScript: A stack records call frames in order
In real life: Keep a photo of only the top ten plates
In JavaScript: Error.stackTraceLimit limits collected frames
In real life: Leave a helper plate out of the photo
In JavaScript: captureStackTrace(obj, fn) omits helper frames
In real life: Keep every visible plate
In JavaScript: Infinity asks V8 to collect all available frames

Where the analogy stops: Real plates do not have a fixed number at the top. JavaScript stacks depend on the engine, host formatting, and privacy choices.

Playground: change the stack frame budget
The code the playground runs with the selected limitJavaScript
const previousLimit = Error.stackTraceLimit;Error.stackTraceLimit = 3; function deep(level) {  if (level === 0) return new Error("limit check").stack;  return deep(level - 1);} const lines = deep(6).split("\n");console.log(lines.slice(1).length);Error.stackTraceLimit = previousLimit;
Observed stacknot run
Requested limit3

Press Run to capture stack lines in this browser.

No stack captured yet. Choose a limit, then press Run.

Step 0 of 4not captured
Choose `Error.stackTraceLimit`

Press Run to capture a stack in this browser. The lesson waits for your click before reading browser-specific stack text.

This is real code running in your browser when the API exists. Browser engines that do not implement stackTraceLimit show a safe fallback. The source panel is a teaching model and is not an engine inspector.

Error.stackTraceLimit defaults to 10 in V8. Setting it to 0 disables collection, a finite integer caps frames, and Infinity asks for all available frames. The setting is per V8 context, which usually means one page or iframe in Chrome.

Stack trace API knobs and when to use them
APIWhat it controlsPractical caution
new Error()Captures stack information in V8 when the error object is constructed.Use it near the failing condition, not minutes before, if you want a useful creation site.
error.stackA de facto, non-standard property today; formats are different across engines.Good for logs, not for portable parsing unless you control the engine.
Error.captureStackTrace(obj, fn)Installs a stack on any object and can hide frames through fn.Great for error helpers, but guard it when supporting older browsers.
Error.stackTraceLimitV8-specific limit for how many frames are collected in the current context.Raise temporarily for diagnostics; restore it so one page does not surprise another.
Error.prepareStackTraceV8-specific hook that receives an error and an array of CallSite objects.Powerful for tools, risky as a global setting in apps.

prepareStackTrace and CallSite objects

V8's Error.prepareStackTrace is a global formatter hook. When V8 formats a stack, it calls your function with the error and an array of CallSite objects. A CallSite is a structured object for one frame: it can report the function name, file name, line, column, whether the call was a constructor, and more.

Step through a custom stack formatter
Step 0 of 8Ready
Your turn: follow the blue line

Watch V8 hand structured CallSite objects to a custom formatter. This is powerful, global, and V8-specific.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
 Error.prepareStackTrace = (error, callSites) => {  const first = callSites[0];  return {    message: error.message,    functionName: first.getFunctionName(),    line: first.getLineNumber(),    native: first.isNative(),  };}; function inspectStack() {  const error = new Error("custom format");  return error.stack;} const stack = inspectStack();console.log(stack.functionName);console.log(typeof stack.line);console.log(stack.native); Error.prepareStackTrace = previous;
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The example returns an object instead of a string to prove that V8 accepts any formatter result. Line 7 reads the newest CallSite's function name. Line 8 stores its line number. Line 23 restores the previous formatter, which is not optional in real tools because the hook affects every stack formatted in that context.

CallSite methods you will see in tooling

V8 documents methods such as getFunctionName(), getFileName(), getLineNumber(), getColumnNumber(), isNative(), isConstructor(), and getTypeName(). They are useful in Node diagnostics and test tools, but they are not portable language features.

If you customize this hook, keep the formatter tiny, wrap changes in try/finally, and avoid shipping a global formatter in a browser app unless you own the whole runtime. Logging frameworks and test runners are better homes for it.

Source-mapped stack traces

Production JavaScript often runs after bundling, minifying, or compiling TypeScript. A source map is a file that maps a generated location back to the original source location. DevTools uses maps for browser stacks, and Node can do the same with --enable-source-maps.

Generated code with an inline source map commentJavaScript
// compiled-stack-demo.js, produced by a build toolfunction crash() {  throw new Error("mapped");}crash();//# sourceMappingURL=data:application/json;base64,...

The generated file still executes. The map only changes reported locations. The lesson test runs a child Node process with an inline source map and --enable-source-maps, then asserts that the thrown stack names original-stack-demo.ts. That is a tooling fact, not a new JavaScript semantic rule.

Verified runtime fact

The test builds a tiny data-URL source map in memory, runs Node as a child process, and checks stderr for the original file name. It does not assert exact columns or absolute paths because those differ by platform.

In a real site, publish source maps only with the privacy level your team accepts. They are excellent for debugging, but they can reveal original names and comments if you upload full maps publicly.

Proposals and engine support

Today, error.stack is a de facto property, not fully standardized behavior. The TC39 Error stack accessor proposal is Stage 3 and focuses on standardizing the long-existing Error.prototype.stack accessor shape. The older broader Error Stacks proposal remains Stage 1.

Node 22 / V8 exposes stack as an own accessorJavaScript
const descriptor = Object.getOwnPropertyDescriptor(new Error("proposal"), "stack");console.log(typeof descriptor.get);console.log(typeof descriptor.set);console.log(descriptor.enumerable);console.log(descriptor.configurable);

Node 22 with V8 12.4 prints function, function, false, and true for that descriptor. Safari's historical shape differs, and Firefox has used a prototype accessor. That is exactly why TC39 is standardizing the common surface carefully instead of standardizing every engine's stack internals.

Which stack features are portable?
FeatureCurrent support shapeHow to use it
error.stackNon-standard today, but browsers and Node expose it with different formats.TC39 is standardizing an accessor, not a universal string format.
Error.captureStackTraceChrome has long supported it; MDN BCD lists Firefox 138+, Safari 17.2+, Node 0.10+, Deno, and Bun too.Still check before calling when your code can run in older or embedded engines.
Error.stackTraceLimitDocumented by V8 and Node/V8; not a JavaScript language feature.Use only as an engine-specific diagnostic knob.
Error.prepareStackTrace and CallSiteDocumented by V8. Other engines may ignore it or expose different internals.Use for Node tooling and controlled Chrome/V8 environments, not portable app logic.
Sort the stack feature
  • `throw new Error("boom")`
  • The Error stack accessor proposal
  • `error.stack`
  • `Error.captureStackTrace`
  • `Error.stackTraceLimit`
  • `Error.prepareStackTrace`
  • `CallSite#getLineNumber()`
  • `node --enable-source-maps app.js`
Try it yourself
0 of 8 correct

Decide whether each card is standard/proposal work, a de facto cross-engine behavior, V8-specific stack API, or host tooling.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Use stack power safely

Working developers usually need stacks for three jobs: logs, test diagnostics, and developer tools. In app code, prefer simple custom error classes and clear messages. Reach for V8 hooks only when you control the runtime or are writing tooling for Node and Chrome.

  • Create fresh errors at the failure boundary so the construction stack is meaningful.
  • Use captureStackTrace to hide helper frames, with a feature check for older engines.
  • Raise stackTraceLimit only around diagnostics, then restore it.
  • Restore prepareStackTrace in finally; it is global in the current context.
  • Enable source maps in development and production observability only when the map exposure is acceptable.

Browser debugging lessons such as Debugging in the browser and Advanced debugging cover DevTools workflows. This lesson gives you the engine-level vocabulary for understanding what those tools display.

Common misconceptions

  • “The stack is captured when I throw.” In V8, normal Error stacks are captured when the Error is constructed.
  • “`error.stack` is standardized like `message`.” The property exists widely, but exact behavior and formatting are still being standardized.
  • “Source maps change what ran.” They change reported source locations, not runtime execution.
  • “`prepareStackTrace` is a safe app-level formatter.” It is a V8-specific global hook. Use it in controlled tooling and restore it.
  • “A longer stack is always better.” Longer stacks cost memory and can expose private implementation details.
Pairs that are easy to confuse
PairCorrect ideaDo not confuse it with
Captured vs thrownV8 captures frames when the Error is created.Throwing the same object later does not rewrite its creation stack.
Captured vs formattedCaptured frames are kept internally; the string is made on first stack access.Formatting is not the same moment as capture.
Source map vs runtimeA source map remaps displayed file, line, and column information.It does not change which JavaScript actually executed.
Proposal vs V8 APIThe Stage 3 accessor proposal standardizes Error.prototype.stack shape.It does not standardize prepareStackTrace, stackTraceLimit, or CallSite methods.

Practice exercises

Exercise 1 · Warm-upPredict a reused error

Read the snippet and type the two printed boolean values.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function makeError() {
  return new Error("packed");
}
function deliver(error) {
  try {
    throw error;
  } catch (caught) {
    return caught.stack;
  }
}
const error = makeError();
const stack = deliver(error);
console.log(stack.includes("makeError"));
console.log(stack.includes("deliver"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeHide a helper frame

    Which function name should remain as the first useful stack frame?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function publicApi() {
      const marker = { name: "StackMarker" };
      Error.captureStackTrace(marker, publicApi);
      return marker.stack.split("\n")[1];
    }
    function userCode() {
      return publicApi();
    }
    console.log(userCode().includes("userCode"));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeCount collected frames

      With a limit of 2, how many stack frame lines are collected?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const oldLimit = Error.stackTraceLimit;
      Error.stackTraceLimit = 2;
      function a() {
        return b();
      }
      function b() {
        return new Error("short").stack.split("\n").slice(1).length;
      }
      console.log(a());
      Error.stackTraceLimit = oldLimit;

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFind the global hook bug

        The starter code is safe because it restores a global hook. Name the thing that must be restored.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const previous = Error.prepareStackTrace;
        try {
          Error.prepareStackTrace = () => "short";
          console.log(new Error("x").stack);
        } finally {
          Error.prepareStackTrace = previous;
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticeApply source maps to a real build

          In the lesson proof, which original file name should Node show after applying the inline source map?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeCheck proposal status before relying on it

            What TC39 stage did the verified Error stack accessor proposal README report?

            Answer, then press Check. Spacing and letter case don’t matter.

              Quiz

              Check the difference between construction, throwing, formatting, mapping, and standardization. The code questions are proved by the lesson tests in Node/V8.

              Stack trace API quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In V8, when is the stack trace for a normal Error captured?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does this reused-error snippet print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const error = new Error("ready");
                function throwLater() {
                  try {
                    throw error;
                  } catch (caught) {
                    console.log(caught.stack.includes("throwLater"));
                    console.log(caught.stack.includes("ready"));
                  }
                }
                throwLater();

                Choose an answer to see the explanation.

              3. Question 3 of 7Which support statement is accurate for Error.captureStackTrace today?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does the stack limit snippet print in V8/Node?

                Read the code, then predictJavaScript
                const oldLimit = Error.stackTraceLimit;
                Error.stackTraceLimit = 1;
                function a() { return b(); }
                function b() { return new Error("short").stack.split("\n").slice(1).length; }
                console.log(a());
                Error.stackTraceLimit = oldLimit;

                Choose an answer to see the explanation.

              5. Question 5 of 7What does prepareStackTrace return here?

                Read the code, then predictJavaScript
                const oldPrepare = Error.prepareStackTrace;
                Error.prepareStackTrace = (error, sites) => sites[0].getFunctionName();
                function sample() {
                  return new Error("x").stack;
                }
                console.log(sample());
                Error.prepareStackTrace = oldPrepare;

                Choose an answer to see the explanation.

              6. Question 6 of 7What does a source map change in a stack trace?

                Choose an answer to see the explanation.

              7. Question 7 of 7Which proposal status did the verified TC39 READMEs report?

                Choose an answer to see the explanation.

              Key takeaways

              • In V8, normal error stacks are captured at construction time and formatted lazily on first access.
              • Error.captureStackTrace can install a stack and hide helper frames; Error.stackTraceLimit controls V8 frame count.
              • Error.prepareStackTrace receives V8 CallSite objects, but it is global and engine-specific.
              • Source maps remap displayed locations; they do not change runtime call order.
              • The Error stack accessor proposal is Stage 3; it standardizes the stack property surface, not V8's formatter API.
              One-line summary

              Treat stacks as captured diagnostic evidence: create errors at the right moment, customize only in controlled runtimes, and map generated locations back to source when tooling can do it safely.

              Next, Async stack traces shows how engines reconstruct stack history across await, promise combinators, and callback boundaries.

              CompleteFrontend Clear concepts. Working examples.