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']);
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 thrown —
getFile()/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.phprather 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_ROOToverrides it when trace paths are not paths on the reporting host (a container built at/build/srcand 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'ssodiumextension enabled on the host; check withphp -m | grep sodium. - Source on GitHub — the full SDK implementation.