AnyDB Scripting
This page documents the reusable scripting runtime used by AnyDB script features, including Automation Script Action and Record Customization Script (In Type designer).
Many Production Level Scripts are available for re-use
Supported Script Keywords (Globals)
| Keyword | Type | Description |
|---|---|---|
anydb | object | Data API for finding, creating, and updating records |
input | object | Host-provided input payload (shape depends on script feature/context) |
output | object | Report/output helper (markdown, text, json, table, csv) |
yieldNow | function | Cooperative yield helper for long loops (await yieldNow()) |
fetch | function | HTTP client for external API calls |
console | object | Logging object (console.log(...)) |
log | function | Shortcut for console.log(...) |
Available Script APIs
anydb.getRecordById(adoid)anydb.getRecordsByType(typeName)anydb.findRecords(queryOrParams)wherequeryOrParamscan be text or{ type, condition, querystring }anydb.findRecordsPage(queryOrParams)wherequeryOrParamscan be text or{ type, condition, querystring, cursor, limit }; returns paged results withnextCursoranydb.getChildren(parentid, options?)whereoptionscan include{ typename?, limit? }anydb.sendNotification({ to, message, hyperlink?, pushnotification? })sends in-app user notifications (supports comma-separated AnyDB user emails/user IDs). Mobile push is opt-in withpushnotification: true.anydb.sendEmail({ to, subject, body })sends email and raises in-app notification for resolvable AnyDB users intoanydb.createRecord({ name, parentid?, typeid?, typename?, cellValues? })anydb.updateRecord({ adoid, cellValues? })anydb.yield()(cooperative event-loop yield for long-running loops)output.summary(text)output.markdown(text)output.text(text)output.json(value)output.table(data)output.csv(data)output.set(key, value)(custom outputs for downstream actions)fetch(url, options)yieldNow()console.log(...)andlog(...)
API Example Index
- Data APIs: getRecordById, getRecordsByType, findRecords, findRecordsPage, getChildren, sendNotification, sendEmail, createRecord, updateRecord, yield
- Output APIs: summary, cellValue, markdown, text, json, table, csv, set
- Utilities: fetch, yieldNow, console.log / log
- Record helpers: getCell, getRefCellValue, setCell, setCellProps, setCellRefValue, lock, unlock, lockCell, unlockCell
Quick Examples By API
anydb.getRecordById
Purpose: Load one record by its ADO ID.
// Signature: anydb.getRecordById(adoid)
const record = await anydb.getRecordById("67f1abc234def56789012345");
if (!record) {
output.summary("Record not found");
return;
}
log(`Loaded record ${record.id}: ${record.name}`);
anydb.getRecordsByType
Purpose: Fetch records for a type name when you need a broad type scan.
// Signature: anydb.getRecordsByType(typeName)
const invoices = await anydb.getRecordsByType("Invoice");
output.summary(`Invoices found: ${invoices.length}`);
anydb.findRecords
Purpose: Query records with filters or query text and return a result list.
// Signature: anydb.findRecords(queryOrParams)
const openIssues = await anydb.findRecords({
type: "Issue",
condition: 'STATE=="OPEN"',
limit: 100,
});
output.set("openIssueCount", openIssues.length);
anydb.findRecordsPage
Purpose: Query records in pages using cursor-based pagination for large datasets.
// Signature: anydb.findRecordsPage(queryOrParams)
const page1 = await anydb.findRecordsPage({
type: "Issue",
condition: 'STATE=="OPEN"',
limit: 50,
});
const page2 = page1.nextCursor
? await anydb.findRecordsPage({
type: "Issue",
condition: 'STATE=="OPEN"',
limit: 50,
cursor: page1.nextCursor,
})
: { records: [] };
output.summary(`Fetched ${page1.records.length + page2.records.length} rows`);
anydb.getChildren
Purpose: Get child records under a parent record, optionally constrained by type and limit.
// Signature: anydb.getChildren(parentid, options?)
const children = await anydb.getChildren(input.recordId, {
typename: "Invoice",
limit: 25,
});
log(`Child records: ${children.length}`);
anydb.sendNotification
Purpose: Send in-app notifications to AnyDB users by email or user ID; mobile push is opt-in.
// Signature: anydb.sendNotification({ to, message, hyperlink?, pushnotification? })
const notify = await anydb.sendNotification({
to: "owner@company.com,67f1abc234def56789012345",
message: "Invoice requires review",
hyperlink: "https://app.anydb.io/team/db/67f1abc234def56789012345",
pushnotification: true,
});
output.set("notify", notify);
anydb.sendEmail
Purpose: Send email to recipients and emit in-app notification for resolvable AnyDB users.
// Signature: anydb.sendEmail({ to, subject, body })
const email = await anydb.sendEmail({
to: "finance@company.com,owner@company.com",
subject: "Daily invoice digest",
body: "The daily digest is ready.",
});
output.set("email", email);
anydb.createRecord
Purpose: Create a new record and optionally initialize cell values.
// Signature: anydb.createRecord({ name, parentid?, typeid?, typename?, cellValues? })
const created = await anydb.createRecord({
name: "Invoice Follow-up",
typename: "Invoice",
cellValues: {
Status: "Draft",
Priority: "High",
},
});
log(`Created: ${created.id}`);
anydb.updateRecord
Purpose: Update one existing record by ADO ID.
// Signature: anydb.updateRecord({ adoid, cellValues? })
const updated = await anydb.updateRecord({
adoid: "67f1abc234def56789012345",
cellValues: {
Status: "Ready",
},
});
log(`Updated: ${updated.id}`);
anydb.yield
Purpose: Cooperatively yield the event loop during heavy loops.
// Signature: anydb.yield()
const records = await anydb.getRecordsByType("Invoice");
for (const record of records) {
await anydb.yield();
// heavy per-record logic
}
output.summary(`Processed ${records.length}`);
output.summary
Purpose: Set a high-level completion summary for script output.
// Signature: output.summary(text)
output.summary("Script completed successfully");
output.markdown
Purpose: Emit markdown-formatted output.
// Signature: output.markdown(text)
output.markdown("## Invoice Summary\n- Open: 12\n- Closed: 34");
output.text
Purpose: Emit plain text output.
// Signature: output.text(text)
output.text("Invoice reconciliation completed");
output.json
Purpose: Emit structured JSON output.
// Signature: output.json(value)
output.json({
runAt: Date.now(),
success: true,
updatedCount: 42,
});
output.table
Purpose: Emit tabular output from row data.
// Signature: output.table(data)
output.table([
{ name: "Invoice A", amount: 1000, status: "Open" },
{ name: "Invoice B", amount: 500, status: "Closed" },
]);
output.csv
Purpose: Emit CSV-formatted output from row data.
// Signature: output.csv(data)
output.csv([
{ id: "INV-1", amount: 1000 },
{ id: "INV-2", amount: 500 },
]);
output.set
Purpose: Store custom named outputs for downstream actions.
// Signature: output.set(key, value)
output.set("updatedCount", 42);
output.set("status", "success");
fetch
Purpose: Call external HTTP APIs and use the response inside your script.
// Signature: fetch(url, options)
const response = await fetch("https://api.example.com/rates?base=USD");
if (!response.ok) {
throw new Error(`Rate API failed with ${response.status}`);
}
const payload = await response.json();
output.set("rates", payload);
yieldNow
Purpose: Yield control manually in custom loops (utility alias for cooperative yielding).
// Signature: yieldNow()
for (let i = 0; i < 1000; i++) {
if (i % 100 === 0) {
await yieldNow();
}
}
output.summary("Loop completed");
console.log / log
Purpose: Write diagnostic lines to script logs.
// Signatures: console.log(...args), log(...args)
console.log("Debug", { step: "start" });
log("A shorter log call");
Record-Level Helpers
Record objects returned by anydb.getRecordById(...), anydb.getRecordsByType(...), anydb.findRecords(...), and anydb.getChildren(...) expose:
record.getCell(refOrKey)record.getRefCellValue(refPath)whererefPathcan be a string path ("A1->B1->D1") or string array (["A1", "B1", "D1"])record.setCell(refOrKey, value)record.setCellProps(refOrKey, props)record.setCellRefValue(refOrKey, adoid)(loads target record and writes normalized REF payload + REF expression)record.lock(options?)/record.unlock(options?)whereoptionscan include{ recursive: true }to also update all descendants recursivelyrecord.lockCell(refOrKey)/record.unlockCell(refOrKey)
record.getCell
Purpose: Read one cell by key or cell position from an already loaded record.
// Signature: record.getCell(refOrKey)
const host = await anydb.getRecordById(input.recordId);
const cell = host ? host.getCell("Status") : null;
log(`Status cell value: ${cell ? cell.value : "N/A"}`);
if (cell) {
log(`Status cell pos: ${cell.pos}`);
log(`Status cell locked: ${Boolean(cell.props?.CELL_LOCKED?.value)}`);
}
Returned cell objects expose pos, key, format, value, and props.
record.getRefCellValue
Purpose: Get a referenced record's cell value, following a REF chain when each hop is itself a REF (for example: host.getRefCellValue("Source Item Ref->Location Ref->Name")).
// Signature: record.getRefCellValue(refPath)
const host = await anydb.getRecordById(input.recordId);
const refTargetValue = host
? await host.getRefCellValue("Source Item Ref->Location Ref->Name")
: null;
output.set("resolvedLocationName", refTargetValue);
record.setCell
Purpose: Set one cell value on the current record by key or position.
// Signature: record.setCell(refOrKey, value)
const host = await anydb.getRecordById(input.recordId);
if (host) {
await host.setCell("Validation State", "OK");
}
record.setCellProps
Purpose: Update props on an existing cell by key or position. This is the low-level helper behind cell locking.
// Signature: record.setCellProps(refOrKey, props)
const host = await anydb.getRecordById(input.recordId);
if (host) {
await host.setCellProps("Approval Status", {
CELL_LOCKED: {
type: "boolean",
value: true,
},
});
}
Use this when you want to set props explicitly. For lock/unlock behavior only, prefer record.lockCell(...) and record.unlockCell(...).
record.setCellRefValue
Purpose: Set a REF cell by target ADO ID and let runtime build the normalized REF value payload.
// Signature: record.setCellRefValue(refOrKey, adoid)
const host = await anydb.getRecordById(input.recordId);
if (host) {
await host.setCellRefValue("Source Location Ref", "6a0b639aa6afd82c3adfcbb0");
}
record.lock
Purpose: Lock the current record. Pass { recursive: true } to also lock child records recursively.
// Signature: record.lock(options?)
const host = await anydb.getRecordById(input.recordId);
if (host && !host.meta.locked) {
await host.lock({ recursive: true });
output.summary(`Locked record ${host.name}`);
}
Use record.lock() when you only want to lock the current record. Use record.lock({ recursive: true }) when the entire descendant tree should be locked together.
record.unlock
Purpose: Unlock the current record. Pass { recursive: true } to also unlock child records recursively.
// Signature: record.unlock(options?)
const host = await anydb.getRecordById(input.recordId);
if (host && host.meta.locked) {
await host.unlock({ recursive: true });
output.summary(`Unlocked record ${host.name}`);
}
Use record.unlock() when you only want to unlock the current record. Use record.unlock({ recursive: true }) when the entire descendant tree should be unlocked together.
record.lockCell
Purpose: Lock one cell by key or position. This is equivalent to setting CELL_LOCKED to true.
// Signature: record.lockCell(refOrKey)
const host = await anydb.getRecordById(input.recordId);
if (host) {
await host.lockCell("Approval Status");
}
record.unlockCell
Purpose: Unlock one cell by key or position. This is equivalent to setting CELL_LOCKED to false.
// Signature: record.unlockCell(refOrKey)
const host = await anydb.getRecordById(input.recordId);
if (host) {
await host.unlockCell("B4");
}
Locking Example: Lock A Record Tree And Specific Cells
Purpose: Show a full pattern for locking a record tree while selectively locking and unlocking cells on the host record.
const host = await anydb.getRecordById(input.recordId);
if (!host) {
output.summary("Record not found");
return;
}
await host.lock({ recursive: true });
await host.lockCell("Approval Status");
await host.lockCell("B4");
await host.unlockCell("Comments");
output.summary(
`Record tree for ${host.name} locked with targeted cell exceptions updated`,
);
Locking Example: Unlock A Record During Reopen Flow
Purpose: Reopen a record tree and make a previously protected cell editable again.
const host = await anydb.getRecordById(input.recordId);
if (!host) {
output.summary("Record not found");
return;
}
await host.unlock({ recursive: true });
await host.unlockCell("Approval Status");
output.summary(`Record tree for ${host.name} unlocked for editing`);
Input Shape By Context
Workflow Script Action
inputis action-driven payload (for example custom fields and upstream mapped values)input.recordIdmay or may not be present depending on trigger/action wiring- Use
output.summary(...),output.set(...), table/json/csv helpers for downstream actions and reports
Record customization Script
input.recordIdis the host record being recomputedinput.changedCellsis available to identify what changed on this update- Record customization logic runs on record updates, so always guard early and return quickly when not relevant
Record Data Access Model (Restricted)
Scripts do not get raw ADO objects. Each record returned by anydb.* APIs exposes only:
record.idrecord.namerecord.urlrecord.cellValues(field key/position values)record.metawith only these fields:parentassigneesnametemplateIDtemplateNametemplateVersionlockedcreatedcreatedbyupdatedupdatedby
No other metadata fields are exposed to script runtime.
How to Access Record Details
const recs = await anydb.findRecords({
type: "AnyDB Issue",
});
for (const record of recs) {
await anydb.yield();
// Basic record fields
const recordId = record.id;
const recordName = record.name;
const recordUrl = record.url;
// Cell values (by key or position)
const title = record.cellValues["TITLE"];
const ticketState = record.cellValues["TICKET STATE"];
const posA1Value = record.cellValues["A1"];
// Date fields need to be unix time stamp in seconds
const newDate = Date.now() / 1000;
// Allowed meta fields
const parents = record.meta.parent; // array of parent IDs
const assignees = record.meta.assignees; // { users: [...], groups: [...] }
const templateId = record.meta.templateID;
const templateName = record.meta.templateName;
const templateVersion = record.meta.templateVersion;
const isLocked = record.meta.locked;
const createdAt = record.meta.created;
const createdBy = record.meta.createdby;
const updatedAt = record.meta.updated;
const updatedBy = record.meta.updatedby;
log(
`Record ${recordId} | name=${recordName} | type=${templateName} (${templateId}) v${templateVersion} | locked=${isLocked} | state=${ticketState} | parentCount=${parents.length}`,
);
}
Script Structures
Structure: Workflow Script Action
// 1) Read workflow/action input
const typeName = String(input.typeName || "Invoice");
// 2) Query records
const records = await anydb.findRecords({
type: typeName,
condition: 'Status=="Open"',
});
// 3) Process and update as needed
let updated = 0;
for (const record of records) {
await anydb.yield();
await anydb.updateRecord({
adoid: record.id,
cellValues: {
Status: "Processed",
},
});
updated += 1;
}
// 4) Emit action outputs for downstream steps
output.summary(`Processed ${updated} records`);
output.set("updatedCount", updated);
Structure: Record customization Script
// 1) Monitor only specific triggering cells
const changedCells = Array.isArray(input.changedCells)
? input.changedCells
: [];
const watchedField = "Source item Ref";
const watchedTriggered = changedCells.some(
(ref) => String(ref || "").toLowerCase() === watchedField.toLowerCase(),
);
if (!watchedTriggered) {
return; // silent no-op on unrelated updates
}
// 2) Validate host record context
const recordId = String(input.recordId || "").trim();
if (!recordId) {
output.cellValue("BAD");
return;
}
// 3) Read host record and evaluate logic
const hostRecord = await anydb.getRecordById(recordId);
if (!hostRecord) {
output.cellValue("BAD ID");
return;
}
// Example: read and write on same host record
const sourceRef = hostRecord.getCell("Source item Ref");
if (!sourceRef || !sourceRef.value) {
output.cellValue("MISSING SOURCE");
return;
}
// Optional helper for REF writes
// await hostRecord.setCellRefValue("Source Location Ref", "6a0b639aa6afd82c3adfcbb0");
// Regular updates on host record
await hostRecord.setCell("Validation State", "OK");
// 4) Set script-cell value (or omit for silent behavior if desired)
output.cellValue("OK");
Script Authoring Policies
These policies are recommended when handcrafting scripts.
Workflow Script Action Policy
- Use only documented runtime APIs. Do not call unsupported helpers such as
anydb.fetchRecordsByTemplateName(...),anydb.query(...), oroutput.html(...). - All
anydb.*data APIs are async. Alwaysawaitreturned values before using them. - For criteria-based queries, prefer
anydb.findRecords(...)oranydb.findRecordsPage(...). - For full type scans without filters,
anydb.getRecordsByType(...)is acceptable. - For
findRecordsandfindRecordsPage, use exactly one type selector:typeORtypeNameORtemplateName. - Do not use
templateIdinside script runtime query params. - Use
conditionwith comparison operators (==,!=,>=,<=, etc.). Avoid single=in conditions. - In long loops, use
await anydb.yield()to keep execution responsive. - Prefer compact outputs for downstream actions:
output.summary(...)andoutput.set(...).
Record customization Script Policy
- Treat Record customization script as update-reactive logic that may run frequently.
- Use
input.changedCellsas trigger context and return early when monitored cells are not part of the update. - Resolve host context via
input.recordIdandawait anydb.getRecordById(input.recordId). - For host-record updates, prefer
record.setCell(...)andrecord.setCellRefValue(...). - Do not use
output.set(...),output.summary(...),output.markdown(...),output.text(...),output.json(...),output.table(...) - Keep Record customization scripts targeted and lightweight; avoid broad full-database scans unless absolutely necessary.
record.setCell(...)supports exactly two arguments:(refOrKey, value).
Create + Update Example
const invoice = await anydb.createRecord({
name: "Invoice Follow-up",
typename: "Invoice",
cellValues: {
Status: "Draft",
Currency: "USD",
},
});
await anydb.updateRecord({
adoid: invoice.id,
cellValues: {
Status: "Ready",
},
});
log(`Created and updated record: ${invoice.id}`);
Find + Update Loop Example
const invoices = await anydb.findRecords({
type: "Invoice",
condition: 'Status=="Open"',
});
let updated = 0;
for (const invoice of invoices) {
await anydb.yield();
const amount = Number(invoice.cellValues["Amount"] || 0);
const taxRate = Number(invoice.cellValues["Tax Rate"] || 0);
const total = amount + amount * taxRate;
await anydb.updateRecord({
adoid: invoice.id,
cellValues: {
Total: total,
Status: "Recalculated",
},
});
updated += 1;
}
log(`Updated ${updated} invoices`);
API Fetch + Update Example
const apiResponse = await fetch("https://currency.rate.com?base=USD");
if (!apiResponse.ok) {
throw new Error(`Exchange API failed: ${apiResponse.status}`);
}
const data = await apiResponse.json();
const conversionRate = Number(data?.rates?.EUR || 0);
if (!conversionRate) {
throw new Error("EUR conversion rate is missing from API response");
}
const invoices = await anydb.findRecords({
type: "Invoice",
condition: 'Currency=="USD" AND Status=="Open"',
});
for (const invoice of invoices) {
await anydb.yield();
const amountUSD = Number(invoice.cellValues["Amount"] || 0);
const amountEUR = Number((amountUSD * conversionRate).toFixed(2));
await anydb.updateRecord({
adoid: invoice.id,
cellValues: {
"Amount EUR": amountEUR,
"FX Rate EUR": conversionRate,
},
});
}
log(`Updated ${invoices.length} invoices with EUR conversion`);
CSV Example
const recs = await anydb.findRecords({
type: "Invoice",
condition: 'Status=="Overdue"',
});
const rows = recs.map((record) => ({
id: record.id,
name: record.name,
amount: record.cellValues["Amount"],
dueDate: record.cellValues["Due Date"],
}));
output.csv(rows);
log(`Generated CSV rows: ${rows.length}`);
Custom Outputs + Downstream Mapping Example
const overdue = await anydb.findRecords({
type: "Invoice",
condition: 'Status=="Overdue"',
});
output.set("overdueCount", overdue.length);
output.set(
"overdueHtml",
`<p>Overdue invoices: <strong>${overdue.length}</strong></p>`,
);
Get Children Example
const parentId = input.recordId;
const children = await anydb.getChildren(parentId, {
typename: "Invoice",
limit: 25,
});
for (const child of children) {
await anydb.yield();
await anydb.updateRecord({
adoid: child.id,
cellValues: {
Status: "Reviewed",
},
});
}
log(`Processed ${children.length} child records`);
Send Notification Example
const result = await anydb.sendNotification({
to: "owner@company.com,67f1abc234def56789012345",
message: "Invoice requires attention",
hyperlink: "https://app.anydb.io/workspace/invoices/67f1abc234def56789012345",
});
output.set("notificationResult", result);
log(
`Notifications requested=${result.requestedRecipients}, delivered=${result.notifiedUsers}`,
);
Notification notes:
toaccepts comma-separated AnyDB user emails and/or user IDs.- Only resolvable AnyDB users are notified.
hyperlinkis optional and defaults to empty when omitted.pushnotificationis optional and defaults tofalsein scripting runtime; setpushnotification: trueto explicitly send mobile push.
Send Email Example
const result = await anydb.sendEmail({
to: "owner@company.com,finance@company.com",
subject: "Invoice summary ready",
body: "Automation completed the invoice reconciliation run.",
});
output.set("emailResult", result);
log(
`Email recipients=${result.emailedRecipients}, notifiedUsers=${result.notifiedUsers}`,
);
Send email notes:
toaccepts comma-separated email recipients.- Email is sent using
email.general.message. - In-app notification is raised for recipients that resolve to AnyDB users by email.
- Return shape:
{ emailedRecipients, notifiedUsers }.
Runtime Safety & Abuse Prevention
- Script execution runs with timeout protection. Timeout limits are enforced by the host feature (for example, Script Action).
- Obvious tight infinite loops are blocked before execution (for example
while(true),while(1), andfor(;;)). - Loop statements (
for,while,do...while,for...of,for...in) must include at least oneawaitin the loop body. - For long-running loops, explicitly yield using
await anydb.yield()(orawait yieldNow()) to prevent event-loop starvation. - Security validation blocks restricted patterns such as
import,export,require(...),process, global/module internals, dynamic code execution (eval,Function), and constructor-escape patterns. - Only the supported runtime surface is available to scripts (
anydb,input,output,yieldNow,fetch,console,log;baseis retained as a compatibility alias). Filesystem and Node system-module access are not allowed.