The Data Sheet task
The Data Sheet workflow task reads from and writes to a sheet in a Database — for example a Stripe webhook that upserts an order row, or a scheduled run that appends a daily summary. It replaces the old workflow Dataset node: state and records now live in shared, queryable tables instead of a workflow-local key-value store.
Configuring the task
- In the workflow builder, add a task and choose Data Sheet as the type.
- Pick a database. The picker is filtered to databases in the workflow's app, so you can only target data that belongs to the same app.
- Pick a sheet within that database.
- Choose the operation:
read,create,edit,upsert, ordelete. - Map the task's dynamic inputs (the
matchfilter, thecellsto write, andlimit/offset/sortfor reads) from trigger inputs or previous task outputs using workflow references.
The operations
- read — returns rows matching the
matchfilter. Outputsitems,total, andcount. Uselimit/offset/sortto page and order. The filter uses the standard query DSL. - create — inserts a new row from
cells. Outputs the newrow_id. - edit — finds the row matching
matchand patches it withcells(cells you omit are untouched). - upsert — edits the matching row if one exists, otherwise creates it. The output's
created/matchedflags tell you which happened. - delete — soft-deletes the row matching
match(recoverable from the sheet's Recently deleted for 90 days).
Ownership and limits
The task runs with the workflow's team identity, so it can only touch databases owned by the same team and app — it reuses the exact ownership gate, cell validation, and per-team row caps that public Data-Sheet writes use, so behavior matches everywhere. Writes are subject to the plan's row cap (max_data_sheet_rows); a write at the cap fails the task with a "sheet full" error. See Data Sheets overview for the feature and plan gating.
Using sheets from a Code task
For logic the Data Sheet task can't express, a workflow Code task can read and write sheets directly through ctx.sheets — the second argument to your code. You name the database per call:
// inside a Code task — ctx is the 2nd argument
const { id } = await ctx.sheets(inputs.database_id).insertRow('orders', { name: inputs.name });
await ctx.sheets(inputs.database_id).updateRow('orders', id, { status: 'paid' });
const { items } = await ctx.sheets(inputs.database_id).query('orders', { where: { status: 'paid' }, limit: 20 });
Each ctx.sheets(databaseId) exposes insertRow, updateRow, deleteRow, query, getRow (fetch one row by its id; returns null if absent), listRows, and list. The database must belong to the workflow's team (ownership-checked on every call), sheets are addressed by slug, and the same cell validation + row caps apply. Pass the database id in as an input rather than hard-coding it.