Workflow ctx
The ctx a workflow's run(ctx, event) receives on the server. Two shapes exist: the @app/sdk shape for defineWorkflow modules (what generated apps use) and the native shape for .workflow.js modules.
On this page
- defineWorkflow
- defineWorkflow
- ctx (defineWorkflow modules)
- ctx.db.list
- ctx.db.get
- ctx.db.create
- ctx.db.update
- ctx.db.updateWhere
- ctx.db.remove
- ctx.http.fetch
- ctx.notify
- ctx.ai.ask
- ctx.ai.chat
- ctx.email
- ctx.emailRole
- ctx.messages.render
- ctx.messages.email
- ctx.files.put
- ctx.files.get
- ctx.files.list
- ctx.files.remove
- ctx.secrets.get
- ctx.vars.get
- ctx.vars.set
- ctx.integrations.call
- ctx.workflows.trigger
- ctx.log
- ctx.now
- ctx (native .workflow.js modules)
- ctx.projectId
- ctx.runId
- ctx.trigger
- ctx.queries.run
- ctx.db.query
- ctx.db.exec
- ctx.api.list
- ctx.api.get
- ctx.api.create
- ctx.api.update
- ctx.api.delete
- ctx.http.fetch
- ctx.files.read
- ctx.files.write
- ctx.files.list
- ctx.files.delete
- ctx.log.info
- ctx.log.warn
- ctx.log.error
- ctx.topics.publish
- ctx.topics.subscribe
- ctx.secrets.get
- ctx.ai.ask
- ctx.ai.chat
- ctx.integrations.invoke
- ctx.messages.email
- ctx.messages.sms
- ctx.messages.whatsapp
- ctx.messages.telegram
- ctx.schedules.create
- ctx.schedules.update
- ctx.schedules.list
- ctx.schedules.delete
- ctx.users.list
- ctx.users.get
- ctx.users.update
- ctx.users.setAdmin
- ctx.users.disable
- ctx.rbac.listRoles
- ctx.rbac.createRole
- ctx.rbac.assign
- ctx.rbac.revoke
- ctx.rbac.hasPermission
- ctx.callWorkflow
- Types
A module that exports defineWorkflow(...) gets the @app/sdk ctx (ctx.db.list, ctx.email, ctx.integrations.call, ctx.log(note)). The runner builds it over the native ctx (services/api/src/app-runtime/workflows/sdk-compat.ts), so both run against the same Postgres, queue and vault.
Identity: runAs "user" (the default for entity_event and ui triggers) scopes ctx.db to the actor's row-level security; "system" (the default for cron and webhook) reads past it. Global fetch is not available: call out with ctx.http.fetch.
defineWorkflow
defineWorkflowfunction
Declare a workflow module: a declarative trigger (database event, UI call, webhook or schedule) and a run(ctx, event) body in plain code. The host imports the default export.
defineWorkflow(def: WorkflowDef): WorkflowDefWithSdkParameters
| Name | Type | Description |
|---|---|---|
defrequired | WorkflowDef | { id (kebab-case), name, description, trigger, retry?, runAs?, run }. trigger: { type: "entity_event", entity, event } | { type: "ui" } | { type: "webhook", path } | { type: "cron", schedule }. retry: { max, backoffMs? } (max counts the first attempt). |
- Returns
- The definition, marked for the @app/sdk ctx.
- Errors
- "defineWorkflow: definition must be an object".
- "defineWorkflow: id \"<id>\" must be kebab-case".
- "defineWorkflow(<id>): trigger.type is required".
- Permissions
- runAs "user" scopes ctx.db to the trigger's actor; "system" reads past row-level security. Default: user for entity_event and ui, system for cron and webhook.
import { defineWorkflow } from "@app/sdk";
export default defineWorkflow({
id: "overdue-reminder",
name: "Overdue reminder",
description: "Nudges assignees when a task slips past its due date.",
trigger: { type: "cron", schedule: "daily 08:00" },
retry: { max: 3, backoffMs: 30_000 },
async run(ctx) {
const today = ctx.now().toISOString().slice(0, 10);
const overdue = await ctx.db.list("tasks", {
filter: [{ field: "due_date", op: "lt", value: today }, { field: "status", op: "neq", value: "done" }],
});
for (const t of overdue) await ctx.notify({ user: String(t.assignee), title: `"${t.title}" is overdue` });
ctx.log(`${overdue.length} overdue task(s) swept`);
},
});Source: services/api/src/platform-sdk/workflow.ts:215
ctx (defineWorkflow modules)
ctx.db.listmethod
Rows of a table matching a query. Comparison and set clauses, the sort and the offset run in SQL; date-relative ops (today, this_week, last_n_days…) are applied after.
ctx.db.list(entity: string, query?: ListQuery): Promise<Row[]>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
queryoptional | ListQuery | { filter?, sort?, limit?, offset? }. filter is FilterClause[] or a plain { field: value } object (equality). limit defaults to 1000; the data API serves at most 500 per call, so page with offset. |
- Returns
- Row[].
- Errors
- The data API's message when the read fails (unknown table, refused by access rules).
- Permissions
- ctx.db uses the run's identity: under runAs "user" the actor's row-level security and access rules apply; under "system" reads bypass row-level security (writes still stamp the owning user when the row belongs to one).
const open = await ctx.db.list("tickets", {
filter: [{ field: "status", op: "eq", value: "open" }, { field: "created_at", op: "last_n_days", value: 7 }],
sort: { field: "created_at", dir: "desc" },
limit: 100,
});Source: services/api/src/platform-sdk/workflow.ts:77
ctx.db.getmethod
One row by id.
ctx.db.get(entity: string, id: string): Promise<Row | null>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row id. |
- Returns
- The row, or null when it doesn't exist, is hidden, or the read fails.
- Errors
- Does not throw.
- Permissions
- ctx.db uses the run's identity: under runAs "user" the actor's row-level security and access rules apply; under "system" reads bypass row-level security (writes still stamp the owning user when the row belongs to one).
const order = await ctx.db.get("orders", String(event.payload.order));Source: services/api/src/platform-sdk/workflow.ts:78
ctx.db.createmethod
Insert a row. Fires the table's row-changed event like any other write.
ctx.db.create(entity: string, row: Record<string, unknown>): Promise<Row>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
rowrequired | Record<string, unknown> | The row's fields. |
- Returns
- The created row.
- Errors
- "create <entity> failed" or the data API's message (constraint violation, refused by access rules).
- Permissions
- ctx.db uses the run's identity: under runAs "user" the actor's row-level security and access rules apply; under "system" reads bypass row-level security (writes still stamp the owning user when the row belongs to one).
await ctx.db.create("audit_log", { action: "refund", order: event.payload.id, at: ctx.now().toISOString() });Source: services/api/src/platform-sdk/workflow.ts:79
ctx.db.updatemethod
Patch a row.
ctx.db.update(entity: string, id: string, patch: Record<string, unknown>): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row id. |
patchrequired | Record<string, unknown> | Fields to change. |
- Returns
- Resolves when saved.
- Errors
- "update <entity>/<id> failed" or the data API's message.
- Permissions
- ctx.db uses the run's identity: under runAs "user" the actor's row-level security and access rules apply; under "system" reads bypass row-level security (writes still stamp the owning user when the row belongs to one).
await ctx.db.update("tickets", String(event.payload.id), { triaged: true });Source: services/api/src/platform-sdk/workflow.ts:80
ctx.db.updateWheremethod
Patch every row matching all clauses (up to 1000 rows, one update each).
ctx.db.updateWhere(entity: string, filter: FilterClause[], patch: Record<string, unknown>): Promise<number>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
filterrequired | FilterClause[] | FilterClause[] (all must match). |
patchrequired | Record<string, unknown> | Fields to set on each row. |
- Returns
- How many rows were updated.
- Errors
- "updateWhere <entity> failed", or "update <entity>/<id> failed" for the first row that fails (earlier rows stay updated).
- Permissions
- ctx.db uses the run's identity: under runAs "user" the actor's row-level security and access rules apply; under "system" reads bypass row-level security (writes still stamp the owning user when the row belongs to one).
const n = await ctx.db.updateWhere("sessions",
[{ field: "expires_at", op: "lt", value: ctx.now().toISOString() }],
{ active: false });
ctx.log(`expired ${n} session(s)`);Source: services/api/src/platform-sdk/workflow.ts:82
ctx.db.removemethod
Delete a row.
ctx.db.remove(entity: string, id: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row id. |
- Returns
- Resolves when deleted.
- Errors
- "delete <entity>/<id> failed" or the data API's message.
- Permissions
- ctx.db uses the run's identity: under runAs "user" the actor's row-level security and access rules apply; under "system" reads bypass row-level security (writes still stamp the owning user when the row belongs to one).
await ctx.db.remove("drafts", String(event.payload.id));Source: services/api/src/platform-sdk/workflow.ts:83
ctx.http.fetchmethod
Outbound HTTP: the only way to call out (global fetch is not available). Objects in body are JSON-encoded.
ctx.http.fetch(url: string, init?: { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; headers?: Record<string, string>; body?: unknown }): Promise<{ status: number; ok: boolean; text: string; json<T = unknown>(): T | null }>Parameters
| Name | Type | Description |
|---|---|---|
urlrequired | string | An https URL on a public host. |
initoptional | { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; headers?: Record<string, string>; body?: unknown } | { method?, headers?, body? }. method defaults to GET. |
- Returns
- { status, ok, text, json() }. json() returns null when the body isn't JSON. A non-2xx answer resolves; check ok.
- Errors
- "Invalid URL", "Only https URLs are allowed in workflows", "Blocked host" (localhost, private ranges, metadata hosts, or a name resolving to a private address).
- A network error or timeout (30 seconds).
- QuotaExceededError past the project's outbound requests per minute.
- Permissions
- Server-side only. Keys come from ctx.secrets, never from the browser.
const key = await ctx.secrets.get("SHIPPO_KEY");
const res = await ctx.http.fetch("https://api.goshippo.com/shipments/", {
method: "POST",
headers: { Authorization: `ShippoToken ${key}` },
body: { address_to: event.payload.address, parcels: [event.payload.parcel] },
});
if (!res.ok) throw new Error(`Shippo answered ${res.status}`);
return res.json<{ rates: unknown[] }>();Source: services/api/src/platform-sdk/workflow.ts:87
ctx.notifymethod
An in-app notification for the bell. Targets one account (user), else one role, else everyone.
ctx.notify(n: { title: string; body?: string; role?: string; user?: string }): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
nrequired | { title: string; body?: string; role?: string; user?: string } | { title, body?, role?, user? }. user is an app account id; role a persona id. |
- Returns
- Resolves when published. The realtime ping is content-free; title and body travel only through GET /__appbricx/notifications, which filters by role and person.
- Errors
- Does not throw.
- Permissions
- The notification feed is filtered server-side: a role-targeted note reaches only sessions holding that role.
await ctx.notify({ role: "staff", title: "New order", body: `Order ${event.payload.id} needs packing` });Source: services/api/src/platform-sdk/workflow.ts:95
ctx.ai.askmethod
One AI answer for one prompt, with the project's own model key or plan credits: the same key choice, budgets and metering as app.ai.
ctx.ai.ask(prompt: string, opts?: { system?: string; maxTokens?: number }): Promise<string>Parameters
| Name | Type | Description |
|---|---|---|
promptrequired | string | What to ask. |
optsoptional | { system?: string; maxTokens?: number } | { system?, maxTokens? }. |
- Returns
- The answer text.
- Errors
- "ctx.ai: <reason>" when AI is off for the project, the budget is spent or the provider fails.
- Permissions
- Metered to the project.
const label = await ctx.ai.ask(`Classify as billing, bug or other: ${event.payload.subject}`, { maxTokens: 5 });
await ctx.db.update("tickets", String(event.payload.id), { category: label.trim().toLowerCase() });Source: services/api/src/platform-sdk/workflow.ts:100
ctx.ai.chatmethod
The next assistant turn of a conversation.
ctx.ai.chat(messages: Array<{ role: "user" | "assistant"; content: string }>, opts?: { system?: string; maxTokens?: number }): Promise<string>Parameters
| Name | Type | Description |
|---|---|---|
messagesrequired | Array<{ role: "user" | "assistant"; content: string }> | [{ role: "user" | "assistant", content }]. |
optsoptional | { system?: string; maxTokens?: number } | { system?, maxTokens? }. |
- Returns
- The reply text.
- Errors
- "ctx.ai: <reason>" as ask.
- Permissions
- Metered to the project.
const reply = await ctx.ai.chat(thread.map((m) => ({ role: m.from === "bot" ? "assistant" : "user", content: String(m.text) })),
{ system: "You answer questions about opening hours only." });Source: services/api/src/platform-sdk/workflow.ts:101
ctx.emailmethod
Send an email through the app's outbox: a bound email integration (Resend, SendGrid, Gmail) when one is connected, else the platform's email queue.
ctx.email(e: { to: string; subject: string; body?: string; html?: string }): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
erequired | { to: string; subject: string; body?: string; html?: string } | { to, subject, body?, html? }. |
- Returns
- Resolves when delivery is accepted.
- Errors
- "ctx.email to <to> failed (delivery reported failure)" or the transport's error: the step fails. Catch it if the workflow should continue.
- QuotaExceededError past the project's emails per day.
- Permissions
- Server-side. Addresses come from your data; nothing is sent from the browser.
await ctx.email({
to: String(event.payload.email),
subject: "Your booking is confirmed",
html: `<p>See you on ${event.payload.date}.</p>`,
});Source: services/api/src/platform-sdk/workflow.ts:105
ctx.emailRolemethod
Email every app user holding a role (a persona id). Addresses are resolved on the platform and never handed to the workflow.
ctx.emailRole(e: { role: string; subject: string; body?: string; html?: string }): Promise<number>Parameters
| Name | Type | Description |
|---|---|---|
erequired | { role: string; subject: string; body?: string; html?: string } | { role, subject, body?, html? }. |
- Returns
- How many emails were sent (0 when no user holds the role; at most 500).
- Errors
- "ctx.emailRole: role is required"; "ctx.emailRole(<role>): could not read the app's users"; delivery failures as ctx.email.
- Permissions
- Reads the tenant users table with a fixed, narrow query; the workflow only sees the count.
const sent = await ctx.emailRole({ role: "staff", subject: "Daily digest", html: digestHtml });
ctx.log(`digest sent to ${sent} staff`);Source: services/api/src/platform-sdk/workflow.ts:111
ctx.messages.rendermethod
Render a named message template. Declared in the type; the @app/sdk host doesn't provide named templates yet.
ctx.messages.render(name: string, vars?: Record<string, unknown>): Promise<{ subject: string; text: string; html?: string }>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The template name. |
varsoptional | Record<string, unknown> | Values for its {{placeholders}}. |
- Returns
- { subject, text, html? } (on hosts that support templates).
- Errors
- Always, on the current host: "ctx.messages.render(\"<name>\"): named templates are not available in this runtime yet — build the message inline and send via ctx.email".
- Permissions
- None.
if (ctx.messages) {
try { await ctx.messages.render("welcome", { name }); } catch { /* fall back to ctx.email */ }
}Source: services/api/src/platform-sdk/workflow.ts:136
ctx.messages.emailmethod
Send an email. The inline shape ({ to, subject, html }) delivers exactly like ctx.email; the template shape is refused on the current host.
ctx.messages.email(e: { template: string; to: string; vars?: Record<string, unknown>; from?: string }): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
erequired | { template: string; to: string; vars?: Record<string, unknown>; from?: string } | { template, to, vars?, from? } (template shape) or { to, subject, body?, html? } (inline shape). |
- Returns
- Resolves when delivery is accepted.
- Errors
- "ctx.messages.email: template \"<name>\" — named templates are not available in this runtime yet; use ctx.email({ to, subject, html })".
- "ctx.messages.email: missing subject (inline shape needs { to, subject, html })"; delivery failures as ctx.email.
- Permissions
- As ctx.email.
await ctx.messages?.email({ to: customer.email, subject: "Receipt", html } as never);Source: services/api/src/platform-sdk/workflow.ts:137
ctx.files.putmethod
Write a small text file in the app's workflow file store (object storage in production, local disk in development).
ctx.files.put(name: string, content: string, contentType?: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | A relative path, e.g. "exports/2026-10.csv". |
contentrequired | string | The text. |
contentTypeoptional | string | Ignored by the server host (stored as text/plain). |
- Returns
- Resolves when stored.
- Errors
- "Invalid file path" for absolute paths or .. escapes.
- Permissions
- Per-app store, outside the project's source tree; not visible to app users.
await ctx.files.put(`exports/${month}.csv`, rows.map((r) => [r.id, r.total].join(",")).join("\n"));Source: services/api/src/platform-sdk/workflow.ts:141
ctx.files.getmethod
Read a file from the workflow file store.
ctx.files.get(name: string): Promise<{ content: string; contentType?: string } | null>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The relative path. |
- Returns
- { content } or null when missing.
- Errors
- Does not throw.
- Permissions
- Per-app store.
const cursor = (await ctx.files.get("sync/cursor.txt"))?.content ?? "0";Source: services/api/src/platform-sdk/workflow.ts:142
ctx.files.listmethod
Names in the store's top folder, optionally filtered by prefix.
ctx.files.list(prefix?: string): Promise<string[]>Parameters
| Name | Type | Description |
|---|---|---|
prefixoptional | string | Keep names starting with this. |
- Returns
- string[] (one level: files and folder names).
- Errors
- Does not throw.
- Permissions
- Per-app store.
const exports = await ctx.files.list("exports");Source: services/api/src/platform-sdk/workflow.ts:143
ctx.files.removemethod
Delete a file.
ctx.files.remove(name: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The relative path. |
- Returns
- Resolves when deleted.
- Errors
- On local disk, deleting a missing file throws ENOENT; on object storage it is a no-op.
- Permissions
- Per-app store.
await ctx.files.remove("sync/cursor.txt");Source: services/api/src/platform-sdk/workflow.ts:144
ctx.secrets.getmethod
Read a secret the app owner configured (project env vars for prod or all environments; workspace secrets apply when the project has none of that name). Never log the value.
ctx.secrets.get(name: string): Promise<string | null>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The secret name, e.g. "STRIPE_KEY". |
- Returns
- The value, or null when it isn't set (or the app doesn't declare it in secrets.refs.json).
- Errors
- Does not throw.
- Permissions
- Read-only. Set and rotate secrets in the builder, with the CLI (appbricx secrets set) or the secrets MCP tool.
const key = await ctx.secrets.get("OPENWEATHER_KEY");
if (!key) throw new Error("OPENWEATHER_KEY is not set");Source: services/api/src/platform-sdk/workflow.ts:147
ctx.vars.getmethod
Read an app variable: workflow-owned, mutable state such as counters, cursors and feature switches, kept in the tenant database.
ctx.vars.get(name: string): Promise<string | null>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The variable name. |
- Returns
- The string value, or null.
- Errors
- Does not throw.
- Permissions
- Runs under the workflow's identity; not exposed to app users.
const since = (await ctx.vars.get("last_sync")) ?? "1970-01-01";Source: services/api/src/platform-sdk/workflow.ts:150
ctx.vars.setmethod
Write an app variable (upsert). Non-strings are converted with String().
ctx.vars.set(name: string, value: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The variable name. |
valuerequired | string | The value. |
- Returns
- Resolves when stored.
- Errors
- Does not throw.
- Permissions
- As vars.get.
await ctx.vars.set("last_sync", ctx.now().toISOString());Source: services/api/src/platform-sdk/workflow.ts:151
ctx.integrations.callmethod
Call a connected integration. payload.action picks the action; without it the integration's canonical action is used (resend, sendgrid, gmail: send_email; twilio: send_sms; slack: send_channel_message; telegram_bot: send_text_message; whatsapp: send_message; anything else: run). The rest of payload is the action's input.
ctx.integrations.call(name: string, payload: Record<string, unknown>): Promise<unknown>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The integration id, e.g. "slack". |
payloadrequired | Record<string, unknown> | { action?, ...input }. |
- Returns
- The runner's result: { success, output, error? }.
- Errors
- A thrown runner error. A missing connection or unknown action resolves with success: false and the reason in error.
- Permissions
- Uses the credential the owner connected (the project's, else the workspace's). The browser never sees it.
const res = await ctx.integrations.call("slack", {
action: "send_channel_message",
channel: "C0123456",
text: `New signup: ${event.payload.email}`,
}) as { success: boolean; error?: string };
if (!res.success) ctx.log(`Slack failed: ${res.error}`);Source: services/api/src/platform-sdk/workflow.ts:155
ctx.workflows.triggermethod
Queue another workflow with a payload (chains are depth-limited to 3).
ctx.workflows.trigger(workflowId: string, payload?: Record<string, unknown>): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
workflowIdrequired | string | The workflow id. |
payloadoptional | Record<string, unknown> | Its event payload. |
- Returns
- Resolves when queued (not when it finishes).
- Errors
- "Max workflow call depth 3 exceeded".
- Permissions
- The child run executes under the same project.
await ctx.workflows.trigger("send-invoice", { order: event.payload.id });Source: services/api/src/platform-sdk/workflow.ts:158
ctx.logmethod
Add a line to the run trace. Say what happened in plain language.
ctx.log(note: string): voidParameters
| Name | Type | Description |
|---|---|---|
noterequired | string | The line. |
- Returns
- Nothing.
- Errors
- Does not throw.
- Permissions
- Run logs are visible to the project's builders (logs, run get).
ctx.log(`charged ${order.id} ${amount} cents`);Source: services/api/src/platform-sdk/workflow.ts:160
ctx.nowmethod
The current time. Mockable in dry runs and tests.
ctx.now(): Date- Returns
- Date.
- Errors
- Does not throw.
- Permissions
- None.
const today = ctx.now().toISOString().slice(0, 10);Source: services/api/src/platform-sdk/workflow.ts:162
ctx (native .workflow.js modules)
ctx.projectIdproperty
The project id.
ctx.projectId: string- Returns
- string.
- Errors
- Does not throw.
- Permissions
- Read-only.
ctx.log.info("run", { project: ctx.projectId });Source: services/api/src/app-runtime/runtime-types.ts:59
ctx.runIdproperty
This run's id (as in logs and GET /__appbricx/runtime/runs/:runId).
ctx.runId: string- Returns
- string.
- Errors
- Does not throw.
- Permissions
- Read-only.
ctx.log.info("started", { run: ctx.runId });Source: services/api/src/app-runtime/runtime-types.ts:60
ctx.triggerproperty
What started the run: { type: "manual" | "cron" | "webhook" | "topic" | "cdc" | "call", payload?, meta? }.
ctx.trigger: WorkflowTrigger- Returns
- WorkflowTrigger.
- Errors
- Does not throw.
- Permissions
- Read-only.
if (ctx.trigger.type === "webhook") {
const body = (ctx.trigger.payload as { body?: unknown }).body;
}Source: services/api/src/app-runtime/runtime-types.ts:61
ctx.queries.runmethod
Run a named query (a .sql file under .appbricx/backend/queries) with parameters.
ctx.queries.run<T = Record<string, unknown>>(name: string, params?: Record<string, unknown>): Promise<RuntimeResult<T>>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The query name, e.g. "reports/monthly". |
paramsoptional | Record<string, unknown> | The declared parameters. |
- Returns
- RuntimeResult<T>: { ok, rows, rowCount, error? }. Failures resolve with ok: false.
- Errors
- Does not throw.
- Permissions
- The query must allow the "workflow" caller. Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
const res = await ctx.queries.run<{ total: number }>("reports/monthly", { month: "2026-10" });
if (!res.ok) throw new Error(res.error?.message);Source: services/api/src/app-runtime/runtime-types.ts:64
ctx.db.querymethod
Run a parameterized SQL statement on the app's tenant database and get its rows.
ctx.db.query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<RuntimeResult<T>>Parameters
| Name | Type | Description |
|---|---|---|
sqlrequired | string | The statement, with $1, $2… placeholders. |
paramsoptional | unknown[] | Values for the placeholders. |
- Returns
- RuntimeResult<T>. Failures resolve with ok: false and error.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
const res = await ctx.db.query<{ n: number }>("SELECT count(*)::int AS n FROM tickets WHERE status = $1", ["open"]);
ctx.log.info("open tickets", { n: res.rows[0]?.n });Source: services/api/src/app-runtime/runtime-types.ts:71
ctx.db.execmethod
Execute a statement (or script) without parameters.
ctx.db.exec(sql: string): Promise<RuntimeResult>Parameters
| Name | Type | Description |
|---|---|---|
sqlrequired | string | The SQL. |
- Returns
- RuntimeResult (rowCount for writes). Failures resolve with ok: false.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
await ctx.db.exec("REFRESH MATERIALIZED VIEW monthly_totals");Source: services/api/src/app-runtime/runtime-types.ts:75
ctx.api.listmethod
List rows through the data API (the same CRUD layer as /__appbricx/api/v1).
ctx.api.list<T = Record<string, unknown>>(table: string, opts?: { limit?: number; offset?: number; where?: Record<string, unknown>; filter?: CrudFilterClause[]; sort?: { field: string; dir: "asc" | "desc" } }): Promise<RuntimeResult<T>>Parameters
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table name. |
optsoptional | { limit?: number; offset?: number; where?: Record<string, unknown>; filter?: CrudFilterClause[]; sort?: { field: string; dir: "asc" | "desc" } } | { limit?, offset?, where?, filter?, sort? }. where is equality; filter is the rich clause grammar. |
- Returns
- RuntimeResult<T>.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
const res = await ctx.api.list("orders", { filter: [{ field: "total", op: "gte", value: 100 }], sort: { field: "total", dir: "desc" }, limit: 10 });Source: services/api/src/app-runtime/runtime-types.ts:79
ctx.api.getmethod
One row by id through the data API.
ctx.api.get<T = Record<string, unknown>>(table: string, id: string): Promise<RuntimeResult<T>>Parameters
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table name. |
idrequired | string | The row id. |
- Returns
- RuntimeResult<T> with zero or one row.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
const res = await ctx.api.get("orders", id);
const order = res.rows[0];Source: services/api/src/app-runtime/runtime-types.ts:90
ctx.api.createmethod
Insert a row through the data API (emits a change event).
ctx.api.create<T = Record<string, unknown>>(table: string, data: Record<string, unknown>): Promise<RuntimeResult<T>>Parameters
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table name. |
datarequired | Record<string, unknown> | The row. |
- Returns
- RuntimeResult<T> with the created row.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
await ctx.api.create("audit_log", { action: "export", at: new Date().toISOString() });Source: services/api/src/app-runtime/runtime-types.ts:91
ctx.api.updatemethod
Patch a row through the data API.
ctx.api.update<T = Record<string, unknown>>(table: string, id: string, data: Record<string, unknown>): Promise<RuntimeResult<T>>Parameters
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table name. |
idrequired | string | The row id. |
datarequired | Record<string, unknown> | Fields to change. |
- Returns
- RuntimeResult<T>.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
await ctx.api.update("tickets", id, { escalated: true });Source: services/api/src/app-runtime/runtime-types.ts:95
ctx.api.deletemethod
Delete a row through the data API.
ctx.api.delete(table: string, id: string): Promise<RuntimeResult>Parameters
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table name. |
idrequired | string | The row id. |
- Returns
- RuntimeResult.
- Errors
- Does not throw.
- Permissions
- Runs with the run's app identity (app.user_id and role) unless the run is elevated, in which case it bypasses row-level security.
await ctx.api.delete("drafts", id);Source: services/api/src/app-runtime/runtime-types.ts:100
ctx.http.fetchmethod
Outbound HTTPS with an SSRF guard and a 30 second timeout. Takes and returns the standard RequestInit and Response.
ctx.http.fetch(url: string, init?: RequestInit): Promise<Response>Parameters
| Name | Type | Description |
|---|---|---|
urlrequired | string | An https URL on a public host. |
initoptional | RequestInit | A standard RequestInit. |
- Returns
- Response.
- Errors
- "Invalid URL", "Only https URLs are allowed in workflows", "Blocked host", "Blocked host (resolves to a private address)", network errors and timeouts, QuotaExceededError past the per-minute budget.
- Permissions
- Server-side only.
const res = await ctx.http.fetch("https://api.github.com/repos/acme/app/releases/latest", { headers: { Accept: "application/json" } });
const release = await res.json();Source: services/api/src/app-runtime/runtime-types.ts:104
ctx.files.readmethod
Read a text file from the workflow file store.
ctx.files.read(relPath: string): Promise<string>Parameters
| Name | Type | Description |
|---|---|---|
relPathrequired | string | A relative path. |
- Returns
- The content.
- Errors
- "ENOENT: no such file — <path>" (or the fs error locally); "Invalid file path".
- Permissions
- Per-app store.
const csv = await ctx.files.read("imports/today.csv");Source: services/api/src/app-runtime/runtime-types.ts:108
ctx.files.writemethod
Write a text file.
ctx.files.write(relPath: string, content: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
relPathrequired | string | A relative path. |
contentrequired | string | The text. |
- Returns
- Resolves when stored.
- Errors
- "Invalid file path".
- Permissions
- Per-app store.
await ctx.files.write("reports/latest.json", JSON.stringify(report));Source: services/api/src/app-runtime/runtime-types.ts:109
ctx.files.listmethod
One-level entry names under a folder.
ctx.files.list(relPath?: string): Promise<string[]>Parameters
| Name | Type | Description |
|---|---|---|
relPathoptional | string | The folder (default the root). |
- Returns
- string[].
- Errors
- "Invalid file path".
- Permissions
- Per-app store.
const names = await ctx.files.list("reports");Source: services/api/src/app-runtime/runtime-types.ts:110
ctx.files.deletemethod
Delete a file (idempotent on object storage).
ctx.files.delete(relPath: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
relPathrequired | string | A relative path. |
- Returns
- Resolves when deleted.
- Errors
- ENOENT on local disk for a missing file; "Invalid file path".
- Permissions
- Per-app store.
await ctx.files.delete("reports/old.json");Source: services/api/src/app-runtime/runtime-types.ts:111
ctx.log.infomethod
Append an info line (with optional structured data) to the run log.
ctx.log.info(message: string, data?: Record<string, unknown>): voidParameters
| Name | Type | Description |
|---|---|---|
messagerequired | string | The line. |
dataoptional | Record<string, unknown> | Structured fields shown with it. |
- Returns
- Nothing.
- Errors
- Does not throw.
- Permissions
- Visible to the project's builders.
ctx.log.info("synced", { count: rows.length });Source: services/api/src/app-runtime/runtime-types.ts:115
ctx.log.warnmethod
Append a warning line to the run log.
ctx.log.warn(message: string, data?: Record<string, unknown>): voidParameters
| Name | Type | Description |
|---|---|---|
messagerequired | string | The line. |
dataoptional | Record<string, unknown> | Structured fields. |
- Returns
- Nothing.
- Errors
- Does not throw.
- Permissions
- Visible to the project's builders.
ctx.log.warn("rate limited, retrying", { attempt });Source: services/api/src/app-runtime/runtime-types.ts:116
ctx.log.errormethod
Append an error line to the run log (the run keeps going; throw to fail it).
ctx.log.error(message: string, data?: Record<string, unknown>): voidParameters
| Name | Type | Description |
|---|---|---|
messagerequired | string | The line. |
dataoptional | Record<string, unknown> | Structured fields. |
- Returns
- Nothing.
- Errors
- Does not throw.
- Permissions
- Visible to the project's builders.
ctx.log.error("webhook body missing id", { body });Source: services/api/src/app-runtime/runtime-types.ts:117
ctx.topics.publishmethod
Publish a message on a topic. Subscribers of /__appbricx/topics/:name/subscribe receive it.
ctx.topics.publish(name: string, payload: unknown): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The topic, e.g. "app:orders". |
payloadrequired | unknown | Any JSON value. |
- Returns
- Resolves when published.
- Errors
- Does not throw.
- Permissions
- Workflows are trusted publishers, including row-changed:* topics.
await ctx.topics.publish("app:kitchen", { order: id, status: "ready" });Source: services/api/src/app-runtime/runtime-types.ts:121
ctx.topics.subscribemethod
Listen to a topic for the rest of the run.
ctx.topics.subscribe(name: string, handler: (ev: unknown) => void): () => voidParameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The topic. |
handlerrequired | (ev: unknown) => void | Called with each payload. |
- Returns
- An unsubscribe function.
- Errors
- Does not throw.
- Permissions
- Same project only.
const off = ctx.topics.subscribe("app:kitchen", (ev) => ctx.log.info("event", { ev }));
// ... later
off();Source: services/api/src/app-runtime/runtime-types.ts:122
ctx.secrets.getmethod
Read a secret (project env vars for production or all; workspace secrets as a fallback).
ctx.secrets.get(name: string): Promise<string | null>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The secret name. |
- Returns
- The value or null.
- Errors
- Does not throw.
- Permissions
- Read-only; values are never logged by the platform.
const token = await ctx.secrets.get("AIRTABLE_TOKEN");Source: services/api/src/app-runtime/runtime-types.ts:126
ctx.ai.askmethod
One AI answer (same plane as app.ai: key choice, budgets, allow-list, metering).
ctx.ai.ask(prompt: string, opts?: { system?: string; maxTokens?: number }): Promise<string>Parameters
| Name | Type | Description |
|---|---|---|
promptrequired | string | What to ask. |
optsoptional | { system?: string; maxTokens?: number } | { system?, maxTokens? }. |
- Returns
- The answer text.
- Errors
- "ctx.ai: <reason>".
- Permissions
- Metered to the project.
const tldr = await ctx.ai.ask(`TL;DR: ${article}`, { maxTokens: 80 });Source: services/api/src/app-runtime/runtime-types.ts:133
ctx.ai.chatmethod
The next assistant turn of a conversation.
ctx.ai.chat(messages: Array<{ role: "user" | "assistant"; content: string }>, opts?: { system?: string; maxTokens?: number }): Promise<string>Parameters
| Name | Type | Description |
|---|---|---|
messagesrequired | Array<{ role: "user" | "assistant"; content: string }> | [{ role, content }]. |
optsoptional | { system?: string; maxTokens?: number } | { system?, maxTokens? }. |
- Returns
- The reply text.
- Errors
- "ctx.ai: <reason>".
- Permissions
- Metered to the project.
const reply = await ctx.ai.chat([{ role: "user", content: question }], { system: "Answer in one line." });Source: services/api/src/app-runtime/runtime-types.ts:134
ctx.integrations.invokemethod
Run one action of a connected integration: ctx.integrations.invoke(id, action, input). The action is the piece's action name, listed on each integration's page.
ctx.integrations.invoke(integrationId: string, action: string, input?: Record<string, unknown>): Promise<unknown>Parameters
| Name | Type | Description |
|---|---|---|
integrationIdrequired | string | The integration id, e.g. "google_sheets". |
actionrequired | string | The action name, e.g. "insert_row". |
inputoptional | Record<string, unknown> | The action's props. |
- Returns
- { success, output, error? }. A missing connection ("Not connected to <name>. Please connect the integration first.") or an unknown action resolves with success: false.
- Errors
- Errors thrown by the action itself surface from the runner.
- Permissions
- Uses the owner's connection (project, else workspace). Credentials stay in the vault and are decrypted server-side.
const res = await ctx.integrations.invoke("google_sheets", "insert_row", {
spreadsheetId: "1AbC...", sheetId: 0, values: { Name: row.name, Email: row.email },
}) as { success: boolean; output: unknown; error?: string };
if (!res.success) throw new Error(res.error);Source: services/api/src/app-runtime/runtime-types.ts:141
ctx.messages.emailmethod
Send an email: explicit provider, else a bound email integration, else the platform's durable email queue. Also takes { template, to, vars } to render .appbricx/backend/templates/<template>.md.
ctx.messages.email(opts: { to: string | string[]; subject: string; body?: string; html?: string; from?: string; provider?: "sendgrid" | "resend" | "gmail" }): Promise<unknown>Parameters
| Name | Type | Description |
|---|---|---|
optsrequired | { to: string | string[]; subject: string; body?: string; html?: string; from?: string; provider?: "sendgrid" | "resend" | "gmail" } | { to, subject, body?, html?, from?, provider? } or { template, to, vars }. |
- Returns
- { ok: true, transport }.
- Errors
- "ctx.messages.email: 'to' is required"; a missing template or placeholder value; QuotaExceededError (emails per day); any delivery failure (logged to the run trace, then thrown).
- Permissions
- Server-side.
await ctx.messages.email({ to: user.email, template: "receipt", vars: { name: user.name, total: "€42.00" } } as never);Source: services/api/src/app-runtime/runtime-types.ts:149
ctx.messages.smsmethod
Send an SMS through the connected Twilio integration (twilio / send_sms).
ctx.messages.sms(opts: { to: string; body: string }): Promise<unknown>Parameters
| Name | Type | Description |
|---|---|---|
optsrequired | { to: string; body: string } | { to, body }. |
- Returns
- The integration runner's result.
- Errors
- Does not throw.
- Permissions
- Needs a Twilio connection.
await ctx.messages.sms({ to: "+15555550123", body: "Your code is 4711" });Source: services/api/src/app-runtime/runtime-types.ts:157
ctx.messages.whatsappmethod
Send a WhatsApp message: send_media with mediaUrl, send_template_message with template, else send_message.
ctx.messages.whatsapp(opts: { to: string; body?: string; template?: string; mediaUrl?: string }): Promise<unknown>Parameters
| Name | Type | Description |
|---|---|---|
optsrequired | { to: string; body?: string; template?: string; mediaUrl?: string } | { to, body?, template?, mediaUrl? }. |
- Returns
- The integration runner's result.
- Errors
- Does not throw.
- Permissions
- Needs a WhatsApp connection.
await ctx.messages.whatsapp({ to: "+15555550123", template: "order_ready" });Source: services/api/src/app-runtime/runtime-types.ts:158
ctx.messages.telegrammethod
Send a Telegram message through the connected bot (telegram_bot / send_text_message).
ctx.messages.telegram(opts: { chatId: string; text: string }): Promise<unknown>Parameters
| Name | Type | Description |
|---|---|---|
optsrequired | { chatId: string; text: string } | { chatId, text }. |
- Returns
- The integration runner's result.
- Errors
- Does not throw.
- Permissions
- Needs a Telegram Bot connection.
await ctx.messages.telegram({ chatId: "123456", text: "Deploy finished" });Source: services/api/src/app-runtime/runtime-types.ts:164
ctx.schedules.createmethod
Create or replace a cron schedule that runs a workflow. It inherits the run's environment (dev or prod).
ctx.schedules.create(spec: { id: string; cron: string; timezone?: string; workflow: string; enabled?: boolean }): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
specrequired | { id: string; cron: string; timezone?: string; workflow: string; enabled?: boolean } | { id, cron, timezone? (default UTC), workflow, enabled? (default true) }. |
- Returns
- Resolves when saved.
- Errors
- The store's validation error for a bad cron expression.
- Permissions
- Project-scoped.
await ctx.schedules.create({ id: "nightly-export", cron: "0 2 * * *", timezone: "Europe/Berlin", workflow: "export-orders" });Source: services/api/src/app-runtime/runtime-types.ts:168
ctx.schedules.updatemethod
Change a schedule.
ctx.schedules.update(id: string, patch: Partial<{ cron: string; timezone: string; workflow: string; enabled: boolean }>): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The schedule id. |
patchrequired | Partial<{ cron: string; timezone: string; workflow: string; enabled: boolean }> | { cron?, timezone?, workflow?, enabled? }. |
- Returns
- Resolves when saved.
- Errors
- Does not throw.
- Permissions
- Project-scoped.
await ctx.schedules.update("nightly-export", { enabled: false });Source: services/api/src/app-runtime/runtime-types.ts:175
ctx.schedules.listmethod
The project's schedules.
ctx.schedules.list(): Promise<Array<Record<string, unknown>>>- Returns
- Array of schedule records.
- Errors
- Does not throw.
- Permissions
- Project-scoped.
const all = await ctx.schedules.list();Source: services/api/src/app-runtime/runtime-types.ts:179
ctx.schedules.deletemethod
Delete a schedule.
ctx.schedules.delete(id: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The schedule id. |
- Returns
- Resolves when deleted.
- Errors
- Does not throw.
- Permissions
- Project-scoped.
await ctx.schedules.delete("nightly-export");Source: services/api/src/app-runtime/runtime-types.ts:180
ctx.users.listmethod
The app's end-user accounts.
ctx.users.list(opts?: { limit?: number; offset?: number }): Promise<AuthUser[]>Parameters
| Name | Type | Description |
|---|---|---|
optsoptional | { limit?: number; offset?: number } | { limit?, offset? }. |
- Returns
- AuthUser[]: { id, email, name, isAdmin?, disabled? }.
- Errors
- Does not throw.
- Permissions
- Platform-side admin access to the app's accounts; never exposed to app users.
const users = await ctx.users.list({ limit: 100 });Source: services/api/src/app-runtime/runtime-types.ts:184
ctx.users.getmethod
One account.
ctx.users.get(id: string): Promise<AuthUser | null>Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The user id. |
- Returns
- AuthUser or null.
- Errors
- Does not throw.
- Permissions
- Admin access.
const u = await ctx.users.get(userId);Source: services/api/src/app-runtime/runtime-types.ts:185
ctx.users.updatemethod
Change an account's name or email.
ctx.users.update(id: string, patch: Partial<{ name: string; email: string }>): Promise<AuthUser | null>Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The user id. |
patchrequired | Partial<{ name: string; email: string }> | { name?, email? }. |
- Returns
- The updated AuthUser, or null.
- Errors
- Does not throw.
- Permissions
- Admin access.
await ctx.users.update(userId, { name: "Ada Lovelace" });Source: services/api/src/app-runtime/runtime-types.ts:186
ctx.users.setAdminmethod
Grant or revoke the app-admin flag.
ctx.users.setAdmin(id: string, isAdmin: boolean): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The user id. |
isAdminrequired | boolean | true to grant. |
- Returns
- Resolves when saved.
- Errors
- Does not throw.
- Permissions
- Admin access.
await ctx.users.setAdmin(userId, true);Source: services/api/src/app-runtime/runtime-types.ts:190
ctx.users.disablemethod
Disable (or re-enable) an account. A disabled account is treated as signed out within 15 seconds.
ctx.users.disable(id: string, disabled?: boolean): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The user id. |
disabledoptional | boolean | Default true. |
- Returns
- Resolves when saved.
- Errors
- Does not throw.
- Permissions
- Admin access.
await ctx.users.disable(userId);Source: services/api/src/app-runtime/runtime-types.ts:191
ctx.rbac.listRolesmethod
The app's RBAC roles.
ctx.rbac.listRoles(): Promise<RoleRecord[]>- Returns
- RoleRecord[]: { id, name, permissions }.
- Errors
- Does not throw.
- Permissions
- Admin access.
const roles = await ctx.rbac.listRoles();Source: services/api/src/app-runtime/runtime-types.ts:195
ctx.rbac.createRolemethod
Create a role with permission strings.
ctx.rbac.createRole(name: string, permissions?: string[]): Promise<RoleRecord>Parameters
| Name | Type | Description |
|---|---|---|
namerequired | string | The role name. |
permissionsoptional | string[] | Permission strings (default []). |
- Returns
- The RoleRecord.
- Errors
- Does not throw.
- Permissions
- Admin access.
await ctx.rbac.createRole("auditor", ["reports:read"]);Source: services/api/src/app-runtime/runtime-types.ts:196
ctx.rbac.assignmethod
Give a user a role.
ctx.rbac.assign(userId: string, roleName: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
userIdrequired | string | The user id. |
roleNamerequired | string | The role name. |
- Returns
- Resolves when saved.
- Errors
- Does not throw.
- Permissions
- Admin access.
await ctx.rbac.assign(userId, "auditor");Source: services/api/src/app-runtime/runtime-types.ts:197
ctx.rbac.revokemethod
Take a role away.
ctx.rbac.revoke(userId: string, roleName: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
userIdrequired | string | The user id. |
roleNamerequired | string | The role name. |
- Returns
- Resolves when saved.
- Errors
- Does not throw.
- Permissions
- Admin access.
await ctx.rbac.revoke(userId, "auditor");Source: services/api/src/app-runtime/runtime-types.ts:198
ctx.rbac.hasPermissionmethod
Whether a user's roles carry a permission.
ctx.rbac.hasPermission(userId: string, permission: string): Promise<boolean>Parameters
| Name | Type | Description |
|---|---|---|
userIdrequired | string | The user id. |
permissionrequired | string | The permission string. |
- Returns
- boolean.
- Errors
- Does not throw.
- Permissions
- Admin access.
if (!(await ctx.rbac.hasPermission(userId, "reports:read"))) return;Source: services/api/src/app-runtime/runtime-types.ts:199
ctx.callWorkflowmethod
Queue another workflow as a child run.
ctx.callWorkflow(workflowId: string, payload?: Record<string, unknown>): Promise<{ runId: string }>Parameters
| Name | Type | Description |
|---|---|---|
workflowIdrequired | string | The workflow id. |
payloadoptional | Record<string, unknown> | Its payload. |
- Returns
- { runId } of the queued child.
- Errors
- "Max workflow call depth 3 exceeded" (appbricx_APP_WF_MAX_CALL_DEPTH).
- Permissions
- Same project.
const { runId } = await ctx.callWorkflow("send-invoice", { order: id });
ctx.log.info("queued invoice", { runId });Source: services/api/src/app-runtime/runtime-types.ts:202
Types
type WorkflowDef = {
id: string;
name: string;
description: string;
trigger: WorkflowTrigger;
retry?: WorkflowRetry;
runAs?: WorkflowRunAs;
run?: (ctx: WorkflowCtx, event: WorkflowEvent) => Promise<void> | void;
}Source: services/api/src/platform-sdk/workflow.ts:190
type WorkflowTrigger = {
type: "entity_event";
entity: string;
event: "created" | "updated" | "removed";
} | {
type: "ui";
hint?: string;
payloadKeys?: string[];
} | {
type: "webhook";
path: string;
payloadHint?: string;
} | {
type: "cron";
schedule: string;
}Source: services/api/src/platform-sdk/workflow.ts:54
type WorkflowEvent = {
type: "entity_event" | "ui" | "webhook" | "cron" | "manual";
payload: Record<string, unknown>;
previous?: Record<string, unknown>;
user?: {
id: string;
name: string;
email: string;
role: string;
};
}Source: services/api/src/platform-sdk/workflow.ts:64
type WorkflowRetry = {
max: number;
backoffMs?: number;
}Source: services/api/src/platform-sdk/workflow.ts:173
type WorkflowRunAs = "user" | "system"Source: services/api/src/platform-sdk/workflow.ts:188
type ListQuery = {
filter?: FilterClause[] | Record<string, unknown>;
sort?: {
field: string;
dir: "asc" | "desc";
};
limit?: number;
offset?: number;
}Source: services/api/src/platform-sdk/workflow.ts:46
type FilterClause = {
field: string;
op: FilterOp;
value?: unknown;
}Source: services/api/src/platform-sdk/workflow.ts:45
type FilterOp = "eq" | "neq" | "in" | "not_in" | "lt" | "lte" | "gt" | "gte" | "contains" | "not_contains" | "starts_with" | "ends_with" | "is_null" | "is_not_null" | "between" | "today" | "yesterday" | "this_week" | "this_month" | "this_year" | "last_n_days" | "next_n_days"Source: services/api/src/platform-sdk/workflow.ts:38
type Row = Record<string, unknown> & {
id: string;
}Source: services/api/src/platform-sdk/workflow.ts:36
interface RuntimeResult<T = Record<string, unknown>> {
ok: boolean;
rows: T[];
rowCount: number;
fields?: Array<{
name: string;
type: string;
}>;
truncated?: boolean;
elapsed_ms?: number;
error?: {
code: string;
message: string;
};
nextCursor?: string | null;
hasMore?: boolean;
}Source: services/api/src/app-runtime/runtime-types.ts:9
interface WorkflowTrigger {
type: "manual" | "cron" | "webhook" | "topic" | "cdc" | "call";
payload?: Record<string, unknown>;
meta?: Record<string, unknown>;
}Source: services/api/src/app-runtime/runtime-types.ts:37
interface AuthUser {
id: string;
email: string;
name: string | null;
isAdmin?: boolean;
disabled?: boolean;
}Source: services/api/src/app-runtime/runtime-types.ts:43
interface RoleRecord {
id: string;
name: string;
permissions: string[];
}Source: services/api/src/app-runtime/runtime-types.ts:51