shibbirweb/peep
Peep is a lightweight Laravel package from shibbirweb that gives developers a quick, simple way to peek into their application during development. Easy to install and use, it helps surface useful information for local debugging and inspection.
composer require shibbirweb/peep --dev. The Laravel adapter is auto-discovered (Laravel 5.5 → 13), registers its watchers, and is disabled by default in production.config/peep.php and turn it on, or set PEEP_ENABLED=true in .env. It stays off unless enabled — nothing is sent anywhere until you flip it.vendor/bin/peep-server serve --open=browser (viewer package requires PHP 8.5; your app can stay on 7.1+).peep://open for serve --open=window.peep($user); // value tree with visibility/redaction
peep()->label('checkout')->large();
User::with('orders')->get()->peep();
Cards appear grouped under the current request/job/command, with the file, line and function that called them.docs/wiki (getting started, Laravel, query debugging, mail/SMS, webhooks, troubleshooting). docs/protocol.md is the reference if you want to build your own client.Daily-driver first use case: you have a slow endpoint and a fat query log — open the viewer, hit the toolbar's slow switch (> 100ms), and the running app starts filtering queries within seconds without a redeploy.
Debugging a request without dd()
->peep() hangs off collections, strings, query builders and test responses; it returns the receiver, so it drops into existing chains.peep()->showQueries() turns on query cards for the current request; the viewer's toolbar switches (all / slow / duplicates) are pushed to running apps through GET /_peep/switches, so you can change the lens mid-session.? soup.Blade and views
@peep($var), @peepAll($loop->all()), and @peepMeasure('render') … @endPeepMeasure for timing a block without editing PHP.Mail and notifications
MAIL_MAILER=peep and every mailable lands in the inbox instead of SMTP: HTML at desktop and mobile width, text, source, headers, attachment download, unread counts, and a link back to the request that sent it.peep()->mailable($mailable) and peep()->markdown($mailable) preview a mailable before sending — the fastest way to check copy and layout.23525, a Mailgun-compatible API and /_peep/mail are there if you can't change the mailer. peep()->sms() covers SMS; Twilio/Vonage API-compatible endpoints answer like the real ones, and Laravel SMS notifications can optionally block the real send.Webhooks you can actually test against
{{ mail.message_id }}, {{ now.unix_float }}, hmac_sha256(...)), Mailgun/Twilio/Vonage presets and signatures the providers' own SDK checks accept, retries, a delivery log with resend, and import/export of rule sets. This replaces ngrok + a prayer for signature verification work.Test suite integration
->peep() on test responses makes a failing assertion inspectable after the fact.peep()->count()/counterValue(), measure(), once(), limit(), rateLimiter(), if()/showIf() keep noisy loops from flooding the viewer.Non-Laravel and multi-app setups
PeepLogger, PeepHandlerFactory::create() for Monolog (1→3), DumpHandler for VarDumper (3.4→8.x), opt-in casters via serializer: var-dumper, and an error handler for warnings, uncaught exceptions and fatals.peep.phar install uses auto_prepend_file to load Peep into every script of a PHP install; projects with Composer keep their own version. uninstall and status to reverse/inspect.Shipping
vendor/bin/peep-clean (and php artisan peep:clean) lists peep(), pd(), ->peep() and @peep calls and removes the safe ones with --fix; add phpstan/rules.neon to CI so a leftover peep() fails the build instead of reaching production.composer.json and expect payload/API churn. Protocol v1 is specified, so viewer-and-client drift should be detectable rather than silent, but the PHP API surface is not frozen.peep-server needs PHP 8.5. If your dev box can't run 8.5, use the desktop app (bundled PHP) instead of fighting the CLI viewer.PEEP_ENABLED / peep.php) are you in production (disabled by default), is the viewer actually listening, is the app pointed at the right host/port. A closed viewer is intentionally silent — there's no exception to grep for, which reads as "the package is broken" when it's working as designed.PEEP_TOKEN in the app (token in config/peep.php), sent as X-Peep-Token; only then does the viewer bind beyond loopback, and its UI page stays local. Also set path mappings in Settings so origin links open the right file locally instead of a container path.peep() returns the Peep instance, not your value. Don't inline it where the value is consumed — assign first, then peep($value). pd() dies; send() is the non-dying send.pdo_sqlite. Without it, cards vanish on viewer restart. Cards-per-project, history and notifications live in settings.json on the viewer, not in the app — serve reads them when no CLI option is given, so a stale settings file can quietly override your flags.peep-clean is conservative about which calls it deems "safe"; review the --fix diff, and rely on the PHPStan rule for the stragglers. The serializer already redacts sensitive keys and reads private properties without side effects, but a peep($request->all()) in a controller is still a habit worth killing.sendCustom() for your own payload types, serializer: var-dumper casters, and the protocol spec if you want a card renderer the bundled viewer doesn't have.--exit-with=<pid> when launching the viewer from a test runner or dev script so it shuts down with the process rather than accumulating orphaned servers on port contention.How can I help you explore Laravel packages today?