beyondcode/laravel-dump-server
Laravel Dump Server captures dump() output from your application and displays it in a dedicated console or browser, so you can inspect variables without interrupting requests or halting execution. No more dd() breaking your workflow.
Install. composer require --dev beyondcode/laravel-dump-server — nothing else. Auto-discovery registers BeyondCode\DumpServer\DumpServerServiceProvider, and there is no middleware, route, migration, or published config to add. Pin ^2.1 (Laravel 12 / PHP 8.4 compatible) rather than floating.
Run it. Open a second terminal, cd into the project, and start php artisan dump-server. Leave it running. That process is the whole "UI" — it binds a TCP listener (default 127.0.0.1:9912) and streams anything sent to it.
Then dump normally. dump() and dd() in your app now write to that terminal instead of the HTTP response or the CLI stdout.
Where to look first.
php artisan dump-server --help — confirms the exact host/port/option surface for the version you installed before you build muscle memory.vendor/beyondcode/laravel-dump-server/src/ — three files matter: DumpServerServiceProvider (where the VarDumper handler is swapped), DumpServer (the socket send/fallback logic), and DumpServerCommand (the server loop). Reading DumpServer answers the "what happens when the server isn't running?" question definitively.First use case: a JSON endpoint. Add dump($request->validated()) to a controller returning response()->json(...), run the request from Postman or your frontend, and watch the terminal. The response stays valid JSON, the client doesn't break, and you see the payload. That single loop — clean response, out-of-band output — is the reason to install this.
Sanity-check the failure mode once. Stop the server, call the same dump(), and observe what happens. Knowing whether dumps fall back to normal output or vanish saves you twenty minutes the first time you forget to start the server.
The two-terminal workflow. One pane runs dump-server, the other runs queue:work, artisan serve, tinker, or your test command. Most developers keep this in a tmux/iTerm split or a dedicated VS Code terminal group. The habit to build: start the server first, then work.
HTTP/API debugging. This is the primary use case. Dumps in controllers, middleware, form requests, and API resources disappear from the response body, so your SPA, Postman collection, or mobile client keeps working while you inspect values. Compare with plain dump(), which corrupts JSON and is invisible to the client anyway.
Blade views. @dump($var) or dump($var) inside a view goes to the terminal rather than injecting a giant var_dump block into the markup — especially useful when the rendered HTML is being consumed by something else (emails, PDFs, Inertia responses).
Queue workers and scheduled jobs. Run php artisan queue:work in one pane and dump-server in another; dump() inside a job's handle() appears immediately. This is the highest-value scenario the package has, because worker output is otherwise buried in Supervisor/systemd logs or lost entirely when the worker is managed by Horizon. Same for schedule:run and long-running Artisan commands.
php artisan tinker. Dumps leave the REPL output clean — the value appears in the server pane, and your tinker session scrollback stays readable. Nice for exploratory work on Eloquent queries.
Conditional dumps. Don't litter dump() calls. Gate them: app()->isLocal() && dump($x), a small dev_dump() helper in app/Support/helpers.php, or a request-header trigger (if ($request->header('X-Debug')) dump($payload);) so you can debug a staging-like environment on demand without a deploy.
Per-project ports. Dumps are global to the port, not per-application. Two projects sharing 9912 will interleave output from whichever app reaches it first — and only one process can bind. Assign a port per repo (via the command options or config/env, whatever your installed version exposes) and document it next to the start command.
Docker / Sail. 127.0.0.1 inside the app container is the container's loopback, not your laptop's. Run the server inside the same container as the app you're debugging (docker compose exec app php artisan dump-server) so both ends share a network namespace. If you must run it on the host, bind 0.0.0.0 and ensure the app's configured host points at the host gateway — and only on a trusted network.
Team convention. The package doesn't decide anything for you, so decide explicitly: dump-server for CLI/queue/API work, Telescope for request history and query tracing, Ray/Debugbar where the team already has them, plain dump() as the always-available fallback. Write it in the dev-setup doc; otherwise new hires run two dumpers and can't find their output.
dd() still dies. The package redirects output; it doesn't change semantics. dd() in an HTTP handler truncates the response and hangs the client until timeout; dd() inside a queue job kills the worker, which Supervisor restarts — potentially in a loop, re-running side effects. Prefer dump() and a deliberate exit only when you mean it.
Silence means the wrong port or no server. If nothing appears, check in this order: is the dump-server process still alive, is it on the port the app is configured for, is there a stale server from another project holding that port (lsof -i :9912 / ss -ltnp), and can the app process actually reach that host:port (Docker/network namespace is the usual culprit). Ninety percent of "it doesn't work" reports are one of these four.
Dumps from CLI commands don't print to that command's stdout. Running php artisan import:things while the server is up routes dump() to the server pane, not to the command's terminal. If you didn't start the server, you'll conclude your code path isn't executing. Know the fallback behavior of your version.
Octane/Swoole. Routing works, but long-lived workers mean stale config and, more importantly, dd() kills a worker process. Dumps themselves are safe; the request lifecycle disruption isn't.
Performance in loops. Every dump() is a socket write. Dumping inside a 10,000-row foreach will flood the terminal and measurably slow the run. Guard with a counter (if ($i < 5)), dump aggregates and counts instead of items, or gate behind a flag.
Truncated payloads. The VarDumper cloner applies depth and item limits — large Eloquent graphs and deeply nested arrays get elided. Dump targeted slices ($order->only('id','status'), $collection->take(3)->toArray()) rather than trying to fight the cloner.
Production safety, verified not assumed. It's a require-dev package excluded by composer install --no-dev, but confirm the image doesn't carry it and that the command can't run. An open TCP dump listener leaks request payloads, tokens, and env values to anyone who can reach the port. Don't bind 0.0.0.0 on shared or CI networks.
Tests and CI. Nothing here belongs in CI. Dumps in a test suite do socket work for no benefit; if connection attempts are blackholed rather than refused, they can introduce latency. Keep them out, or gate behind an env check.
Config and host/port parity. Server-side host/port and the app-side connection target must match. Whichever mechanism your version uses (command options, config file, env vars), verify with --help and set it in one place — a mismatch produces the "silent" failure described above.
Extension point. The package is a thin wrapper over Symfony\Component\VarDumper\Server\DumpServer and a VarDumper::setHandler() swap. If you want dumps going somewhere else in local — a rotating file, a Slack webhook for a specific debug flag — read DumpServer::send() and register your own handler in a local-only service provider. That's the clean seam; there's nothing else to extend.
Maintenance posture. Compatibility is reactive to Laravel/PHP majors, so treat it as a one-line bump per major plus a smoke test (one dump() in a queued job, confirm it lands). Pin the version, review the changelog on each framework upgrade, and keep the pre-agreed fallback — native dump()/Xdebug — recorded so an incident doesn't turn into a debate.
How can I help you explore Laravel packages today?