~/docs/features/data-inspection
╭─ § 03.05 ─╮

Data InspectionPRO

└───────────╯

Corral lets you browse the local development data that Cloudflare’s runtime stores on disk. This includes D1 databases, KV namespaces, R2 buckets, Durable Objects, Workflows, and the Secrets Store. In the macOS app you can also invoke live Durable Object methods against your running dev server and see the marshaled return value — see Invoke a method.

Pro enforcement is at the core layer. The ProBindings wrapper struct in corral-core is the single entitlement gate — it’s constructed via ProBindings::entitled(is_pro)? and returns an error for free-tier callers, so there’s no “forgot to add the gate” failure mode. D1/KV/R2/DO/Workflows/Secrets inspection are methods on it. Free-tier helpers like binding_summary and *_data_exists remain as module-level free functions — you can check whether data exists without a Pro license, but you can’t read the data itself.

Command execution is gated through the same struct but behaves differently: Pro buys Corral-managed Node resolution, not permission to run the tool. Without it, corral exec, corral npm, and corral npx hand off to your system binary rather than failing. See Node management.

All data is read from .wrangler/state/v3/ inside each project’s directory — the same local storage used by wrangler dev. CLI access is read-only except for the Secrets Store (create/delete). The macOS app adds Pro write verbs for KV, R2, and D1 — see In the macOS App.

Binding Summary

Get a quick overview of all bindings for a project and whether they have local data:

corral data bindings my-worker

This shows each binding’s type, name, and whether local data exists for it.

D1 Databases

List tables in a D1 binding:

corral data d1 tables my-worker MY_DB

Query rows from a table:

corral data d1 query my-worker MY_DB users

Pagination:

corral data d1 query my-worker MY_DB users --limit 20 --offset 40

The default limit is 50 rows. Results include column names and typed values.

View the column schema for a table:

corral data d1 schema my-worker MY_DB users

This shows each column’s name, type, nullable status, default value, and primary key.

KV Namespaces

List keys in a KV binding:

corral data kv keys my-worker MY_KV

Filter by prefix:

corral data kv keys my-worker MY_KV --prefix user:

Pagination:

corral data kv keys my-worker MY_KV --limit 20 --offset 0

Get a specific key’s value:

corral data kv get my-worker MY_KV user:123

This returns the value, expiration (if set), and metadata.

R2 Object Storage

List objects in an R2 binding:

corral data r2 objects my-worker MY_BUCKET

Filter by prefix:

corral data r2 objects my-worker MY_BUCKET --prefix uploads/

Pagination:

corral data r2 objects my-worker MY_BUCKET --limit 20 --offset 0

Each object shows its key, size (formatted as B/KB/MB), and ETag.

Download an object:

corral data r2 get my-worker MY_BUCKET image.png --output ./image.png
corral data r2 get my-worker MY_BUCKET config.json > /tmp/config.json

Without --output, raw bytes are written to stdout for piping. With --json, the data is base64-encoded.

Durable Objects

List instances for a Durable Object binding:

corral data do instances my-worker MY_DO

Each instance shows its ID and storage size, plus the name your Worker passed to idFromName(...) when there is one. Named instances are listed first. A missing name is ordinary rather than a fault: instances reached through newUniqueId() never have one, and neither do KV-backed classes, service-worker-format Workers, instances whose files predate a Wrangler upgrade, or any project running Wrangler 3, which never recorded names at all. Mixed lists are normal.

Browse storage entries for a specific instance:

corral data do storage my-worker MY_DO abc123def456
corral data do storage my-worker MY_DO abc123def456 --limit 20 --offset 0

Each entry shows its key and value. The default limit is 50 entries.

Invoke a Durable Object (macOS app)

Beyond browsing storage, the macOS app can reach a live Durable Object and show you what it returned. Open the Durable Objects browser, select an instance, and choose Invoke Durable Object….

A Durable Object has two ways in, and the sheet offers whichever your class has.

Call a method. For a class that extends DurableObject, pick a method — the field autocompletes from the class’s callable methods — enter arguments as a JSON array, and invoke.

Send a request. For a Durable Object written before RPC existed, methods aren’t callable at all and its fetch handler is the only entry point. Build a request — method, URL, headers, body — and it goes straight to the object. A class that has both a fetch handler and callable methods offers both.

