BugBoardDocs

PHP SDK

Report errors to BugBoard in three steps: install, configure a shared client, then call it.

Source on GitHub: github.com/bug-board/bugboard-php Requires PHP 8.2+ and any PSR-18 HTTP client (Guzzle recommended).

1. Install

composer require bugboard/sdk

2. Add your credentials

A server uses a secret key — a key id plus a signing secret. Every request is HMAC-signed and the secret never travels on the wire:

BUGBOARD_KEY_ID=bbk_xxxxxxxx
BUGBOARD_SIGNING_SECRET=bb_sec_xxxxxxxx

Get them from your project under Settings → API Keys (create a Secret key).

3. Get a client

How you build the client depends on your framework. Each of these has a detailed guide — start here, then follow the link for error handlers, queue workers, testing and delivery timing.

Laravel

Nothing to wire up: the package is auto-discovered, binds a shared client, and delivers reports after the response has been sent. The .env values above are the whole setup.

use BugBoard\Laravel\Facades\BugBoard;

BugBoard::critical('Payment failed', $e, ['payments', 'checkout']);

Full Laravel guide

Symfony

Enable the bundle, point it at the env vars, and autowire BugBoard\Client anywhere:

# config/packages/bugboard.yaml
bugboard:
    key_id: '%env(BUGBOARD_KEY_ID)%'
    signing_secret: '%env(BUGBOARD_SIGNING_SECRET)%'
    environment: '%kernel.environment%'

Full Symfony guide — including the kernel.terminate listener that keeps reporting off the request path.

Any other framework

Build the client once and hold onto it — reports buffer during the request and are delivered by a shutdown hook (or call flush() yourself):

use BugBoard\ClientBuilder;
use BugBoard\Config;

$bugboard = ClientBuilder::create(new Config(
    keyId: getenv('BUGBOARD_KEY_ID') ?: null,                 // bbk_…
    signingSecret: getenv('BUGBOARD_SIGNING_SECRET') ?: null, // bb_sec_…
    environment: 'production',

    // OPTIONAL — if provided, every payload is encrypted before it leaves the server.
    // encryptionKeyId: getenv('BUGBOARD_ENCRYPTION_KEY_ID') ?: null,         // bbek_…
    // encryptionPublicKey: getenv('BUGBOARD_ENCRYPTION_PUBLIC_KEY') ?: null, // base64 X25519

    // OPTIONAL — log reports locally instead of sending them (local debugging / dry run).
    // logLocally: true,
));

$bugboard->minor('Tooltip misaligned', null, 'ui,polish');

If your configuration already lives in an array, ClientBuilder::createFromArray() takes snake_case or camelCase keys — it is what the Laravel and Symfony integrations use internally.

Full plain-PHP guide — Slim, CodeIgniter, WordPress, CLI tools and workers.

4. Use it

Call a severity method with a title (required); optionally pass a description — a string or the caught Throwable — and tags.

try {
    $payments->charge($order);
} catch (\Throwable $e) {
    $bugboard->criticalHigh('Payment failed', $e, ['payment', 'backend']);
}

The title is all you need:

$bugboard->major('Checkout is slow');

That's it — the report is buffered and delivered in the background, and your request keeps going.

Common methods

Most apps only need these four (medium priority); each severity also has …Low and …High variants — criticalHigh, minorLow, and so on:

$bugboard->critical('Payment failed', $e); // highest severity
$bugboard->major('Checkout is slow', $e);
$bugboard->moderate('Slow image upload', $e);
$bugboard->minor('Tooltip misaligned');

Every method takes (string $title, string|\Throwable|null $description = null, array|string $tags = []). Tags are an array or a comma-separated string:

$bugboard->critical('Payment failed', $e, ['payments', 'checkout']);
$bugboard->critical('Payment failed', $e, 'payments,checkout');

$bugboard->droppedCount() reports how many reports the buffer discarded — useful as a health metric if you sample heavily or report in tight loops.

Where cards point

Pass the caught Throwable as the description and the card names where it was throwngetFile()/getLine(), recorded by PHP when the exception was constructed — rather than the line your reporting call sits on:

// app/Services/Charge.php:17
throw new RuntimeException('card declined');

// bootstrap/app.php:34 — reported here, but the card says /app/Services/Charge.php:17
BugBoard::criticalHigh($e->getMessage(), $e);

This is what makes one global handler workable. $exceptions->report(), a Symfony exception listener, and a bare set_exception_handler() are all your own code, so without this every unhandled exception in the application would report the same file — and since file_name is part of how cards are deduplicated, unrelated bugs would merge onto one card. Where a framework wraps an exception (a PDO failure inside a QueryException), the getPrevious() chain is followed to the root cause. Reports that carry no throwable point at the call site instead.

Two normalizations are applied before sending:

  • The project root is trimmed, so cards read /app/Http/Controllers/PayController.php rather than /var/www/releases/20260801143022/app/…. Otherwise every deploy reports the same call site under a new path and one card's history splits. The root is detected from Composer, so this needs no setup; BUGBOARD_PROJECT_ROOT overrides it when trace paths are not paths on the reporting host (a container built at /build/src and run elsewhere).
  • Compiled Blade views map back to the template. A report from inside a view arrives as /storage/framework/views/8b1e0f2a….php; BugBoard shows /resources/views/orders/show.blade.php. The line number is omitted for these, because the only line available counts lines in the generated PHP.

Unlike JavaScript, PHP runs the file you wrote — there is no bundling to undo, so no build plugin or source map is involved and the line is exact everywhere else. Turn the whole thing off with BUGBOARD_CAPTURE_LOCATION=false.

Keep titles stable. Reports are deduplicated server-side by title, so 'Stripe webhook verification failed' becomes one card with a climbing occurrence count, while "Webhook {$id} failed" becomes a new card every time. Put the variable parts in the description or the tags. See Deduplication.

Next steps

  • Framework guides — Laravel, Symfony and plain PHP in detail: exception handlers, queue workers, Monolog, config caching, testing and troubleshooting.
  • API Reference — config options, every severity/priority variant (criticalHigh, …), and the full HTTP contract.
  • Encrypting sensitive reports — seal the payload in transit with encryptionPublicKey. Needs PHP's sodium extension enabled on the host; check with php -m | grep sodium.
  • Source on GitHub — the full SDK implementation.