All guides

Getting started

Capture errors from your server

Report crashes from API routes, cron jobs and background workers with glitchgrab/server

Steps checked September 12, 2026

The browser SDK only sees errors in an open tab. A cron job that fails at 3am, a webhook a payment provider rejects, or an email that times out never reaches it. glitchgrab/server sends those to the same repo, as GitHub issues, from any Node code.

Before you start

  • The glitchgrab package installed (see Add Glitchgrab to your Next.js app).
  • A gg_ token for the repo (see Create an API token). On the server it can be a normal env var — no NEXT_PUBLIC_.
GLITCHGRAB_TOKEN=gg_your_token

Report an error you caught

// app/api/cron/daily-digest/route.ts
import { reportServerError } from "glitchgrab/server";

export async function GET() {
  try {
    await sendDigest();
    return Response.json({ ok: true });
  } catch (error) {
    await reportServerError(error, { context: "cron/daily-digest" });
    throw error;
  }
}

The issue carries the error message, the stack, the Node version and the region.

Always await it. On Vercel and other serverless hosts your function can be frozen the moment it returns; an un-awaited report dies with it and never arrives.

Report a failure that isn't a thrown error

Often the useful failure is a value — an API that answers { ok: false }:

const result = await sendWhatsApp(payload);
if (!result.success) {
  await reportServerError(result.error, {
    context: "whatsapp/task-reminder",
    description: `Template ${payload.template} rejected`,
    severity: "high",
  });
}

severity must be low, medium, high or critical; it becomes a severity:<value> label on the issue.

Set the token once and catch everything

// instrumentation.ts
import { configureServerReporter, captureServerErrors } from "glitchgrab/server";

export function register() {
  configureServerReporter({
    token: process.env.GLITCHGRAB_TOKEN,
    metadata: { service: "web" },
  });

  // Optional: report every uncaught exception and unhandled rejection.
  captureServerErrors();
}

captureServerErrors() only watches — your process still crashes exactly as it would have. A reporter that kept a broken process alive would be worse than none.

Give every call site its own context

context decides which reports count as the same problem. Two different jobs both throwing "Timeout" stay two issues as long as their contexts differ (cron/daily-digest, cron/invoice-sync). Reuse a context and they merge.

How often you get an issue

  • One issue per error per 24 hours, and nothing new for 7 days while an issue for that error is still open. A job failing every hour files one issue, not twenty-four.
  • Nothing is sent in development unless you pass enableInDevelopment: true. This is on purpose: a server sends no browser origin, so this flag is the only thing stopping a local refactor from filing real issues.

Options

OptionWhat it does
contextThe grouping key, e.g. cron/daily-digest
descriptionWhat the job was doing
severitylow / medium / high / critical
typeReport type, default BUG
metadataYour own key-values
reporterWho triggered it, when there is a person (id, name, email)
token / baseUrlOverride GLITCHGRAB_TOKEN / GLITCHGRAB_BASE_URL
enableInDevelopmentSend while NODE_ENV is development

Troubleshooting

  • Nothing arrives — are you in development? Is GLITCHGRAB_TOKEN set on the server that runs the job, not just locally? Did you await the call?
  • reportServerError returned null — nothing was filed: no token, development mode, or the API refused it. It never throws.
  • Several different failures merged into one issue — they share a context. Give each call site its own.
Still stuck? Press ⌘⇧G on any Glitchgrab page to tell us — or see the SDK docs.