Work with tables in agent functions
This page covers using your agent's database tables from the code of an agent function: searching, inserting and updating rows with tables from "@yellow/bot". You create the tables and their columns on the Database page.
import { tables } from "@yellow/bot";
The examples use an orders table with the columns email, status, order_id and total. In the editor, pick tables and columns from the suggestions instead of typing them:
- Type
"right aftertables.search(,tables.insert(ortables.update(to choose the table. - Type
"@to choose a column for a filter key, a row you insert, or thesetof an update. The column appears as a label such as orders.email. - Type
"aftercolumn:in a list-style filter to choose a column.
See Get suggestions.
tables.search
Finds rows that match a filter.
tables.search(table, filter?, options?) // → rows
| Parameter | Description |
|---|---|
table | The table to search. Required. |
filter | Which rows to return. Leave it out to return every row, up to the limit. |
options.sort | Sort by one column: 1 ascending, -1 descending |
options.limit | Number of rows to return. Default 10, maximum 100. A larger number returns 100 rows, without an error. |
options.skip | Number of rows to skip, for paging |
options.columns | Return only these columns |
Each row comes back keyed by column name, plus id, created_at and updated_at:
created_at: "2026-10-14T12:04:09.157Z", updated_at: "2026-10-14T12:04:09.157Z" }
A JSON column comes back as text. Parse it before you read its fields:
const details = typeof row.details === "string" ? JSON.parse(row.details) : row.details;
Match a value
const rows = await tables.search("orders", {
"orders.email": email,
});
Match several conditions
All conditions in a filter must match. To match any one of them, use $or.
// Open orders for this customer
await tables.search("orders", {
"orders.email": email,
"orders.status": "open",
});
// Orders that are open or on hold
await tables.search("orders", {
$or: [
{ "orders.status": "open" },
{ "orders.status": "on_hold" },
],
});
$or applies to the whole filter. It can't be combined with other conditions next to it.
Other comparisons
| Operator | Matches when | Example |
|---|---|---|
$between | The value is in a range, inclusive. Also how you write greater than or less than. | { $between: [100, 500] } |
$contains | The text contains a value | { $contains: "pune" } |
$ncontains | The text doesn't contain a value | { $ncontains: "test" } |
$ci | Add to ignore upper and lower case. Works only on columns set as case-insensitive. | { $eq: "[email protected]", $ci: true } |
const bigOrders = await tables.search("orders", {
"orders.total": { $between: [100, 1000] },
});
Sort, limit and choose columns
const latest = await tables.search(
"orders",
{ "orders.email": email },
{
sort: { created_at: -1 },
limit: 5,
columns: ["orders.order_id", "orders.status"],
},
);
You can sort by id, created_at and updated_at by name. Sort by one column only.
Page through many rows
One search returns at most 100 rows, and a function can search 5 times per run, so one run can read at most 500 rows. To read more than 100, page with skip:
- Sort by
id, so pages don't overlap. - Keep
limitat 100 or less. - Move
skipforward by the number of rows the last page returned, and stop when a page comes back empty or short.
const rows = [];
for (let page = 0; page < 5; page++) {
const batch = await tables.search(
"orders",
{ "orders.status": "open" },
{ sort: { id: 1 }, limit: 100, skip: rows.length },
);
rows.push(...batch);
if (batch.length < 100) {
break;
}
}
If limit is above 100, each page still returns 100 rows. Moving skip by your limit would then jump past rows without any error, so always move it by what came back.
List-style filter
search also accepts conditions as a list, with equals, contains, not_contains and range. Both styles return the same rows.
const rows = await tables.search("orders", {
where: [{ column: "orders.email", operation: "equals", value: email }],
sort: [{ column: "orders.total", order: "desc" }],
limit: 5,
});
tables.insert
Adds one row.
tables.insert(table, data) // → { success, message }
const result = await tables.insert("leads", {
"leads.name": name,
"leads.phone": phone,
});
if (!result.success) {
return { saved: false, reason: result.message };
}
return { saved: true };
A row the database rejects doesn't raise an error. insert returns { success: false, message } instead, so always check result.success.
Pass one object per call. A list is refused with "one row per call". To add several rows, call insert once for each, up to 5 per run.
tables.update
Changes values in rows that match a filter.
tables.update(table, { set, where }) // → true
await tables.update("orders", {
set: { "orders.status": "cancelled" },
where: [{ column: "orders.order_id", operation: "equals", value: orderId }],
});
whereis required. An update without it is refused, so a function can never rewrite a whole table.- An update that would change more than 5 rows is refused.
updatereturnstruewhen it has run, whether or not a row matched. If you need to know that a row exists, search for it first.
Related
- Global variables, for small values shared across the conversation
- Examples: order lookup, saving a lead without duplicates, cancelling an order