treblle/treblle-laravel
Treblle for Laravel is a drop-in SDK that adds real-time API observability: request and response logging, performance and error monitoring, auto-generated documentation, and actionable insights with minimal setup.
Treblle is an API observability layer: the Laravel package registers a middleware that captures each API request/response pair and ships it to Treblle's ingest API, where you get request logging, error tracking, latency/performance analytics, and auto-generated API docs.
Minimal setup:
composer require treblle/treblle-laravel
php artisan vendor:publish --tag=treblle-config
Then add credentials to .env:
TREBLLE_API_KEY=your_key
TREBLLE_PROJECT_ID=your_project_id
Register the middleware on your API stack. In Laravel 11/12 (bootstrap/app.php):
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup('api', \Treblle\Middlewares\TreblleMiddleware::class);
})
On older skeletons, add it to the api array in app/Http/Kernel.php.
Where to look first: config/treblle.php — that's where environment gating, masking, and route exclusions live. Read it before you touch anything else.
First use case: point it at one non-production-but-real environment (staging), hit a few API endpoints, and confirm payloads land in the Treblle dashboard. That validates credentials, middleware placement, and outbound network access in one pass before you consider production.
Environment gating. Don't rely on remembering to remove the middleware locally. Use the config's environment allow/deny list so Treblle only reports from production/staging, and keep the API key out of local .env files entirely. This is the single highest-leverage config decision — most "why is Treblle noisy/slow in dev" complaints trace back to skipping it.
Runtime masking with the facade. Static config covers known-sensitive keys, but per-endpoint masking is often cleaner inside the controller or a form request:
use Treblle\Laravel\Facades\Treblle;
Treblle::mask(['credit_card', 'ssn']); // replaced with asterisks
Treblle::hide(['internal_notes']); // removed from payload entirely
Use hide for fields that shouldn't exist off-box at all, mask for values you want structurally visible but redacted. Wire this into a base controller or a middleware that runs before Treblle so you don't repeat it per action.
Route exclusions as a scaling tool. Health checks, metrics scrapes, webhooks from high-volume providers, and file-download endpoints should be excluded via the config's URL patterns. Treblle on a /up health check firing every 10 seconds will drown your real traffic in the dashboard and waste outbound bandwidth.
Treat it as a middleware-ordering concern. Place Treblle after authentication/authorization middleware so you capture the authenticated user context and the real status codes your clients see, but before anything that transforms the response shape. If it runs too early, error responses thrown deeper in the stack won't be captured.
Pair it with your existing error handling. Treblle captures the exception payload, but it is not a replacement for your log aggregation or Sentry-style alerting. The useful pattern is: exceptions page you, Treblle tells you which endpoints and clients are affected and what the request looked like.
Guzzle 8 (v6.1.8). The package now allows Guzzle 8. If your app (or a freshly-created Laravel 12 project) resolves Guzzle 8, this release is what unblocks composer update — previously you'd hit a constraint conflict and be forced to pin Guzzle 7 or drop Treblle. If you're on an older lockfile nothing changes for you; if you were blocked, upgrade and re-run your test suite once to confirm the outbound reporting path still works.
Middleware registration is the #1 failure mode. On Laravel 11+, forgetting appendToGroup/prependToGroup in bootstrap/app.php (rather than editing a Kernel.php that no longer exists) results in silent no-ops — the package installs, config publishes, and nothing ever appears in the dashboard.
Silent failures by design. Reporting is best-effort; if the outbound call to Treblle fails (DNS, firewall, bad key), your API keeps serving and you see nothing. When data isn't showing up, check in this order: middleware registration → env credentials → egress firewall/proxy → config environment gating. Enable the package's debug mode in a staging environment to surface what it's actually attempting to send, then turn it off.
Payload size and file uploads. Large request bodies and multipart uploads are poor fits for this kind of capture — expect truncated or unhelpful bodies, and consider excluding upload endpoints from monitoring entirely rather than debugging why the recorded payload is partial.
Masking is allowlist-by-default only for well-known keys. Default masking covers common fields like passwords and tokens, but anything domain-specific (account numbers, internal IDs, PII in nested arrays) is on you. Audit one real production payload in the dashboard before trusting the redaction, and remember nested keys need to be reachable by the masking logic.
Don't let it become a latency source. The reporting call happens in the request lifecycle. For latency-sensitive endpoints, verify response times with and without the middleware, and exclude streaming/SSE endpoints where the response body isn't a discrete payload.
Long-running workers and Octane. Facade-based masking mutates package state. Under Octane or queue workers, make sure runtime masking is applied per request rather than accumulating — reset or re-apply explicitly if you see masking bleeding between requests.
Extension points. The config's exclusion and masking arrays are the supported surface. Beyond that, wrapping the facade calls in your own small service (e.g. ApiObservability::redact()) keeps you from sprinkling package-specific calls throughout controllers and gives you one place to swap providers later.
How can I help you explore Laravel packages today?