Either way, the result renders as a collapsible tree that preserves the types plain JSON can’t: Date, Map, Set, bigint, typed byte arrays, Response, and cyclic references all show as themselves rather than being flattened or lost. A method that throws shows the thrown value the same way.

By default the call targets the instance you selected. You can also target an instance by name, which reaches one that doesn’t exist yet — that’s how you create it. A namespace with no instances at all opens the sheet straight into name mode, since naming one is the only way to bring the first into being.

If the class offers nothing to invoke, the sheet says which of the reasons applies rather than looking empty: it defines neither methods nor a fetch handler, the export named by class_name isn’t a Durable Object class, or this Worker doesn’t export it at all (usually a script_name binding, whose class lives in a different Worker).

Invocation runs through a small Corral sidecar injected into your Worker’s dev server, so the project must be running with the sidecar enabled. The first time you invoke, Corral asks for consent (the sidecar runs inside the Worker and can read the project’s env, including secrets) and then starts or restarts the dev server with it. This surface is macOS-app only; there is no CLI or MCP equivalent.

Some projects structurally can’t host the sidecar. Those say so, with the reason, instead of offering an enable button that couldn’t work:

ReasonWhat it means
HTTPS upstreamYour dev server speaks HTTPS; Corral reaches the sidecar over plain loopback
Service-worker entryThe Worker uses the old service-worker format, which has no export default to wrap
Custom dev commandYour dev command isn’t a wrangler invocation Corral can add an entry to
Entry already pinnedThe dev script names its own entry file, which would swallow the one Corral appends
Not a Cloudflare WorkerThe project isn’t a Workers project
Config unreadableThe wrangler config, or the main entry it points at, couldn’t be resolved

What this writes into your project

Enabling the sidecar is the one thing in Corral that puts files in your project directory rather than in ~/.corral/. All of it is added to your .gitignore automatically:

PathWhat it is
.corral/sidecar-shim.mjs, .corral/sidecar-marshal.mjsThe injected entry point and the encoder it uses. Rewritten every time the project starts
.dev.vars (or .dev.vars.<environment>)A short-lived access token, in a marked block at the end of the file. Your own variables are left alone, and the block is removed when the project stops
.gitignore/.corral/, /.dev.vars, and /.dev.vars.* appended under a # Corral (managed) marker, if they aren’t listed already. Nothing you already wrote is changed

The token lives in a file rather than on the command line so it doesn’t show up in ps output or in Corral’s own logs.

Alarms

Each instance row shows its pending alarm — a countdown when it’s near (“in 4m”), a date when it’s further out, and overdue once its time has passed. This is read from the same local files as everything else on the page, so it shows whether or not your Worker is running.

Changing an alarm needs the Worker running with the sidecar enabled, because an alarm lives inside the object. Right-click an instance:

VerbWhat it does
Set alarm…Schedule one — either “in n minutes” or at a specific time. The resolved time is always shown, and a Durable Object has only one alarm, so this replaces any existing one
Clear alarmCancel the pending alarm
Run alarm handler nowRun your alarm() handler immediately instead of waiting. The pending alarm is consumed first, exactly as it would be in production — so a handler that re-schedules itself behaves the same way here

All three need the sidecar live, because an alarm lives inside the object and only a running isolate can reach it. Beyond that: Set and Run need your class to define an alarm() handler — scheduling one nothing can service just fails later, out of sight — and Clear needs a pending alarm, but deliberately not a handler, so an alarm your own code set on a handler-less class is still yours to cancel.

Run alarm handler now asks first if this Worker has any remote: true bindings. Your handler is your code, and your code reads them, so a single click could reach production — it names the bindings and lets you go ahead.

Bindings that aren’t local

Two kinds of binding legitimately have nothing to show, and Corral says which rather than reporting them as empty.

A binding marked remote: true in your Wrangler config talks to the real Cloudflare resource instead of a local emulation. Its tile is marked REMOTE and, when there’s nothing stored locally, reads Remote binding rather than “No local data” — the resource may well be full; there’s just nothing on this machine to browse.

The marker shows whether or not local data exists, and that’s the point. If you’d already run the project before flipping the binding to remote, the old local store is still sitting there — so without the marker the tile would go on reporting a count your Worker no longer reads. Corral doesn’t hide it, since those files are real and worth being able to open or clear; opening the browser explains what you’re looking at.

