dumpio/client is the official PHP package for sending dumps to the Dumpio app. It's the richest way to use Dumpio from PHP: instead of hand-writing JSON, you call a helper like dio($user) and the package builds the whole message for you — types, colors, labels, the file and line it was called from, even HTML and email previews.
This page is written to get you productive even if you've never used a tool like this. If you use Laravel or Symfony, read this page first, then see the dedicated PHP + Laravel and PHP + Symfony pages for the framework parts.
What is a "dump"?
A dump is a snapshot of a value in your running program, sent to the Dumpio window so you can look at it. It's the same idea as PHP's var_dump() or Laravel's dd() — except the data travels over the network to a separate app instead of being printed into your page or API response.
Why that's better for debugging:
- It doesn't break your output. You can inspect a variable in the middle of a JSON API, a queued job, or a Livewire component without corrupting the response.
- It keeps the types. You see that
42is anintand"paid"is astring, and for objects you get the class name and which properties arepublic/protected/private. - It never crashes your app. Every call is wrapped in a
try/catch. If the Dumpio app isn't running, the dump is quietly dropped — your code runs exactly as before.
Before you start
- The Dumpio app is running with its default server (HTTP on
127.0.0.1:21234). See Installation. - PHP 8.0 or newer.
Install
1composer require dumpio/client
That's it — no configuration needed while the Dumpio app is on your machine using the default port.
Tip: install it as a dev dependency (
composer require --dev dumpio/client) if you only debug locally. The package is a safe no-op in production either way (see Production safety).
Your first dump
Put this anywhere in your code — a controller, a script, a test — and load the page or run the script:
1use Dumpio\Dumpio;2 3$user = ['id' => 7, 'name' => 'Ada', 'roles' => ['admin', 'billing']];4 5Dumpio::dump($user);
Switch to the Dumpio window and you'll see a new entry at the top. Click it and the value is shown as an expandable, typed tree:
Nothing showing up? Jump to Troubleshooting at the bottom of this page.
The four ways to send a value
There are four entry points. They do the same core thing — send a value — but differ in what they return and whether they stop your script. Pick whichever fits the line you're editing.
1use Dumpio\Dumpio; 2 3// 1) The plain call 4Dumpio::dump($user); 5 6// 2) dio() — the fluent builder (recommended). Lets you chain color/label/etc. 7dio($user)->green()->label('current user'); 8 9// 3) dumpio() — "tap". Sends the value AND returns it unchanged.10$total = dumpio($cart->total(), 'total'); // $total still holds the number11 12// 4) ddio() — "dump and die". Sends every argument, then stops the script.13ddio($request->all(), $user);
| I want to… | Use |
|---|---|
| Quickly show a value | Dumpio::dump($x) or dio($x) |
| Add a color / label / channel | dio($x)->red()->label('…')->channel('…') |
| Inspect a value without breaking a chain or expression | dumpio($x) (it returns $x) |
| Stop the script right here and look | ddio($x) |
| Send something structured (an exception, a query, an email…) | the typed helpers below |
dio(), dumpio(), and ddio() are plain global functions — they're autoloaded by the package, so you can call them from anywhere without a use statement.
The "tap" trick
Because dumpio() returns its argument, you can wrap any expression with it and see the value without changing what your code does:
1return dumpio($service->build($request), 'built'); // dumps, then returns the value
Colors, labels, and channels
These three things help you find the dump you care about in a busy list.
- Flag (color): one of
red,yellow,blue,green,purple,pink,gray. Use it however you like — e.g. red for problems, green for success. In the viewer you can filter by color. - Label: the title shown in the list. Without one, the list shows a generic title.
- Channel: a group name shown as
#name. Handy for separating, say,#checkoutdumps from#authdumps. You can filter by channel too.
1dio($order)2 ->green() // color3 ->label('new order') // title in the list4 ->channel('checkout'); // group
The fluent builder
dio() (and Dumpio::make()) return a builder you can chain on. The dump is sent automatically when the builder is done — either when you call ->send() or when it goes out of scope — so ->send() is optional.
1dio($cart)2 ->purple() // or ->flag('purple')3 ->label('cart')4 ->channel('checkout')5 ->send(); // optional
- Colors:
red(),yellow(),blue(),gray(),purple(),pink(),green()(orflag('…')). - Text:
label('…'),channel('…'). - Conditions:
when($bool)sends only if true,unless($bool)is the opposite.
1dio($internalState)->when($debugMode); // sent only when $debugMode is true
Debugging inside loops (flood control)
A dump inside a loop can send hundreds of entries and drown the viewer. These modifiers keep that under control. They're keyed to the exact line in your code (per process), so each call site counts on its own:
1foreach ($rows as $row) {2 dio($row)->once(); // send only the FIRST iteration3 dio($row)->limit(5); // send at most 54 dio($row)->count(); // send ONE entry that shows "×N" and counts up5}
count() is the friendliest for loops: instead of N rows you get a single live-updating entry with a ×N badge. Pass a name to share one counter across several places: dio($x)->count('processed rows').
Timing a block
A stopwatch measures how long something takes and sends it as a measure dump (with elapsed milliseconds and the memory change):
1$sw = Dumpio::stopwatch('import');2// … first part …3$sw->lap('parsed'); // a split time; the stopwatch keeps running4// … second part …5$sw->stop('done'); // the final time
Interactive breakpoint
Dumpio::pause() stops execution and blocks the request until you click
Continue or Stop in the viewer — a breakpoint without a debugger.
1Dumpio::pause('before charging the card');
Stop ends the script (exit(1)); Continue lets it proceed. It's a safe
no-op when Dumpio is disabled or the app isn't running, works on loopback servers
only (a network-exposed server continues immediately), and a viewer-side timeout
auto-continues so a forgotten breakpoint never hangs forever.
Update in place
Dumpio::update($id, $value) sends a keyed dump: send again with the same
$id and the viewer replaces the existing entry instead of adding a row —
perfect for a live counter or a value that changes across a loop. Dumpio::remove($id)
drops it.
1foreach ($rows as $i => $row) {2 Dumpio::update('import', ['done' => $i + 1, 'total' => count($rows)]);3 // …4}5Dumpio::remove('import');
Typed helpers
So far we've dumped plain values. Dumpio also has typed helpers for common debugging targets — exceptions, SQL queries, HTTP calls, and more. Each one gets its own nicely formatted view in the app (see Dump types) and picks a sensible default color. They're all static methods on Dumpio and, like everything else, never throw.
| Method | What it's for | Default color |
|---|---|---|
Dumpio::exception(\Throwable $e, $context = [], $opts = []) |
An exception, with a parsed stack trace | red |
Dumpio::query($sql, $bindings = [], $timeMs = null, $opts = []) |
A database query (with its parameters and time) | purple |
Dumpio::http($method, $url, $status = null, $opts = []) |
An HTTP request/response (headers, body, responseTime) |
by status |
Dumpio::log($level, $message, $details = [], $opts = []) |
A log line | by level |
Dumpio::model($class, $attributes, $opts = []) |
One ORM record (relations, exists, connection) |
– |
Dumpio::collection($items, $opts = []) |
A list of items (message) |
– |
Dumpio::table($columns, $rows, $opts = []) |
An explicit table | – |
Dumpio::measure($name, $timeMs, $opts = []) |
One timing (memory, context) |
– |
Dumpio::performance($metrics, $opts = []) |
A bundle of metrics (breakdown, context) |
– |
Dumpio::event($event, $opts = []) |
A business/domain event (entity, actor, data, …) |
– |
Dumpio::trace(?$label = null, $opts = []) |
The current call stack ("how did we get here?") | gray |
Dumpio::memory(?$label = null, $opts = []) |
A memory snapshot (current/peak/limit) | blue |
Dumpio::html($html, $opts = []) |
Preview an HTML fragment in a safe sandbox | – |
Dumpio::mail($mail, $opts = []) |
Preview an email (html, text, subject, to, …) |
– |
Dumpio::mailable($mailable, $opts = []) |
Render and preview a Laravel Mailable | – |
Automatic colors where they help: HTTP status → null/blue, ≥500 red, ≥400 yellow, ≥300 blue, else green; log level → error red, warning yellow, info blue, else gray.
Examples
1// An exception with useful context — call it in a catch block 2try { 3 $gateway->charge($order); 4} catch (\Throwable $e) { 5 Dumpio::exception($e, ['order_id' => $order->id, 'user' => $user->email]); 6 throw $e; // re-throw; you only wanted to see it 7} 8 9// A database query with its bindings and time (ms)10Dumpio::query('select * from orders where status = ?', ['paid'], 12.4);11 12// An outgoing HTTP call — the color comes from the status13Dumpio::http('POST', 'https://api.stripe.com/v1/charges', 402, [14 'responseTime' => 240,15 'body' => ['error' => 'card_declined'],16]);17 18// A quick table19Dumpio::table(['id', 'status'], [[1, 'paid'], [2, 'pending']], ['message' => 'Orders']);20 21// "How did the code even reach this line?"22Dumpio::trace('unexpected branch');
For how each of these renders (and how exception parsing works), see Dump types and Exceptions.
Short global helpers
The most common typed helpers have plain global shortcuts, autoloaded like dio():
1dumpio_exception($e, ['user' => ['id' => 1]]); // = Dumpio::exception(...)2dumpio_query('select * from users', [], 1.2); // = Dumpio::query(...)3dumpio_trace('reached checkout'); // = Dumpio::trace(...)4dumpio_memory('after import'); // = Dumpio::memory(...)
Recipes
Small, real-world patterns you'll reach for most.
1// Inspect one branch of a request 2dio($request->all(), 'payload')->channel('checkout')->yellow(); 3 4// Count how often a condition fires (one live-updating "×N" entry) 5foreach ($lines as $line) { 6 if ($line->isDiscounted()) { 7 dio($line)->count('discounted lines'); 8 } 9}10 11// Profile a block with splits12$sw = Dumpio::stopwatch('checkout');13$cart = $this->buildCart($request); $sw->lap('cart built');14$order = $this->placeOrder($cart); $sw->stop('order placed');15 16// Debug only when a flag is present (safe on shared/staging)17dio($internalState)->when(config('app.debug'))->channel('internals');18 19// Tap inside an expression without changing the result20return response()->json(dumpio($payload, 'response'));
Configuration
You usually don't need to configure anything. When you do, either set environment variables or call Dumpio::configure([...]) once during boot:
| Option | Env var | Default | Meaning |
|---|---|---|---|
host |
DUMPIO_HOST |
localhost |
Where the Dumpio app listens |
port |
DUMPIO_PORT |
21234 |
The port it listens on |
token |
DUMPIO_TOKEN |
"" |
Shared token, if the app requires one |
enabled |
DUMPIO_DISABLE (set it ⇒ off) |
true |
Master on/off switch |
timeoutMs |
– | 1500 |
Network timeout in milliseconds |
maxDepth / maxItems / maxString |
– | 6 / 100 / 2000 |
How deep/wide/long the value tree may get |
1Dumpio::configure([2 'host' => '127.0.0.1',3 'port' => 21234,4 'token' => 'Dio-…', // only if the app has a token set5]);
The token is sent as the X-Dumpio-Token header. The package talks HTTP only.
Running your app in Docker or a VM?
localhostinside the container isn't your host machine. PointDUMPIO_HOSTat the host (oftenhost.docker.internal) and make sure the Dumpio server accepts non-loopback connections.
What actually gets sent (the wire format)
You rarely need this, but it helps to know there's no magic. Each dump is a small JSON object with a standard envelope plus type-specific fields:
1{ 2 "timestamp": 1749900000000, 3 "flag": "blue", 4 "channel": "default", 5 "type": "var", 6 "language": "php", 7 "label": "current user", 8 "caller": { "file": "/app/Http/UserController.php", "line": 21, "function": "show" }, 9 "value": {10 "kind": "array",11 "children": [12 { "kind": "int", "value": 7, "key": "id" },13 { "kind": "string", "value": "Ada", "key": "name" }14 ]15 }16}
The serializer understands scalars, arrays, objects (with class and property visibility), enums, DateTime, Eloquent models, Laravel and Doctrine collections, Symfony UID (Uuid/Ulid), SplObjectStorage, and Stringable value objects (their __toString() is shown alongside the real properties), detects cyclic references, and trims by maxDepth/maxItems/maxString. The framework-aware shapes are detected by class name, so they stay inert when the framework isn't installed.
Production safety
The package is built to be safe to leave installed:
- Off in production by default. The standalone client is enabled unless
DUMPIO_DISABLEis set; under Laravel,enabledfollowsAPP_DEBUG. A normal production deploy sends nothing. - Never throws. Every public method swallows its own errors — a missing app, a serialization edge case, or a network hiccup is silently ignored.
- Bounded work. The serializer caps depth/items/string length, so even a huge object graph does limited work.
Using it with a framework
The framework integrations add automatic capture — queries, exceptions, emails, jobs, and more — with zero code changes, plus conveniences like chainable macros. They have their own pages:
- PHP + Laravel — auto-discovered service provider,
config/dumpio.php, the->dio()macros, Monolog handler, Livewire, and more. - PHP + Symfony — the Dumpio bundle, Doctrine/Messenger/Mailer capture, and the exception subscriber.
Troubleshooting
Nothing shows up.
- Is the Dumpio app running and listening on the configured port? Test it directly:
curl http://127.0.0.1:21234/healthshould return{"ok":true,…}. - Is the client enabled? Standalone:
DUMPIO_DISABLEmust be unset. Laravel:DUMPIO_ENABLED=true(orAPP_DEBUG=true). - Confirm the transport independently:
echo '{"message":"ping"}' | nc localhost 21234. - Custom host/port? Make sure
DUMPIO_HOST/DUMPIO_PORTmatch the app.
A token is set. Give the client the same value with DUMPIO_TOKEN; it's sent as the X-Dumpio-Token header.
A dump looks cut off. You hit maxDepth / maxItems / maxString. Raise them via Dumpio::configure([...]) for that run.
For app-side issues (403/413/429, the red raw entry, filters hiding dumps), see the app's Troubleshooting page.