completefrontend
Open lab My progress
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
    Know when a stack is madeExplain why V8 writes down the list of calls when an error is made, and turns it into text only when stack is first read.
  • 02
    Use V8's stack tools with careSet how many calls are kept with Error.stackTraceLimit, hide helper functions with Error.captureStackTrace, and read CallSite objects with Error.prepareStackTrace.
  • 03
    Read mapped stacks and know what is officialDescribe how source maps change the file names a stack shows, and how the Error stack accessor proposal differs from V8's own tools.

A stack trace is a list of calls

A stack trace is a list of the function calls that were still running at one moment in your program. Each line in the list is one call. Tools call one line a frame. The newest call is at the top.

Say an error shows at renderInvoice above at checkout. That means checkout called renderInvoice, and the error happened inside renderInvoice.

The main idea

Every error object has a stack property. It holds the stack trace as text. The stack trace API is a small set of tools around error.stack. Most of them are not in the official JavaScript rules.

Many of these tools come from V8. V8 is the engine, the program that runs JavaScript, inside Chrome, Edge, and Node.js. In V8:

  • An error writes down the list of calls when it is made.
  • It turns that list into text only when stack is first read.
  • You can change how many calls it keeps and how the text looks.

This lesson is not about reading an everyday error. For that, see Reading errors, Custom errors, try/catch, and the debugging lessons. Here you will see how the list is made, and where tools can change it.

A stack trace goes through four steps:

  1. Your code makes an error object.
  2. The engine writes down the calls that are running. Each engine has its own limit on how many it keeps.
  3. Later, some code reads error.stack. Only then does the engine turn the list into text.
  4. A tool may change the file names and line numbers in that text to point at the files you wrote.

All the code in this lesson runs straight through, one line after another. Code that waits, with await or promises, comes in the next lesson, Async stack traces.

The list is written down when the error is made

In V8, new Error() writes down the list of calls at the moment the error object is made. It does not wait until the error is thrown. This surprises people who make an error early, keep it, and throw it later from somewhere else.

Real-life analogyA photo of a stack of plates

A plate falls from a stack of plates, so you take a photo right away. Later you show someone the photo. It still shows the plates as they were when you took it. An error's stack works the same way: it shows where the error was made, not where you throw it.

In real life: A plate falls from the stack
In JavaScript: new Error() makes an error object
In real life: You take a photo right away
In JavaScript: V8 writes down the calls that are running
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 as it was
In JavaScript: error.stack still shows where the error was made

In the code below, top-level code means code that is not inside any function. Step through it and watch line 2 and line 8.

Step through: an error made in one place, thrown in another
Step 0 of 8Ready
Your turn: follow the blue line

Watch when the list of calls is written down. The error is made on line 2 and thrown on line 8, and its stack still shows line 2.

Running in
  1. script
Next: line 14
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")));
CallStoreChangeResultBlue line: runs next. 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 makes the error inside makeError. Line 8 throws the same object later, inside throwLater.

  • The first console.log prints true. The stack names makeError, because the list was written down there.
  • The second prints false. throwLater was not running when the list was written down, so it is not in it.
Left: on line 2, new Error() runs while makeError is on top of the call pile, above the top-level code. That pile is written down. Right: on line 8, throw error runs while throwLater is on top. Nothing new is written down. The error.stack window lists Error: plate fell, at makeError, at top level. throwLater is not in it.line 2: new Error()makeErrortop levelline 8: throw errorthrowLatertop levelthrowLater is not in the listwritten downerror.stackError: plate fellat makeErrorat top level
The list of calls is written down on line 2, when new Error() runs. Throwing the same error on line 8 does not change that list, so throwLater is not in it. (Real stack lines also show a file name and a line number.)
Writing the list down and turning it into text are two steps

V8 keeps the list of calls hidden inside the error. It turns the list into text only the first time some code reads error.stack. That is why your own function for the text, which you will meet below, can still change what that first read gives back.

This gives a useful habit: make the error at the place where things go wrong. If you make it much earlier, your logs will point at the function that made the error. They will not point at the place where your code decided to fail.

Hide a helper, and choose how many calls to keep

Error.captureStackTrace(target, hiddenFunction) writes a stack onto any object you give it, as a stack property. It takes two arguments:

  • target: the object that gets the stack property.
  • hiddenFunction (optional): a function to leave out. The list then starts just below this function. The function itself and every call above it are left out.

Error helpers use this to hide their own code, so the stack starts at the code that called them.