Durable Objects can’t be remote, but the Worker’s other bindings can, and since invoking a Durable Object runs your code — which reads those bindings — the invoke sheet warns you and names them before you go ahead.

A Durable Object bound with script_name is defined in a different Worker, so its data lives with that project. Its tile names the Worker it belongs to, and its browser explains why the list is empty.

Workflows

List instances for a workflow binding:

corral data workflow instances my-worker MY_WORKFLOW

Each instance shows its ID, status (Created/Running/Completed), step count, and size.

View details for a specific instance:

corral data workflow detail my-worker MY_WORKFLOW abc123def456

This shows the instance params, trigger, and an ordered list of steps with their type (Do/Sleep), status, and result.

Queues

Queues work differently from every other binding here, because the local runtime does. wrangler dev keeps queue messages in memory, with no file to read and no way to ask how many are waiting — on any version. So Corral doesn’t show you a queue’s depth. It shows you two things it can be certain about.

Where a message goes

The Queue tile reports its binding’s destination rather than a count:

Tile readsWhat it means
This Worker consumes itYour own queue() handler receives these messages.
→ other-workerAnother project consumes it, and it’s running.
→ other-worker · not runningIt consumes it, but nothing is up to receive. The tile dims.
→ some-worker · outside CorralA wrangler dev you started yourself is consuming it.
No local consumer — sends are discardedNothing consumes this queue. The tile dims.
Remote bindingremote: true, so sends go to the real queue on Cloudflare.

That last-but-one row is the one worth knowing about. Local development accepts a send to a queue nothing consumes and throws the message away, reporting success. Nothing in the runtime warns you, and producing to a queue some other Worker consumes is the ordinary shape — so the tile says it before you send.

Watching a consumer run

Click the tile to open the Queues console. Send message… puts a synthetic message on the queue from inside your running Worker — JSON, text or base64 bytes, optionally delayed, optionally several at once — and the list records every batch your consumer handled: how many messages, whether the handler threw, and how many attempts it took.

Recording deliveries needs Corral’s sidecar running inside your dev server, since a delivery is only visible from inside the Worker that handled it. If it isn’t running, the console says Not recording rather than pretending the feed is live, keeps whatever it captured earlier on screen, and offers Enable capture…. Enabling it restarts the dev server, so it’s always your choice — and the first time, Corral explains what the sidecar can see before you agree. Sending a message walks the same path if you haven’t enabled it yet.

If the consumer is another project that isn’t running, the send sheet offers a Start button. That’s the only case Corral can fix for you; a queue with no consumer at all needs a config change.

Corral also reads the consumer settings actually in force — batch size, batch window, retries, retry delay, dead-letter queue — from whichever Worker declares the consumer. max_concurrency is called out separately when you’ve set it, because local development ignores it.

Delivery lines from the runtime also appear in the project’s Logs tab with the acked-of-total count colour-coded, and corral logs --context queue filters to them.

Secrets Store

The Secrets Store is the only binding type that supports write operations from Corral.

List secrets:

corral data secrets list my-worker API_KEY

Get a secret’s value:

corral data secrets get my-worker API_KEY my-api-key

Create a secret (value is read from stdin for security — never passed as a CLI argument):

corral data secrets create my-worker API_KEY my-api-key
echo "sk-1234" | corral data secrets create my-worker API_KEY my-api-key

Delete a secret:

corral data secrets delete my-worker API_KEY my-api-key

The macOS app’s Secrets Store browser provides a visual interface with masked/revealable values and confirmation before deletion.

In the macOS App

The macOS app provides visual data browsers for all binding types. From a project’s Overview tab, click any binding card to open its browser in a sheet.

Each browser features a split-view layout with a list on the left and detail view on the right, plus pagination controls and search/filter capabilities.

Beyond browsing, the app exposes Pro write verbs the CLI does not — right-click a row (or use the browser’s toolbar):

  • KVDelete a key, or Edit TTL to set or clear its expiration.
  • R2Delete an object (removes its row and data blob).
  • D1Reset Local Data truncates every user table in place; the schema and the .sqlite file are preserved.
  • Secrets StoreAdd and Delete secrets (also available from the CLI above).

Destructive actions confirm before running.

JSON Output

All CLI data commands support --json for machine-readable output:

corral data d1 tables my-worker MY_DB --json
// Last updated 2026-08-11