Tables allow you to create and manage custom data tables directly within Sim. Store, query, and manipulate structured data within your workflows without needing external database integrations.
Why Use Tables?
- No external setup: Create tables instantly without configuring external databases
- Workflow-native: Data persists across workflow executions and is accessible from any workflow in your workspace
- Flexible schema: Define columns with types (string, number, currency, boolean, date, json, select) and constraints (required, unique)
- Powerful querying: Filter, sort, and paginate data using a typed predicate grammar
- Agent-friendly: Tables can be used as tools by AI agents for dynamic data storage and retrieval
Key Features:
- Create tables with custom schemas
- Insert, update, upsert, and delete rows
- Query with filters and sorting
- Batch operations for bulk inserts
- Bulk updates and deletes by filter
Creating Tables
Tables are created from the Tables section in the sidebar. Each table requires:
- Name: Alphanumeric with underscores (e.g.,
customer_leads) - Description: Optional description of the table's purpose
- Schema: Define columns with name, type, and optional constraints
Column Types
See Table column types for the supported types and their storage behavior.
Column Constraints
- Required: Column must have a value (cannot be null)
- Unique: Values must be unique across all rows (enables upsert matching)
Usage Instructions
Create and manage custom data tables. Store, query, and manipulate structured data within workflows. Query Rows accepts a plain predicate — {"field":"wins","op":"gte","value":10} — for one condition. Use all (AND) or any (OR) groups for multiple or nested conditions. Operators: eq, ne, gt, gte, lt, lte, in, nin, like, ilike, nlike, nilike, contains, ncontains, startsWith, endsWith, isNull, isNotNull, isEmpty, isNotEmpty. Order is a sort spec [{"field":"wins","direction":"desc"}]. Query Rows returns every matching row when Limit is omitted (fails if the result exceeds 5MB — add a filter or a Limit). With a Limit, responses page: a non-null nextCursor means more rows exist — pass it back as the cursor. Columns to Return narrows each row to the selected columns (by stable id or name; one that no longer exists is skipped); leave it empty for every column.
Actions
Insert Row
Insert a new row into a table. IMPORTANT: You must use the "data" parameter (not "values", "row", "fields", or other variations) to specify the row contents.
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
data | object | Yes | Row data as JSON object |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether row was inserted |
row | json | Inserted row data |
message | string | Status message |
Batch Insert Rows
Insert multiple rows into a table at once (up to 1000 rows)
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
rows | array | Yes | Array of row data objects (max 1000 rows) |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether rows were inserted |
rows | array | Inserted rows data |
insertedCount | number | Number of rows inserted |
message | string | Status message |
Upsert Row
Insert or update a row based on unique column constraints. If a row with matching unique field exists, update it; otherwise insert a new row. IMPORTANT: You must use the "data" parameter (not "values", "row", "fields", or other variations) to specify the row contents.
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
data | object | Yes | Row data to insert or update |
conflictTarget | string | No | Unique column to match on. Required only when the table has more than one unique column. |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether row was upserted |
row | json | Upserted row data |
operation | string | Operation performed: insert or update |
message | string | Status message |
Update Row
Update an existing row in a table. Supports partial updates - only include the fields you want to change. IMPORTANT: You must use the "data" parameter (not "values", "row", "fields", or other variations) to specify the fields to update.
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
rowId | string | Yes | Row ID to update |
data | object | Yes | Updated row data |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether row was updated |
row | json | Updated row data |
message | string | Status message |
Update Rows by Filter
Update multiple rows that match filter criteria. Data is merged with existing row data.
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
filter | object | Yes | Filter criteria using operators like $eq, $ne, $gt, $lt, $contains, $ncontains, $startsWith, $endsWith, $in, $nin, $empty, etc. |
data | object | Yes | Fields to update (merged with existing data) |
limit | number | No | Maximum number of rows to update (default: no limit, max: 1000) |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether rows were updated |
updatedCount | number | Number of rows updated |
updatedRowIds | array | IDs of updated rows |
message | string | Status message |
Delete Row
Delete a row from a table
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
rowId | string | Yes | Row ID to delete |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether row was deleted |
deletedCount | number | Number of rows deleted |
message | string | Status message |
Delete Rows by Filter
Delete multiple rows that match filter criteria. Use with caution - supports optional limit for safety.
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
filter | object | Yes | Filter criteria using operators like $eq, $ne, $gt, $lt, $contains, $ncontains, $startsWith, $endsWith, $in, $nin, $empty, etc. |
limit | number | No | Maximum number of rows to delete (default: no limit, max: 1000) |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether rows were deleted |
deletedCount | number | Number of rows deleted |
deletedRowIds | array | IDs of deleted rows |
message | string | Status message |
Query Rows
Query rows with a typed predicate filter and cursor pagination. A single filter can be a plain condition: \{"field":"wins","op":"gte","value":10\}. Use all (AND) or any (OR) groups for multiple or nested conditions. Operators: eq, ne, gt, gte, lt, lte, in, nin, like, ilike, nlike, nilike, contains, ncontains, startsWith, endsWith, isNull, isNotNull, isEmpty, isNotEmpty. Order is a sort spec, e.g. [\{"field":"wins","direction":"desc"\}]. Omit limit to return the entire result — the query fails if it exceeds the 5MB budget (narrow with a filter or set a limit). With a limit, a page can end early at the byte budget: a non-null nextCursor means more rows exist — pass it back as cursor to continue; never infer completion from page size.
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
filter | json | No | Predicate condition, e.g. \{"field":"wins","op":"gte","value":10\}. Use all or any for multiple conditions; omit to match all rows. |
columns | array | No | Stable column IDs or table column names to include in each row data object. Omit or pass an empty array to return all columns. A reference that matches no column is ignored. |
order | json | No | Sort spec, e.g. \[\{"field":"wins","direction":"desc"\}\]. |
limit | number | No | Maximum rows per page. Omit to return the entire matching result — fails if it exceeds the 5MB budget. With a limit, pages may byte-cut early and set nextCursor when more remain. |
cursor | string | No | Opaque pagination cursor returned by a prior query. Omit for the first page. |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether the query succeeded |
rows | array | Query result rows |
rowCount | number | Number of rows returned |
totalCount | number | Total rows matching the predicate (computed on the first page only) |
limit | number | Limit used in the query |
nextCursor | string | Cursor to fetch the next page, or null on the last page |
Get Row
Get a single row by ID
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
rowId | string | Yes | Row ID to retrieve |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether row was retrieved |
row | json | Row data |
message | string | Status message |
Get Schema
Get the schema configuration of a table
Input
| Parameter | Type | Required | Description |
|---|---|---|---|
tableId | string | Yes | Table ID |
Output
| Parameter | Type | Description |
|---|---|---|
success | boolean | Whether schema was retrieved |
name | string | Table name |
columns | array | Column definitions (each includes its stable id) |
columnCount | number | Number of columns |
rowCount | number | Number of rows in the table |
maxRows | number | Max rows per table for the workspace's plan |
message | string | Status message |
Filter Operators
A filter is a predicate. One condition is an object naming a column, an operator, and a value:
{"field": "status", "op": "eq", "value": "active"}| Operator | Description | Example |
|---|---|---|
eq | Equals | {"field": "status", "op": "eq", "value": "active"} |
ne | Not equals | {"field": "status", "op": "ne", "value": "deleted"} |
gt | Greater than | {"field": "age", "op": "gt", "value": 18} |
gte | Greater than or equal | {"field": "score", "op": "gte", "value": 80} |
lt | Less than | {"field": "price", "op": "lt", "value": 100} |
lte | Less than or equal | {"field": "quantity", "op": "lte", "value": 10} |
in | In array | {"field": "status", "op": "in", "value": ["active", "pending"]} |
nin | Not in array | {"field": "type", "op": "nin", "value": ["spam", "blocked"]} |
contains / ncontains | Contains, or does not contain (case-insensitive) | {"field": "email", "op": "contains", "value": "@gmail.com"} |
like / nlike | Pattern match, * wildcard (case-sensitive) | {"field": "name", "op": "like", "value": "Dr.*"} |
ilike / nilike | Pattern match, * wildcard (case-insensitive) | {"field": "name", "op": "ilike", "value": "*jo*"} |
startsWith | Starts with (case-insensitive) | {"field": "name", "op": "startsWith", "value": "Dr."} |
endsWith | Ends with (case-insensitive) | {"field": "file", "op": "endsWith", "value": ".pdf"} |
isNull / isNotNull | Cell is (not) null | {"field": "phone", "op": "isNull"} |
isEmpty / isNotEmpty | Cell is (not) empty | {"field": "phone", "op": "isEmpty"} |
Most columns are scalar (string, number, boolean, date) or opaque JSON; use ilike with *value* for substring matching on text.
Select columns accept only a subset of these operators, and a query using any other operator on one is rejected rather than returning no rows:
| Column | Allowed operators |
|---|---|
| Single-select | eq, ne, in, nin, isEmpty, isNotEmpty |
| Multi-select | contains, ncontains, isEmpty, isNotEmpty |
A multi-select cell holds a list of options, so match it with contains (by option name) rather than ilike.
Combining Filters
Wrap conditions in all for AND:
{
"all": [
{"field": "status", "op": "eq", "value": "active"},
{"field": "age", "op": "gte", "value": 18}
]
}Use any for OR:
{
"any": [
{"field": "status", "op": "eq", "value": "active"},
{"field": "status", "op": "eq", "value": "pending"}
]
}Groups nest, so mixed logic is a group inside a group:
{
"all": [
{"field": "status", "op": "eq", "value": "active"},
{"any": [
{"field": "plan", "op": "eq", "value": "pro"},
{"field": "score", "op": "gte", "value": 90}
]}
]
}Omit the filter entirely to match every row.
Sort Specification
Order is a list of column/direction pairs, applied in order:
[{"field": "createdAt", "direction": "desc"}]Multi-column sorting:
[
{"field": "priority", "direction": "desc"},
{"field": "name", "direction": "asc"}
]Pagination
Omit Limit to return every matching row in one response; the query fails if the result exceeds 5MB, so narrow with a filter rather than guessing a limit.
With a Limit, results page. A page can end at the limit or at the 5MB byte budget, whichever comes first, so a short page does not mean the end. Pass the returned nextCursor back as Cursor to fetch the next page and stop only when nextCursor is null — never infer completion from the row count.
Built-in Columns
Every row automatically includes:
| Column | Type | Description |
|---|---|---|
id | string | Unique row identifier |
createdAt | date | When the row was created |
updatedAt | date | When the row was last modified |
These can be used in filters and sorting.
Limits
| Resource | Limit |
|---|---|
| Tables per workspace | Depends on workspace plan and deployment settings |
| Rows per table | Depends on workspace plan and deployment settings |
| Columns per table | 1,000 |
| Max row size | 400KB by default; configurable on self-hosted deployments |
| String value length | 10,000 characters |
| Query limit | 1,000 rows |
| Batch insert size | 1,000 rows |
| Bulk update/delete | 1,000 rows |
Notes
- Tables are scoped to workspaces and accessible from any workflow within that workspace
- Data persists across workflow executions
- Use unique constraints to enable upsert functionality
- The visual filter/sort builder provides an easy way to construct queries without writing JSON