Skip to main content

AnyDB Scripting

This page documents the reusable scripting runtime used by AnyDB script features, including Automation Script Action and Script Cell.

Example Scripts

Many Production Level Scripts are available for re-use

Automation Script Library

Supported Script Keywords (Globals)

KeywordTypeDescription
anydbobjectData API for finding, creating, and updating records
inputobjectHost-provided input payload (shape depends on script feature/context)
outputobjectReport/output helper (markdown, text, json, table, csv)
yieldNowfunctionCooperative yield helper for long loops (await yieldNow())
fetchfunctionHTTP client for external API calls
consoleobjectLogging object (console.log(...))
logfunctionShortcut for console.log(...)

Available Script APIs

  • anydb.getRecordById(adoid)
  • anydb.getRecordsByType(typeName)
  • anydb.findRecords(queryOrParams) where queryOrParams can be text or { type, condition, querystring }
  • anydb.findRecordsPage(queryOrParams) where queryOrParams can be text or { type, condition, querystring, cursor, limit }; returns paged results with nextCursor
  • anydb.getChildren(parentid, options?) where options can 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 with pushnotification: true.
  • anydb.sendEmail({ to, subject, body }) sends email and raises in-app notification for resolvable AnyDB users in to
  • anydb.createRecord({ name, parentid?, typeid?, typename?, cellValues? })
  • anydb.updateRecord({ adoid, cellValues? })
  • anydb.yield() (cooperative event-loop yield for long-running loops)
  • output.summary(text)
  • output.cellValue(value) (primarily for Script Cell return value)
  • 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(...) and log(...)

API Example Index

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.cellValue

Purpose: Set the Script Cell computed value.

// Signature: output.cellValue(value)
// Typical for Script Cell scripts
output.cellValue("OK");

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) where refPath can 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?) where options can include { recursive: true } to also update all descendants recursively
  • record.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

  • input is action-driven payload (for example custom fields and upstream mapped values)
  • input.recordId may or may not be present depending on trigger/action wiring
  • Use output.summary(...), output.set(...), table/json/csv helpers for downstream actions and reports

Script Cell

  • input.recordId is the host record being recomputed
  • input.changedCells is available to identify what changed on this update
  • Script Cell runs on record updates, so always guard early and return quickly when not relevant
  • Use output.cellValue(...) to set the script cell value

Record Data Access Model (Restricted)

Scripts do not get raw ADO objects. Each record returned by anydb.* APIs exposes only:

  • record.id
  • record.name
  • record.url
  • record.cellValues (field key/position values)
  • record.meta with only these fields:
    • parent
    • assignees
    • name
    • templateID
    • templateName
    • templateVersion
    • locked
    • created
    • createdby
    • updated
    • updatedby

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: Script Cell

// 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(...), or output.html(...).
  • All anydb.* data APIs are async. Always await returned values before using them.
  • For criteria-based queries, prefer anydb.findRecords(...) or anydb.findRecordsPage(...).
  • For full type scans without filters, anydb.getRecordsByType(...) is acceptable.
  • For findRecords and findRecordsPage, use exactly one type selector: type OR typeName OR templateName.
  • Do not use templateId inside script runtime query params.
  • Use condition with 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(...) and output.set(...).

Script Cell Policy

  • Treat Script Cell code as update-reactive logic that may run frequently.
  • Use input.changedCells as trigger context and return early when monitored cells are not part of the update.
  • Resolve host context via input.recordId and await anydb.getRecordById(input.recordId).
  • For host-record updates, prefer record.setCell(...) and record.setCellRefValue(...).
  • Use output.cellValue(...) only when the script should set or refresh the Script Cell value.
  • Do not use output.set(...), output.summary(...), output.markdown(...), output.text(...), output.json(...), output.table(...), or output.csv(...) in Script Cell mode.
  • Keep Script Cell 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:

  • to accepts comma-separated AnyDB user emails and/or user IDs.
  • Only resolvable AnyDB users are notified.
  • hyperlink is optional and defaults to empty when omitted.
  • pushnotification is optional and defaults to false in scripting runtime; set pushnotification: true to 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:

  • to accepts 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), and for(;;)).
  • Loop statements (for, while, do...while, for...of, for...in) must include at least one await in the loop body.
  • For long-running loops, explicitly yield using await anydb.yield() (or await 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; base is retained as a compatibility alias). Filesystem and Node system-module access are not allowed.