
Vue 3 Global Error Handler: What app.config.errorHandler Catches, and What It Misses
- vuetelemetry
- Guides
- 8 min read
app.config.errorHandler takes three lines to install. The real work is knowing the seven sources it covers, why its third argument turns into a code in production, how onErrorCaptured can silence it, and which errors only the browser will ever tell you about.
A component throws inside a watcher on a customer's phone. In development the same failure would have filled the screen with a stack trace. In a production build, by default, Vue writes the error to a console nobody is looking at, and the app carries on in whatever state the failure left it. A global error handler is the piece that turns that silent console line into something you can receive, count and fix.
Vue 3 provides one for the whole application: app.config.errorHandler. The official API reference describes it as a global handler for uncaught errors propagating from within the application. Installing it takes three lines. The real work is knowing what it covers, what it leaves out, and what its arguments contain once the code is built for production.
What app.config.errorHandler catches

The handler is a property of the application instance: app.config.errorHandler = (err, instance, info) => { ... }. It goes between createApp() and app.mount(). The Vue guide is explicit on the order: make sure to apply all app configurations before mounting the app. The reason is easy to see here, since the first render and the setup() of the root component both run at mount time.
The reference lists seven sources the handler can capture errors from: component renders, event handlers, lifecycle hooks, the setup() function, watchers, custom directive hooks and transition hooks. Read that list as a contract. It describes code that Vue calls on your behalf. Code that runs with no Vue call above it on the stack is a different matter, and it gets its own section below.
Three arguments, and the production catch
The handler receives three arguments. The first is the error, typed unknown in the TypeScript signature, because JavaScript lets code throw anything: a string, a plain object, undefined. Check that it is an Error instance before reading its message or its stack. The second is the component instance that triggered the error, or null. The third, info, is a Vue-specific string naming the type of source, for example which lifecycle hook the error was thrown in.
- app.config.errorHandler receives (err, instance, info) and is set before app.mount()
- Seven documented sources: component renders, event handlers, lifecycle hooks, setup(), watchers, custom directive hooks, transition hooks
- In production builds, info is a short code; the Production Error Code Reference maps it back to text
- Default behaviour: re-throw in development, console log in production; throwUnhandledErrorInProduction (3.5+) changes the second
- An onErrorCaptured() hook that returns false stops the error before it reaches the global handler
- Outside Vue: the window error event for synchronous script errors, unhandledrejection for promises
That third argument holds a surprise for anyone opening a production report for the first time. In production builds, info is a shortened code instead of the full string. The Vue documentation keeps a Production Error Code Reference that maps each runtime code back to its original text. Send the raw value as it arrives and do the lookup when reading the report. Logic that compares info with a string seen in development will stop matching once the app is built.
Development and production do not behave alike
Without a handler, what Vue does depends on the build. According to the reference, the default error handler re-throws errors during development and logs them during production. The development half is deliberate. The documentation says the error is thrown, and can possibly crash the application, to make it more prominent so that it is noticed and fixed.
The production half has a side effect that matters for monitoring. An error that is only logged to the console is not thrown, and the documentation itself notes that this may prevent errors that only happen in production from being caught by error monitoring services. Since Vue 3.5 there is a switch for that case: app.config.throwUnhandledErrorInProduction, a boolean that defaults to false. Set to true, unhandled errors are thrown in production mode as well.
onErrorCaptured: local boundaries before the global handler
The global handler is the last stop. Before it, any component can intercept errors coming from its descendants with onErrorCaptured(), or errorCaptured in the Options API. The hook receives the same three arguments and is the tool for an error boundary: change some state, render a fallback. The reference attaches one warning. The error state must not render the original content that caused the error, otherwise the component is thrown into an infinite render loop.
Propagation follows four documented rules. By default, captured errors are still sent to app.config.errorHandler if it is defined, so they can be reported in a single place. If several errorCaptured hooks sit on the parent chain, all of them are invoked, from bottom to top, much like the bubbling of DOM events. If a hook itself throws, both that error and the original one go to the global handler. And a hook can return false to stop the error there: no further hook and no global handler is invoked for it. That last rule is the usual explanation for an error that shows a fallback on screen and never turns up in a report.
What the global handler never sees
Now the part outside the contract. A callback passed to setTimeout, a listener attached by hand with addEventListener, a promise chain started in a utility module, a third-party script: none of these is one of the seven sources. The browser has two events for them. According to MDN, the error event fires on window when a resource failed to load or could not be used, for example when a script has an execution error, and it is only generated for script errors thrown synchronously. A promise rejected with no rejection handler, which includes an uncaught throw inside an async function, fires unhandledrejection instead.
A complete setup therefore has three listeners feeding one function: app.config.errorHandler for the code Vue calls, an error listener on window for synchronous failures elsewhere, and an unhandledrejection listener for the promises nobody caught. Leave one out and a whole class of failures stays invisible.
Reporting without making things worse
What the function does with an error decides whether the handler helps. Keep it small and defensive: normalise the error, attach info, the current route and the release version, then send it to your endpoint. Wrap the body in try and catch, since a reporting function that throws only adds a second failure to the first. Deduplicate before sending, because a render error inside a list can repeat for every row. And keep a console.error call in the handler while developing: once your own handler is installed, Vue's default one, the one that re-throws in development, no longer runs.
Errors answer one question: what broke. They do not say how long the user waited before it broke, or which request was in flight at the time. That is the territory of traces, covered in OpenTelemetry in a Vue app. Start with the handler all the same. It is a few lines of code with no dependency, and it reports the problems you did not know to look for.
FAQ
Where do you set the global error handler in Vue 3?
On the application instance, between createApp() and app.mount(): app.config.errorHandler = (err, instance, info) => { ... }. The Vue guide asks for all app configurations to be applied before mounting the app.
Does app.config.errorHandler catch every error on the page?
No. The Vue reference lists seven sources: component renders, event handlers, lifecycle hooks, setup(), watchers, custom directive hooks and transition hooks. Errors raised outside them are reported by the browser, through the error event on window for synchronous script errors and through unhandledrejection for promises rejected with no handler.
Why is the info argument a code instead of a readable string in production?
In production builds Vue passes a shortened code as the third argument of app.config.errorHandler and onErrorCaptured. The Production Error Code Reference in the Vue documentation maps each runtime code to its original information string.
What is the difference between onErrorCaptured and app.config.errorHandler?
onErrorCaptured is a component hook that captures errors from descendant components, which makes it the tool for a local fallback. app.config.errorHandler is application-wide and still receives those errors by default, unless a hook returns false to stop the propagation.



A complete setup therefore has three listeners feeding one function: app.config.errorHandler for the code Vue calls, an error listener on window for synchronous failures elsewhere, and an unhandledrejection listener for the promises nobody caught. Leave one out and a whole class of failures stays invisible.