Framework guides
The JavaScript and PHP SDK pages cover the three steps every integration shares: install, configure a shared client, call a severity method. These guides pick up where those leave off — where the client is built in each framework, which key it uses, where you hook it into the framework's error handling, and where the queued reports actually get delivered.
Pick your framework
| PHP | JavaScript / TypeScript |
|---|---|
| Laravel | Node: Express, Fastify, Koa |
| Symfony | NestJS |
| Plain PHP — Slim, CodeIgniter, WordPress, CLI | Next.js |
| Nuxt | |
| SvelteKit | |
| Remix / React Router | |
| Vite SPA: React, Vue, Svelte | |
| Serverless & edge — Lambda, Vercel, Workers, Deno |
Using something not listed? For PHP, the plain PHP guide
applies to any framework without a dedicated integration. For JavaScript there is no
framework-specific package at all — import { createClient } from 'bugboard' is the entire
surface on every runtime, so the closest guide above will get you there.
What every integration has in common
1. Build one client and share it. The client owns a buffer (and in JavaScript, a drain timer and a shutdown hook). Building one per report gives you N buffers and N hooks. Use a container binding, a module-level export, or a singleton — the point is that there is one.
2. Pick the key that matches where the code runs. Servers use a secret key
(keyId + signingSecret, HMAC-signed, the secret never travels on the wire). Browsers use
a publishable key (apiKey, bb_pub_…, public by design and write-only). See
Choosing a key type.
3. Reporting never blocks and never throws. A reporting call validates, clamps and buffers the report, then returns. Delivery failures surface on the debug channel, never as an exception in your app.
4. Titles must be stable. Reports are deduplicated server-side by title, so
'Stripe webhook verification failed' is one card with a climbing occurrence count, while
"Webhook {$id} failed at " . now() is a new card every request. Put the variable parts in
the description or the tags. See
Deduplication.
The one thing that differs: where reports are delivered
Reports are buffered, not sent at call time. What drains the buffer is the only genuinely framework-specific concern, and it is what each guide below spends most of its words on.
| Runtime | What drains the buffer | Do you need an explicit flush()? |
|---|---|---|
| Laravel (any SAPI) | The terminating phase, after the response is sent |
No |
| Symfony | The SDK's shutdown hook — before the response under FPM | Yes — add a kernel.terminate listener |
| Plain PHP under FPM | The SDK's shutdown hook, on the request path | Recommended, after fastcgi_finish_request() |
| Long-running Node server | process.on('beforeExit') |
No |
| Browser | pagehide → flushSync() with keepalive |
No, but flush before a deliberate navigation |
| Serverless / edge | Nothing | Yes — always, before returning |
| Queue workers & daemons (any language) | Only when the process finally exits | Yes — flush per unit of work |
The failure mode is the same everywhere: reports are queued, the process goes away, nothing
was sent, and there is no error to tell you so. If reports are not appearing, this table is
the first place to look — followed by turning on debug.