Skip to main content
This page contains a detailed specification of the Inertia DevTools protocol. Be sure to read the DevTools page first for a high-level overview of the feature. The DevTools extension is backend-agnostic. Any server-side Inertia adapter may integrate with it by implementing this protocol. The reference implementation is the Laravel adapter, and nothing in this specification is Laravel-specific. The protocol has three layers:
  1. Discovery, how the extension learns an entry was recorded.
  2. Correlation headers, request and response headers that correlate and group entries.
  3. Read API, the endpoints the extension calls to fetch recorded entries, along with the entry format they return.
A conforming adapter records one entry per HTTP request it handles, stores it keyed by a generated id and scoped per browser tab, and exposes it through the read API. The extension fetches each entry out of band rather than reading it from the app’s response, so recording never alters the payload your application actually returns.

Discovery

For every response, the adapter generates a unique entry id (any collision-resistant string, such as a ULID) and sets it on a response header.
string
The generated id for this entry, set on every response. The extension learns the id from this header, then fetches the entry from the read API.
The header alone is not enough for the first document load, which the extension observes through the DOM rather than fetch. On the initial full-page HTML response, also inject a script tag so the extension may read the id before any XHR occurs.
The tag content is the JSON-encoded id string.

Correlation Headers

These headers correlate entries with client-side page state and group related requests. The extension stamps the request headers, the adapter reads them and stores their values on the entry. The adapter emits the one response header. The following request headers are sent by the extension, all optional per request.
string
Per-tab UUID, stored as __meta.tabUuid. Used to scope stored entries so one tab’s history does not evict another’s.
string
Client visit id, stored as __meta.visitId. Lets the extension pair the entry with the browser-side page snapshot.
string
Id of the entry that started this batch, stored as __meta.batchId. Stored verbatim, grouping is a client concern.
string
Present (1) when this is a deferred-prop follow-up. Wire-indistinguishable from a partial reload, so the client declares the intent.
string
Present (1) when this is a polling tick, also wire-indistinguishable from a partial reload.
The following response header is emitted by the adapter.
string
The batch root id (the batchId of this request, or this entry’s own id when it starts a batch). The client forwards this value as X-Inertia-Devtools-Parent on the next same-batch request. Pointing every follow-up at the root keeps a batch flat, and hands the true root down across a redirect so the post-redirect request attaches to the batch root rather than the throwaway redirect hop. A prefetch response returns its own id instead, since a prefetch is speculative and must not advance the batch cursor for unrelated traffic.
An adapter should set this header on every response. Omitting it degrades batching, since the client then falls back to the response’s own X-Inertia-Devtools-Id, so each request chains to the previous response rather than the batch root and redirects may group less precisely.

The Read API

Two authenticated endpoints, gated so they are reachable only in development or by an authorized developer. The paths are fixed. GET /_inertia/devtools/entries/{id} returns a single entry as JSON, or 404 if the id is unknown. This is the only endpoint the extension currently calls. It learns an id from discovery or the response header, then fetches that one entry. GET /_inertia/devtools/entries returns a JSON array of entries, newest first. The extension does not call it yet, so it is optional. It exists for tooling that lists a tab’s history. An adapter may implement these optional query filters: component (exact component name to keep), type and exclude (comma-separated requestType lists to keep or drop), offset, and limit. A minimal adapter may ignore them and return the full buffer. Requests are made with credentials (same-origin cookies) so the adapter may authorize them.

The Entry Format

An entry is a JSON object. Fields are split into two tiers. Required fields must be emitted for the panel to function. Where a value is genuinely absent, the adapter emits the documented empty or null form rather than omitting the key. Optional fields are enhancement data. An adapter may emit null, {}, or omit them, and the panel degrades gracefully, for example hiding “open in editor” links when source locations are absent. Adding fields beyond this specification is safe. The extension ignores unknown fields rather than rejecting the entry. A fully-populated entry for a standard navigation looks like this.
The fields are detailed below.

Meta

The __meta object carries the entry’s identity and summary.
string
required
Matches the X-Inertia-Devtools-Id for this response.
string
required
HTTP method.
string
required
Absolute request URL.
number
required
HTTP status code.
string
required
One of the request types.
string | null
required
Inertia page component, null for non-Inertia (raw HTTP) responses.
string
required
ISO 8601, used for display.
number
required
Float seconds since epoch, used for stable ordering.
string | null
required
From X-Inertia-Devtools-Tab, null on the initial full-page load, which carries no tab header yet.
string | null
required
From X-Inertia-Devtools-Parent, null starts a new batch.
number | null
required
Server processing time in milliseconds, null when unavailable.
string | null
Redirect target for 3xx and Inertia location responses.
string | null
From X-Inertia-Devtools-Visit.
timestamp and utime describe the same instant in two forms: timestamp keeps a stored entry readable on its own, while utime carries sub-millisecond precision and is the key the panel orders and groups by. Adapters emit both, derived from the same instant.

HTTP

The http object is required, and each sub-key is required (use the empty forms when there is nothing to show).
BodyCapture is a tagged union.
value is a string for raw textual bodies, otherwise a decoded JSON value. Recognized omitted reasons are non-inertia-response, non-inertia-request, non-textual, streamed, too-large, unserializable, and binary. The panel maps these to friendly notices, and an unknown reason falls back to a generic message, so new reasons are safe. Adapters redact sensitive keys and summarize file uploads rather than serializing binary content.

Props

Record<string, PropMeta>
required
Per-prop metadata, keyed by dotted path. May be {} when the adapter cannot classify props; the panel then renders values only.
Record<string, unknown>
Resolved prop values, keyed by dotted path. Sensitive values are replaced with "[REDACTED]". May be omitted or {}, the panel guards against absence.

Route

string
required
Matched route path, "" if none.
string | null
required
Route name, null when the route is unnamed.
string | null
required
Controller or handler identifier, null when none applies.
{ file, line }
Handler source location.
{ file, line } | null
required
Where the page render was invoked, null when it cannot be resolved.
string | null
required
Resolved path of the component file, null when it cannot be resolved.
Source-location fields power the “open in editor” links. Adapters that cannot cheaply resolve them emit null and lose only those links.

Request Type Derivation

The type is derived from headers the adapter already sees, plus the two DevTools hint headers. Evaluate in this order and take the first match. initial and http are both non-Inertia requests, and the adapter distinguishes them by whether it rendered an Inertia page. The app’s initial full-page load renders a page (so component is set) and is initial, while a plain JSON or text endpoint the app serves alongside Inertia renders no page (so component is null) and is http. The adapter already knows which it produced, so it classifies once on the wire rather than leaving the panel to re-derive it. The client-visit and cache-hit types are client-only and never come from an adapter.

Storage Semantics

An adapter buffers recent entries and evicts old ones, typically capping entries per tabUuid so a busy tab cannot evict another tab’s history, and pruning entries past a TTL. These are adapter-internal concerns; the protocol only requires that the read API return what is currently stored, newest first.