Leave a helper function out of the stackJavaScript
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 by line:

  • Line 2 makes a plain object, marker. Its name becomes the first line of its stack.
  • Line 3 writes a stack onto marker and leaves out publicApi.
  • Line 4 cuts the stack text into lines with split("\n"), keeps the first four with slice(0, 4), and removes spaces at each end with trim().
  • Line 12 prints function, so this engine has captureStackTrace.
  • Line 13 prints StackMarker, the first line.
  • Line 14 prints true: the first call in the list is userCode.
  • Line 15 prints false. some() asks "does any line name publicApi?" None does.
A pile of calls: publicApi on top, then userCode, then the top-level code. captureStackTrace(marker, publicApi) leaves publicApi out. The marker.stack window lists StackMarker, at userCode, at top level.captureStackTrace(marker, publicApi)left outpublicApiuserCodetop levelmarker.stackStackMarkerat userCodeat top level
The second argument, publicApi, says "start the list below this function". So the list starts at userCode, the code that called the helper. (Real stack lines also show a file name and a line number.)

captureStackTrace started in V8. MDN's browser support tables now list it in new Firefox and Safari too. Old browsers and small engines may not have it, so check that it exists before you call it.

Real-life analogyKeeping only the top ten plates

You take a photo of a stack of plates, but you keep only the top ten in the picture. You could keep fewer, more, or every plate. The stack of plates is the same. Only the number you keep in the photo changes.

In real life: A stack holds plates in order
In JavaScript: A stack trace lists calls in order
In real life: Keep only the top ten plates in the photo
In JavaScript: Error.stackTraceLimit sets how many calls are kept
In real life: Leave one plate out of the photo
In JavaScript: captureStackTrace(obj, fn) leaves helper calls out
In real life: Keep every plate in the photo
In JavaScript: Infinity asks V8 to keep every call

In the playground, deep(6) calls itself again and again until its number reaches 0: seven deep calls in a row. The last one makes the error. Pick a limit, press Run, and count the calls that are kept.

Playground: choose how many calls to keep
The code the playground runs with your 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;
Stack your browser gave backnot run
Limit you picked3

Press Run to get the stack lines from this browser.

No stack yet. Pick a limit, then press Run.

Step 0 of 4not run yet
Pick a value for `Error.stackTraceLimit`

Press Run to make an error in this browser. Nothing runs until you press it.

This code really runs in your browser. If your browser has no stackTraceLimit, you see what it gives back without a limit.

Error.stackTraceLimit is a number that V8 reads each time an error is made. It is 10 unless you change it.

  • 0 keeps no calls at all.
  • A whole number, like 3, keeps at most that many calls.
  • Infinity keeps every call.

The setting belongs to one page, or one iframe, in Chrome. V8 calls this a context. Changing it on one page does not change it on another.

Each row of this table is one stack tool. Read across to see what it does and what to be careful about.

Stack tools and when to use them
What you writeWhat it doesWhat to be careful about
new Error()In V8, writes down the list of calls at the moment the error is made.Make it where things go wrong, not long before, so the stack points at the right place.
error.stackHolds the list as text. It is not in the official rules yet, and each engine writes it a little differently.Good for logs. Don't pick it apart in code unless you know which engine runs it.
Error.captureStackTrace(obj, fn)Writes a stack onto any object, and can leave out fn and the calls above it.Great for error helpers. Check that it exists first if old browsers must work.
Error.stackTraceLimitA V8 number: how many calls an error keeps, on this page.Raise it only while you look for a bug, then put it back.
Error.prepareStackTraceA V8 setting: your function gets the error and a list of CallSite objects, and returns the stack.Useful in tools. Risky in a website, because it changes every stack on the page.

Make your own stack text with prepareStackTrace

Error.prepareStackTrace is a V8 setting that holds a function. V8 calls that function when it turns a list of calls into stack text. It passes in two things:

  • the error object;
  • an array of CallSite objects, newest call first. A CallSite describes one call in the list.

A CallSite has methods that answer questions about its call: the function name, the file name, the line, the column, whether the function was called with new, and more. Whatever your function returns becomes the value of error.stack.

Step through: your own function makes the stack
Step 0 of 8Ready
Your turn: follow the blue line

Watch V8 pass CallSite objects to your own function. This setting changes every stack on the page, and only V8 has it.

Running in
  1. script
Next: line 1
 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;
CallStoreChangeResultBlue line: runs next. 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.

This example returns an object instead of text. That shows V8 accepts any value your function returns.

  • Line 1 saves the old function in previous.
  • Line 3 puts in the new function.
  • Line 7 asks the newest CallSite for its function name. It is inspectStack, the function that made the error.
  • Line 8 asks for its line number.
  • Line 23 puts the old function back. Real tools must do this. The setting changes every stack on the page, not just this one.
