The Table block is how a workflow reads from and writes to Tables. The same query semantics are exposed by the Tables REST API and through the actana_tables skill that the Assistant and Crew agents use.
Operations
The block exposes a single operation parameter — pick one of:
| Operation | What it does |
|---|---|
read | Return rows matching a filter. Optional sort, limit, offset. |
read-row | Return a single row by rowId. |
insert | Insert one row from a data object. |
bulk-insert | Insert many rows from a rows array. |
update | Update a single row by rowId with a data patch. |
bulk-update | Update every row matching a filter with a data patch. |
delete | Delete one row by rowId. |
bulk-delete | Delete every row matching a filter. |
The operation determines which sub-blocks are visible in the UI — rowId is required for the row-scoped operations, filter is required for the bulk operations.
Filters
Filters are evaluated against the JSONB data column. The block accepts two input modes:
- Builder (basic) — UI filter rows, internally compiled to the same shape as JSON mode.
- JSON (advanced) — pass a filter object directly. Useful when constructing filters from upstream block output.
A filter is a tree of conditions. A leaf condition is { column, op, value } where op is one of eq, neq, gt, gte, lt, lte, contains, starts_with, ends_with, in, is_null, is_not_null. Branch nodes are { all: [...] } (AND) or { any: [...] } (OR).
{
"all": [
{ "column": "status", "op": "eq", "value": "active" },
{ "any": [
{ "column": "score", "op": "gt", "value": 80 },
{ "column": "vip", "op": "eq", "value": true }
]}
]
}The block converts builder rules to this shape via filterRulesToFilter from lib/table/query-builder/converters.ts.
Sorts
Sorts are an array of { column, direction } entries applied in order. direction is asc or desc. Builder mode emits the same shape via sortRulesToSort.
[
{ "column": "createdAt", "direction": "desc" },
{ "column": "name", "direction": "asc" }
]Limit + offset
Both are integers passed as block params (the UI accepts strings and the params transformer coerces them at execution time). The block enforces a hard ceiling from TABLE_LIMITS in lib/table/constants.ts.
Referencing block outputs in JSON
When constructing filters or data payloads from another block's output, wrap the reference in quotes:
// ✓ Good
{ "column": "email", "op": "eq", "value": "<signupForm.email>" }
// ✗ Bad — invalid JSON, the parser hint will surface this
{ "column": "email", "op": "eq", "value": <signupForm.email> }The block's parseJSON helper (in blocks/blocks/table.ts) detects the unquoted-reference pattern and surfaces a specific error message before the run proceeds.
Bulk operations are atomic at the row level — a partial failure in one row does not roll back others. If you need transactional semantics across many rows, build a workflow that runs the operations sequentially and writes a status column.
Source
apps/actana/blocks/blocks/table.ts— block definition + JSON parsingapps/actana/lib/table/query-builder/converters.ts— builder → filter/sort shapeapps/actana/lib/table/constants.ts—TABLE_LIMITSapps/actana/tools/table/types.ts—TableQueryResponseapps/actana/app/api/v1/tables/[tableId]/— REST endpoints