Skip to content

API reference

<ErrorBoundary>

PropTypeDefaultDescription
resetKeysunknown[]When any value changes (compared with Object.is), the boundary auto-resets — same idea as react-error-boundary's resetKeys
resetOnPropsChangebooleanfalseReset whenever any prop reference changes, not just resetKeys
beforeReset() => voidCalled right before a reset (auto or manual). Not named onReset — see note below
isolatebooleantruefalse lets the error also propagate to the nearest ancestor <ErrorBoundary>
maxRetriesnumberunlimitedOnce reached, the fallback slot's canRetry becomes false
reporterErrorReporter | ErrorReporter[]Reporter(s) invoked once per captured error — see Reporting adapters
shouldCatch(error: CapturedError) => booleanReturn false to let an error pass through this boundary untouched — no state change, no report, no fallback — exactly as if it weren't there. See Ignoring specific errors
internalErrorPrefixstring'[vue-error-boundary-kit]'Prefix for the safety-net log emitted when your own beforeReset/reporter itself throws. Pass '' to omit it

Why beforeReset, not onReset? This component also emits a reset event, and Vue derives onReset as that event's own listener prop. A declared prop with the identical name would collide with it: Vue's emit() looks up props.onReset independently of whether it's "really" a declared prop, so it would've fired twice per reset — and the second, emit-triggered call happens outside this package's own try/catch, defeating the recursion guard entirely if it throws. beforeReset avoids the collision structurally.

Events:

  • error(error: CapturedError) — fired for every captured failure, including ones bubbled up from a descendant boundary with isolate: false.
  • reset() — fired on every manual or automatic reset.

Slots:

  • default — normal content.
  • fallback — scoped slot: { error, reset, retry, retryCount, canRetry }. reset() clears retry count too; retry() increments retryCount and re-attempts default without resetting the count.

Exposed (via a template ref): error, hasError, retryCount, canRetry, reset(), retry() — the same state and methods the fallback slot gets, but reachable from outside it (e.g. a retry control that lives elsewhere in the UI, or an ancestor recovering a boundary it doesn't render the fallback for). Calling reset()/retry() on a boundary that isn't in an error state is a harmless no-op.

vue
<script setup>
const boundary = ref()
</script>

<template>
  <ErrorBoundary ref="boundary">…</ErrorBoundary>
  <button @click="boundary?.reset()">Reset from elsewhere</button>
</template>

Retry with backoff

retry() is instant and unconditional — fine for most cases, but for a transient/network-ish failure, retrying the instant the button is clicked usually just fails again the same way. vue-error-boundary-kit/retry-backoff wraps a template ref's retry() with an increasing delay instead:

ts
import { createBackoffRetry } from 'vue-error-boundary-kit/retry-backoff'

const boundary = useTemplateRef('boundary')
const backoff = createBackoffRetry(boundary, { baseDelayMs: 1000, factor: 2, maxDelayMs: 30_000 })
vue
<ErrorBoundary ref="boundary">
  <template #fallback="{ error }">
    <button :disabled="backoff.isPending.value" @click="backoff.retry()">
      {{ backoff.isPending.value ? 'Retrying…' : 'Retry' }}
    </button>
  </template>
</ErrorBoundary>

The delay is computed from the boundary's own retryCount (baseDelayMs * factor ** retryCount, capped at maxDelayMs), so each successive attempt waits longer. cancel() clears a pending retry. Works with <AsyncBoundary>'s ref too — both expose the same { retry, retryCount } shape.

<AsyncBoundary>

Separate entry point (vue-error-boundary-kit/async-boundary) — not part of the core bundle, so it costs nothing if you don't import it. Combines <Suspense> and <ErrorBoundary>, which today you'd otherwise nest by hand:

ts
import { AsyncBoundary } from 'vue-error-boundary-kit/async-boundary'
vue
<AsyncBoundary :reset-keys="[userId]">
  <template #default>
    <UserProfile :id="userId" />
    <!-- async setup() / async components allowed -->
  </template>
  <template #loading>
    <Spinner />
  </template>
  <template #fallback="{ error, retry }">
    <ErrorState :message="error.message" @retry="retry" />
  </template>
</AsyncBoundary>

It's a composition, not a reimplementation: internally it's <ErrorBoundary> wrapping a <Suspense>, so it accepts every <ErrorBoundary> prop (resetKeys, maxRetries, reporter, shouldCatch, …), emits the same error/reset events, and exposes the same error/hasError/retryCount/canRetry/reset()/retry() via a template ref — all handled by the one onErrorCaptured implementation <ErrorBoundary> already has. The only thing it adds is the loading slot, rendered while the default slot's async dependencies are pending. retry()/reset() remount the default slot, so a retried async operation genuinely re-runs (the loading slot reappears while it does) rather than just re-showing stale state.

useErrorBoundary()

For programmatic use outside a template <ErrorBoundary> — e.g. a custom layout-level error state in Nuxt, or registering errors from code that errorCaptured never sees:

ts
const { error, hasError, reset, captureError } = useErrorBoundary({
  onError: (e) => report(e),
  reporter: myReporter,
})

try {
  JSON.parse(untrustedInput)
} catch (err) {
  captureError(err, { source: 'manual', componentName: 'ImportPanel' })
}
  • captureError(err, info?) — registers an error manually; returns the resulting CapturedError.
  • reset() — clears error back to null.
  • error: ShallowRef<CapturedError | null>, hasError: ComputedRef<boolean>.

Options: onError, beforeReset (called right before reset() clears state), reporter, reportContext, internalErrorPrefix (see the <ErrorBoundary> note above — same default, same reasoning, though there's no naming collision risk here since this is a plain composable, not a component with its own reset emit).

useGlobalErrorCapture()

Separate entry point (vue-error-boundary-kit/global-capture) — not wired up by default, so it costs nothing in bundles that don't import it (including SSR bundles, where it's a no-op if window isn't defined). Wires up window.addEventListener('error', …) and unhandledrejection, funneled through the same reporter/onError pattern:

ts
import { useGlobalErrorCapture } from 'vue-error-boundary-kit/global-capture'

useGlobalErrorCapture({
  reporter: myReporter,
  onError: (e) => console.warn('uncaught:', e),
})

Options: onError, reporter, reportContext, shouldCatch, internalErrorPrefix, captureErrors (default true), captureRejections (default true). Returns { stop }; cleanup also runs automatically if called inside an active effect scope (e.g. a component's setup()).

Types

ts
interface CapturedError {
  error: unknown
  message: string
  stack?: string
  componentName?: string
  lifecycleHook?: string
  source: 'render' | 'async' | 'event' | 'unhandledrejection' | 'manual'
  timestamp: number
}

interface ErrorReporter {
  report(error: CapturedError, context?: Record<string, unknown>): void | Promise<void>
}

source notes:

  • 'render' — a synchronous failure during a component's render or (sync) setup().
  • 'async' — an async setup() that rejects after an await. Vue reports both cases with the identical "setup function" info string, so this package additionally checks whether the component's own setup is an AsyncFunction to tell them apart.
  • 'event' — a Vue-compiled v-on handler (native DOM event or component emit) that throws. Note: Vue does route these through onErrorCaptured — what it genuinely can't see is covered below.
  • 'unhandledrejection' / and non-Vue 'event's — only produced by useGlobalErrorCapture().
  • 'manual' — the default for captureError() when no source is given.