V8 passes two things into your prepareStackTrace function: the error and the callSites list. The function returns an object. That object becomes the stack, and its functionName is inspectStack.V8 passes inerrorcallSitesprepareStackTraceyour functioninspectStackstackfunctionName
When error.stack is read, V8 calls your function with the error and the list of calls. Whatever your function returns becomes the stack. Here it is an object whose functionName is inspectStack.
CallSite methods you will see in tools

V8's documentation lists methods such as getFunctionName(), getFileName(), getLineNumber(), getColumnNumber(), isNative(), isConstructor(), and getTypeName(). They are useful in Node.js tools and test tools. They are not part of the JavaScript language, so other engines may not have them.

If you change this setting:

  • keep your function very small;
  • put the old function back inside finally, the block that runs whether or not an error is thrown;
  • don't change it in a website unless you control all the code on the page.

Logging tools and test runners are better places for it.

Source maps point the stack at the file you wrote

The JavaScript that runs on a real website is often not the code you wrote. A build tool changes it first. It may:

  • join many files into one (this is called bundling);
  • shorten names and remove spaces to make the file smaller (minifying);
  • turn TypeScript into JavaScript.

Then the stack trace names the built file, not yours. A source map is a file that links each place in the built file back to a place in the file you wrote. Browser DevTools use source maps for stack traces. Node.js does too, when you start it with --enable-source-maps.

A built file with a source map comment at the endJavaScript
// compiled-stack-demo.js, produced by a build toolfunction crash() {  throw new Error("mapped");}crash();//# sourceMappingURL=data:application/json;base64,...

Line 3 throws the error. Line 6 is a comment that tells tools where the source map is. Here the map is written right into the comment (the ... stands for the long map text).

Top: the built file, compiled-stack-demo.js, runs throw new Error mapped. An arrow labelled source map points down to the stack trace window, which lists Error: mapped, at crash (original-stack-demo.ts).compiled-stack-demo.jsthrow new Error("mapped")source mapstack traceError: mappedat crash (original-stack-demo.ts)
The built file is the code that runs. The source map only changes the place the stack trace names: it shows the file you wrote, original-stack-demo.ts. (Line and column numbers are left out here.)

The built file is still the code that runs. The map only changes the file names and line numbers that the stack trace shows. It is a feature of the tools, not a rule of the JavaScript language.

How the lesson test checks this

The lesson test writes a tiny source map right into the code, as text. It starts Node.js with --enable-source-maps, runs the code, and checks that the error output names original-stack-demo.ts. It does not check exact column numbers or full folder paths, because those are different on each computer.

Be careful when you put source maps on a public website. They are great for finding bugs. But anyone can download them, and a full map can show your original code, names, and comments. Your team should decide how much it is happy to share.

Which stack tools work everywhere?

TC39 is the group that writes the official rules of JavaScript. A new idea goes through stages, from Stage 0 to Stage 4. Stage 4 means it is finished and part of the language. Stage 3 means it is nearly finished.

Today, error.stack is not fully in the official rules. Every major engine has it, but each one behaves a little differently. There are two TC39 proposals about it:

An accessor is a property that runs a function when you read it (a getter) or write it (a setter). Object.getOwnPropertyDescriptor returns a small object that describes one property. For an accessor, its get and set fields hold those two functions.

In Node.js 22, stack is a getter and setter on each errorJavaScript
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);

In Node.js 22 (with V8 12.4), this prints:

  • Line 2: function. There is a getter.
  • Line 3: function. There is a setter.
  • Line 4: false. stack does not show up when you loop over the error's properties.
  • Line 5: true. The property can be changed or deleted.

Other engines are different. Safari has used a different shape in the past. Firefox has kept the getter on Error.prototype, not on each error. That is why TC39 is writing down only the shared part. It is not trying to make every engine's stack text the same.

Each row of this table is one stack tool. Read across to see where it works today and how to use it.

