# Framework guides

The **[JavaScript](/docs/sdks/javascript)** and **[PHP](/docs/sdks/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](/docs/frameworks/laravel)**                                         | **[Node: Express, Fastify, Koa](/docs/frameworks/node)**                             |
| **[Symfony](/docs/frameworks/symfony)**                                         | **[NestJS](/docs/frameworks/nestjs)**                                                |
| **[Plain PHP](/docs/frameworks/plain-php)** — Slim, CodeIgniter, WordPress, CLI | **[Next.js](/docs/frameworks/nextjs)**                                               |
|                                                                                 | **[Nuxt](/docs/frameworks/nuxt)**                                                    |
|                                                                                 | **[SvelteKit](/docs/frameworks/sveltekit)**                                          |
|                                                                                 | **[Remix / React Router](/docs/frameworks/remix)**                                   |
|                                                                                 | **[Vite SPA: React, Vue, Svelte](/docs/frameworks/vite-spa)**                        |
|                                                                                 | **[Serverless & edge](/docs/frameworks/serverless)** — Lambda, Vercel, Workers, Deno |

Using something not listed? For PHP, the **[plain PHP guide](/docs/frameworks/plain-php)**
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](/docs/installation/choosing-a-key)**.

**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](/docs/api-reference#deduplication-important-for-sdk-design)**.

## 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`.
