Tables & data
Tables are built-in spreadsheets your workflows and agents read and write, with no external database to set up. A workflow can find rows, insert and update them, delete them, or start when a row changes.
How tables are stored
A table's rows live in one shared store, with your columns packed into a single data field per row (there is no external database to connect or configure). Every row also carries three system fields you don't define yourself:
| System field | Meaning |
|---|---|
id | The row’s primary key. Matches the reserved filter name id (see filtering below). |
priority | An integer used for the default sort order. |
created_at | Timestamp set when the row was inserted. |
These fields come back alongside your own columns on every read. A few names are reserved and can't be used for your own columns: id, data_source_id, tenant_id, data, priority, row_index, created_at, and updated_at. A write to one of them is refused.
The CRUD operations
There are five underlying operations. The workflow builder and the agent-facing table tool label them slightly differently, but they map onto the same behavior:
| Builder node | Agent tool action | What it does |
|---|---|---|
| Find Rows | query_rows | Query rows by a filter and return them as an items list, meant to feed a Split step for per-row parallel processing. Find Rows does not split by itself. |
| Get Row | query_rows | Query rows by a filter and return them as a flat rows list. Find Rows and Get Row are separate builder nodes, but both resolve to the same underlying read. |
| Create Row | insert_rows | Add a new row from a map of column values. |
| Update Row | update_rows | Change columns on every row matching a filter. |
| Delete Row | delete_rows | Remove every row matching a filter. |
| Create Column | add_columns | Add a column to the table, optionally backfilling existing rows with a default value. |
The agent table tool also exposes table-level actions (create, get, list, update, delete) and marketplace actions (publish, unpublish), plus help.
Column types
A column is one of fifteen types, which control how it's validated and shown:
| Type | Notes |
|---|---|
text | Free text. |
number | Numeric value (stored, but compared as text: see filtering below). |
date | Date value. |
checkbox | Boolean. |
select | One choice from a fixed list. Requires a non-empty options list at creation. |
multi_select | Several choices from a fixed list. Also requires a non-empty options list. |
rating | Numeric rating. |
sentiment | Sentiment value. |
progress | Progress value. |
file | A stored file, shown as a card. See file and image columns below. |
image | The same value as file, shown as a thumbnail. |
email | Email address. |
phone | Phone number. |
url | URL. |
vector | Embedding vector for similarity search. Requires a dimension (1 to 2000). Plan-gated on the managed cloud: see below. |
Values are converted to the column type
Every value you write is converted to its column's type: a number column turns "3,14" or "42%" into a number, a checkbox accepts yes or 1, and so on. A value that doesn't fit its column does not fail the write: instead the write reports warnings. Most are harmless normalizations, but a value that can't be parsed leaves the cell empty, and a file reference with nothing to fetch it by is stored but unusable.
In a workflow, read them from the step output as {{table:<label>.output.warnings}}; the agent table tool adds them to its reply. A vector value is the exception: a malformed embedding fails the whole write and no row is stored.
File and image columns
A file column and an image column hold the same value, a file reference, and differ only in how the grid shows it (a card or a thumbnail). In the grid, a cell can take an upload, an existing file picked from Files, or an external URL. Reads return the file reference as an object (id, path, mimeType, and size appear only when known):
{
"_type": "file",
"id": "b21f6c1e-5a9d-4c1b-9d0e-2f7a8c3e41d2",
"url": "/api/proxy/files/by-id/b21f6c1e-5a9d-4c1b-9d0e-2f7a8c3e41d2/raw",
"name": "report.pdf",
"mimeType": "application/pdf",
"size": 48213
}Write a whole file reference (for example {{core:dl.output.file}}) into the cell, not one of its fields. Store files in a file or image column, not a textcolumn: a text column keeps whatever text it was given and gives it back as text. A file cell can't be used as a filter value (it matches nothing).
Filtering: the rule that trips people up
A filter (where) is a bare column name, an operator, and a value: write status, not data.status (a data. prefix is stripped for you). The reserved name idmatches a row's primary key, not a column you defined.
| Operator | Reliable? |
|---|---|
=, !=, IN, IS NULL, IS NOT NULL, LIKE | Yes, use these. |
>, <, >=, <= | Textual order only: unreliable for numbers and dates. |
LIKE does not add wildcards automatically: include % or _ in the value yourself (for example %gmail.com). IN requires a non-empty list of values. Aliases are accepted too: ==, EQ, NE, GT, LT, GTE, LTE, CONTAINS (same as LIKE, still without automatic wildcards), ISNULL, and NOTNULL.
Reading & pagination
How many rows a read returns, and how many it can return at most, depends on where it runs from:
| Caller | Rows per read |
|---|---|
| Workflow Get Row node | Up to 500 rows per read. With no limit set it reads up to 500; a higher limit is lowered to 500. |
| Workflow Find Rows node | Keeps 100 rows by default. You can set a higher limit, but the read behind it still returns at most 500 rows. |
| Agent table tool (query_rows) | 20 rows by default, up to 10,000. There is no offset: to page through a large table, narrow the where filter instead of raising the limit. |
| The table grid in the app | 20 rows per page by default; choose 10, 20, 50, or 100 per page. |
Every returned row carries the system fields id, priority, and created_at alongside your own columns. The default sort is priority DESC, id DESC (newest or highest priority first). There is no user-configurable sort on Find Rows or Get Row, so sort downstream in a Code step if you need a specific order. A limit of 0 returns no rows, which makes a cheap existence check.
Writing rows
Insert takes a map of column to value. Update takes a filter plus a non-empty set map: both are required, or the write fails fast. An empty value is stored as an empty string (not a true null).
Insert "Save contact":
columns = { name: "{{trigger:form.output.name}}", email: "{{trigger:form.output.email}}" }
Update "Mark done":
where = { column: "status", operator: "=", value: "in_progress" }
set = { status: "completed" }Deleting
Delete always needs a wherefilter: there is no “clear table” operation. To wipe every row, match on the always-present primary key:
Delete "Clear table":
where = { column: "id", operator: "IS NOT NULL" }Don't create duplicates
When a workflow can run more than once on the same item, guard your insert so re-runs don't pile up duplicate rows:
- Find by a unique keyLook the item up first with Find Rows, for example
where: { column: "message_id", operator: "=", value: "{{trigger:gmail.output.id}}" }. - Decide on the countA Decision on
{{table:check.output.item_count}} == 0splits “new” from “already there”. - Insert only on the new branchInsert on the new branch; end on the other. The write is now idempotent.
Creating a column
A select or multi_select column restricts values to a set of choices you provide: creation is rejected if that options list is empty. A vectorcolumn needs a declared dimension (1 to 2000) and a distance metric. When you add a column with a default value, existing rows that don't already have that key are back-filled with the default automatically.
Exporting a table
In the table grid, Export downloads the rows as CSV, JSON, or Excel, either the current view or all data. There is no CSV import button in the grid: to fill a table from a file, read the file with an Extract from File step and insert the rows, or ask the assistant to create the table with its data.
Vector similarity search
Tables store and search embeddings; they don't create them. Generate the embedding with an embeddings API (through an integration or an HTTP Request step) and insert it into the vector column. To prepare documents, an Extract from File step in text mode splits a PDF, Word, HTML, or text file into chunks you can embed one by one (see Files & storage).
A similarity search is a read with a similarity block instead of (or alongside) where: column, queryVector, an optional topK (default 5), and an optional threshold. The query vector must match the column's declared dimension. The distance metric is not part of the query: it is set on the vector column when you create it (cosine by default, or l2 or dot). Combine where with similarity for hybrid search: the filter narrows the candidate rows before nearest-neighbor ranking runs.
When a row changes
Every insert, update, and delete fires a row-changed event once it commits. This is what powers a datasource trigger: a workflow that starts automatically whenever a row in a chosen table is created, updated, or deleted. An update event also carries the row's previous values, so a trigger can compare before and after.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
A > or < filter skips rows it should match | Comparisons are textual, so "100" sorts before "9". | Filter with = or IN on known values, or compare in a Code step. |
| A delete fails because it has no filter | Delete always needs a where filter: there is no clear-table operation. | To remove every row, filter on id with IS NOT NULL. |
| Re-running a workflow creates duplicate rows | The insert runs every time, even for an item already stored. | Look the item up with Find Rows first and insert only when item_count is 0. |
| A read returns fewer rows than the table holds | A workflow read returns at most 500 rows, and Find Rows keeps 100 by default. | Narrow the filter, or page with offset on Get Row. |
| A stored file comes back as text | It was saved in a text column. | Use a file or image column and map the whole file reference into it. |
| A write succeeds but a cell is empty | The value could not be converted to the column type. The write reports it in warnings instead of failing. | Read the warnings output and fix the value or the column type. |