Which stack tools work in every engine?
What you writeWhere it works todayHow to use it
error.stackNot in the official rules yet. Browsers and Node.js all have it, but their text looks different.TC39 is writing down the getter and setter, not one text format for everyone.
Error.captureStackTraceChrome has had it for a long time. MDN's support tables also list Firefox 138+, Safari 17.2+, Node.js 0.10+, Deno, and Bun.Check that it exists before you call it, if your code can run in old or small engines.
Error.stackTraceLimitDescribed in the V8 and Node.js docs. It is not part of the JavaScript language.Use it only as a V8 setting while you look for a bug.
Error.prepareStackTrace and CallSiteDescribed in the V8 docs. Other engines may ignore it or work differently.Use it in Node.js tools, or in Chrome when you control the page. Don't rely on it in a website.
Sort each stack tool
  • `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

Put each card in a group: official JavaScript or a TC39 proposal; not official but in every major engine; V8 only; or a feature of a tool such as Node.js.

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

Use stack tools safely

Developers mostly need stack traces for three jobs:

  • logs, which record what went wrong on a live site;
  • test results, which show where a failing test broke;
  • developer tools, such as debuggers and error reporters.

In everyday website code, use simple custom error classes and clear messages. Use the V8 settings only when you control everything that runs, or when you are writing a tool for Node.js or Chrome.

  • Make a new error at the place where things go wrong, so its stack points there.
  • Use captureStackTrace to hide helper functions. Check that it exists first, for older engines.
  • Raise stackTraceLimit only while you look for a bug. Then put the old value back.
  • Put the old prepareStackTrace back inside finally. It changes every stack on the page.
  • Turn on source maps for your live site only if your team is happy for people to see the original code.

The lessons Debugging in the browser and Advanced debugging show how to use DevTools. This lesson explains what is behind the stack traces those tools show.

Common misconceptions

  • "The stack is written down when I throw." In V8, an error's stack is written down when the error is made.
  • "error.stack is in the official rules, like message." Every major engine has it, but the exact behavior and the text are still being written into the rules.
  • "Source maps change what runs." They only change the file names and line numbers that a stack trace shows.
  • "prepareStackTrace is safe to change in any website." It is a V8 setting that changes every stack on the page. Use it only in tools you control, and put the old one back.
  • "A longer stack is always better." Longer stacks use more memory. They can also show private details about how your code works.

Each row of this table is one pair of ideas that people mix up. Read across: the right idea, then what it is not.

Pairs of ideas that are easy to mix up
Which two ideas?What is trueWhat it does not mean
Made or thrownV8 writes down the list of calls when the error is made.Throwing the same error later does not write a new list.
Written down or turned into textV8 keeps the list hidden, and makes the text the first time stack is read.Making the text is a separate, later step.
Source map or what runsA source map changes the file, line, and column a stack shows.It does not change which JavaScript runs.
The proposal or V8's toolsThe Stage 3 proposal writes down how Error.prototype.stack works.It does not add prepareStackTrace, stackTraceLimit, or CallSite methods to the rules.

Practice exercises

Exercise 1 · Warm-upPredict what a reused error prints

Read the code and type the two values it prints.

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 function

    Which function name is the first call left in the stack?

    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 the calls that are kept

      With a limit of 2, how many call lines does the stack keep?

      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 · PracticePut the page-wide setting back

        This code is safe because it puts a page-wide setting back at the end. Name the setting it puts back.

        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 · PracticeSource maps in a real build

          In the lesson test, which file name does Node.js show once it uses the source map?

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

            Exercise 6 · ChallengeCheck a proposal's stage before you rely on it

            What TC39 stage is the Error stack accessor proposal at, according to its page?

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

              Quiz

              These questions check five moments: making an error, throwing it, turning the stack into text, mapping it to your files, and what is in the official rules. The lesson tests run each code question in Node.js to check the answer.

              Stack trace API quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In V8, when does an error write down its list of calls?

                Choose an answer to see the explanation.

              2. Question 2 of 7This error is made first and thrown later. What does the code 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 sentence about Error.captureStackTrace is true today?

                Choose an answer to see the explanation.

              4. Question 4 of 7With a limit of 1, what does this code print in Node.js?

                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 this custom prepareStackTrace make the stack?

                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 7What stage are the two TC39 stack proposals at?

                Choose an answer to see the explanation.

              Key takeaways

              • In V8, an error writes down its list of calls when it is made. It turns the list into text only when stack is first read.
              • Error.captureStackTrace writes a stack onto any object and can leave helper functions out. Error.stackTraceLimit sets how many calls V8 keeps.
              • Error.prepareStackTrace gets a list of V8 CallSite objects. It changes every stack on the page, and only V8 has it.
              • Source maps change the file names and line numbers a stack shows. They don't change what runs.
              • The Error stack accessor proposal is at Stage 3. It covers the stack property, not V8's own settings like prepareStackTrace.
              One-line summary

              A stack is a record of which calls were running: make errors at the right moment, change stack settings only in code you control, and use source maps to point stacks back at the files you wrote.

              Next, Async stack traces shows how engines rebuild the list of calls when code waits, with await, promises, and callbacks.

              CompleteFrontend Clear concepts. Working examples.