Menu

data-tables

n8n-decanter data-tables [table…]              # schema + rows for every table (or the named ones)
n8n-decanter data-tables [table…] --filter '<json>' --search <text> --sort <col:asc|desc> --limit N --all
n8n-decanter data-tables [table…] clean        # delete fetched data (offline)

Fetches each n8n data table (the built-in project-scoped tables, n8n ≥ 2.x) — its schema and its rows — into local files, so you can develop and debug a workflow against the real table contents offline (e.g. to give a node run fixture realistic shapes, or just to eyeball what a table holds). Read-only against the API — the CLI never creates, updates, or deletes a data table, column, or row.

A <table> is a table’s id, its name in any case, its kebab form, or its folder name. A table cardmuseum marketplace sync queue with id 3 is matched by 3, cardmuseum marketplace sync queue, Cardmuseum Marketplace Sync Queue, cardmuseum-marketplace-sync-queue, or cardmuseum-marketplace-sync-queue-3 — an exact name or id match always wins over the kebab form, so two similarly-named tables don’t collide. With no argument, every table is fetched.

Uses the n8n public API key (N8N_API_KEY) — MCP’s data-table tools are add-only with no row reads, so this verb keeps the REST path. Without a key it fails with guidance.

Where the data lands

Data tables are project-scoped — not owned by a workflow — so, unlike executions (which nest under each workflow folder), they land in a single top-level data-tables/ dir next to decanter.config.json:

data-tables/
  <table-slug>/
    meta.json         # id, name, projectId, fetchedAt, rowCount + the applied filter/search/sort/limit
    columns.json      # the table schema (each column's name + type)
    rows.json         # the (possibly filtered) rows
    rows.stale.json   # present only after a failed refetch — see below

The slug is the kebab of the table name with its id appended (names aren’t guaranteed unique). meta.json records what produced rows.json, so a filtered slice is self-describing and never mistaken for the whole table. Its fetchedAt is the field to check for freshness — it dates whatever rows the dir currently holds, fresh or stale.

When a fetch fails

Any of these failures renames the affected tables’ existing rows.json to rows.stale.json rather than deleting it or leaving it in place:

  • A table’s columns or rows fail to fetch — a dropped connection, a 403 from a key missing a scope. That table goes stale, and so does every table after it in the run (the ones not yet fetched): once one table fails, the run stops rather than retrying or continuing past it.
  • The initial table-list call itself fails. Every local table dir the run’s refs would have selected (or every local dir, if run with no refs) goes stale, since none of them could have been refreshed.
  • One of several <table> refs doesn’t match anything on the server. The run aborts before fetching any table, so the local dirs the other, valid refs select go stale too — a typo in one ref can stale rows for an unrelated table named correctly on the same command line.

meta.json and columns.json are untouched by any of this, so meta.json’s fetchedAt still dates the stale rows honestly. A table dir with a rows.stale.json and no rows.json means its last refetch failed; the next successful fetch restores rows.json and removes rows.stale.json.

Renaming rather than deleting means a transient failure never costs you a large table’s previously-fetched rows. Renaming rather than leaving a separate “stale” marker next to an untouched rows.json means anyone opening rows.json directly sees “file not found” instead of silently reading yesterday’s data as if it were current.

The command fails with exit code 1 and reports, by name, which tables it refreshed and which it didn’t — including any table it renamed rows.json to rows.stale.json for.

After a successful run, if the data-tables/ dir holds other local table folders the run wasn’t asked to fetch, it prints one line naming each of them and its fetchedAt (or stale, or no meta.json), so you can see at a glance which tables are from an earlier run.

Pull a filtered slice

A table’s rows can be large, so the verb pulls a slice by pushing the filter down to the server rather than downloading everything:

Flag Meaning
--filter '<json>' Server-side condition filter — a JSON string passed 1:1 to the API (see shape below)
--search <text> Free-text search across string columns
--sort <col:asc|desc> Sort by a column, ascending or descending
--limit N Rows per page (default 100, API cap 250; --limit N or --limit=N)
--all Follow the cursor to exhaust the (usually filtered) result, not just one page

The --filter value is n8n’s own condition object as a JSON string:

{ "type": "and", "filters": [ { "columnName": "status", "condition": "eq", "value": "active" } ] }

condition is n8n’s row operator (eq, neq, gt, gte, lt, lte, like, …); combine several under "type": "and" / "or". For example, only the active orders, newest first:

n8n-decanter data-tables "Orders" \
  --filter '{"type":"and","filters":[{"columnName":"status","condition":"eq","value":"active"}]}' \
  --sort createdAt:desc

The applied filter, search, sort, and limit are written into that table’s meta.json alongside the resulting rowCount.

Never commit table data

Each data-tables/ dir is written self-ignored (it contains a .gitignore of just *) because table rows can hold PII — they must never reach git. init’s scaffolded root .gitignore also lists data-tables/.

data-tables clean

Offline. Deletes the whole local data-tables/ dir. Run it when you’re done.

Config gate

The fetch is gated by the dataTables key in decanter.config.json (default true). Set it to false to disable the fetch entirely — the verb then refuses with a clear message, and the recommended API key needn’t carry the data-table read scopes. data-tables clean stays available regardless.

Scopes

While dataTables is on, the recommended scoped key needs the read scopes dataTable:list, dataTable:read, dataTableColumn:read, and dataTableRow:read (a full-access key also works). Data-table endpoints need n8n ≥ 2.x — on an older instance the fetch reports that it isn’t available.