n8n-data-tables
Installation
SKILL.md
n8n Data Tables
Data Tables are n8n's built-in tabular storage: real tables inside the n8n instance with columns, types, rows, and CRUD via the dataTable node and data-table MCP tools.
Use them for local persistent state: lookup tables, recent events, per-session inventories, counters, idempotency tracking, dedup state when there's row-level logic or external visibility (plain "have I seen this value?" dedup belongs in the Remove Duplicates node). Small-to-moderate volume (tens of thousands of rows fine, millions belong in a real DB).
Non-negotiables
- System-managed columns + external IDs. Three columns auto-exist on every table:
id(bigserial),createdAt,updatedAt. Don't declare them increate_data_table(errors or shadows the system column). Don't write them on insert. For domain identifiers from outside (arxivId, stripeCustomerId, requestId), add a separate column and key dedup/lookup on that. - Only primitives in columns, nested data uses
string+_objectpostfix. No JSON/object/array column types exist. For nested data (arrays, parsed objects), use astringcolumn withJSON.stringify(...)on write andJSON.parse(...)on read. Mark the column with_object(e.g.,keyInsights_object). The postfix is the contract that tells readers to parse. Seereferences/SCHEMA_DESIGN.md.
Strong defaults
- Don't add a Set node before a Data Table node to modify fields. The Data Table node's per-column expression slots are just as powerful as Set fields, so the Set node is doing zero work the Data Table node can't do itself. (Same Set-node antipattern called out in
n8n-expressions.) - Match n8n's column casing: camelCase. The auto-managed columns are camelCase (
createdAt,updatedAt), so user columns read more cleanly when they match:arxivId,paperId,taxRate. Mixed casing in the same query (createdAt >= ... AND arxiv_id eq ...) reads as a typo. Keep the_objectpostfix on stringified-blob columns regardless (keyInsights_object), the underscore is a contract marker, not casing.
- Verify the
columnsparameter viaget_workflow_detailsafter create/update. The UI has a display quirk in manual mapping mode ("Currently no items exist" with no actual data loss). Checking the JSON confirms what's persisted. - Relational design works when the shape calls for it. For genuine parent-child data (papers → summaries, customers → orders), reference parents by
id, name columns explicitly (paperId,customerId), and enforce integrity in workflow logic. Don't force it on flat use cases (dedup, lookup, audit) where there's no relationship to model. - Storage format is not interface format. Parse
_objectfields before returning them from a sub-workflow. Callers should never receive stringified shells they have to parse themselves. Seereferences/SCHEMA_DESIGN.md"Storage format ≠ interface format".