Appbricx
Docs
FeaturesPricingStart free

Get started

OverviewWhat Appbricx isQuickstart with a coding agentQuickstart with the builderCore concepts

Guides

Data and tablesAccess and rolesBusiness rulesSign-inCustom screensRealtime and topicsWorkflows, schedules and webhooksServer functionsAI in your appPaymentsFilesMessagesIntegrationsVerifyPublishing, domains and rollbackOperating your appExport and self-hostSelf-host install

Tutorials

Build a booking app with Claude CodeMulti-user todo listClient portalSlack on new sign-upTemplates

Reference

Reference homeFrontend SDKHTTP APIMCP toolsCLIIntegrations catalogContract formatAI key resolutionRun Appbricx locally

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.

70 membersSDK referenceGuide: Run work in the background

On this page
  1. defineWorkflow
  2. defineWorkflow
  3. ctx (defineWorkflow modules)
  4. ctx.db.list
  5. ctx.db.get
  6. ctx.db.create
  7. ctx.db.update
  8. ctx.db.updateWhere
  9. ctx.db.remove
  10. ctx.http.fetch
  11. ctx.notify
  12. ctx.ai.ask
  13. ctx.ai.chat
  14. ctx.email
  15. ctx.emailRole
  16. ctx.messages.render
  17. ctx.messages.email
  18. ctx.files.put
  19. ctx.files.get
  20. ctx.files.list
  21. ctx.files.remove
  22. ctx.secrets.get
  23. ctx.vars.get
  24. ctx.vars.set
  25. ctx.integrations.call
  26. ctx.workflows.trigger
  27. ctx.log
  28. ctx.now
  29. ctx (native .workflow.js modules)
  30. ctx.projectId
  31. ctx.runId
  32. ctx.trigger
  33. ctx.queries.run
  34. ctx.db.query
  35. ctx.db.exec
  36. ctx.api.list
  37. ctx.api.get
  38. ctx.api.create
  39. ctx.api.update
  40. ctx.api.delete
  41. ctx.http.fetch
  42. ctx.files.read
  43. ctx.files.write
  44. ctx.files.list
  45. ctx.files.delete
  46. ctx.log.info
  47. ctx.log.warn
  48. ctx.log.error
  49. ctx.topics.publish
  50. ctx.topics.subscribe
  51. ctx.secrets.get
  52. ctx.ai.ask
  53. ctx.ai.chat
  54. ctx.integrations.invoke
  55. ctx.messages.email
  56. ctx.messages.sms
  57. ctx.messages.whatsapp
  58. ctx.messages.telegram
  59. ctx.schedules.create
  60. ctx.schedules.update
  61. ctx.schedules.list
  62. ctx.schedules.delete
  63. ctx.users.list
  64. ctx.users.get
  65. ctx.users.update
  66. ctx.users.setAdmin
  67. ctx.users.disable
  68. ctx.rbac.listRoles
  69. ctx.rbac.createRole
  70. ctx.rbac.assign
  71. ctx.rbac.revoke
  72. ctx.rbac.hasPermission
  73. ctx.callWorkflow
  74. 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): WorkflowDefWithSdk

Parameters

NameTypeDescription
defrequiredWorkflowDef{ 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.
Example
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`);
  },
});

Relatedapp.callGuides workflowsGuides workflows

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

NameTypeDescription
entityrequiredstringThe table name.
queryoptionalListQuery{ 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).
Example
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,
});

Relatedctx.db.updateWherectx.api.list

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

NameTypeDescription
entityrequiredstringThe table name.
idrequiredstringThe 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).
Example
const order = await ctx.db.get("orders", String(event.payload.order));

Relatedctx.api.get

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

NameTypeDescription
entityrequiredstringThe table name.
rowrequiredRecord<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).
Example
await ctx.db.create("audit_log", { action: "refund", order: event.payload.id, at: ctx.now().toISOString() });

Relatedctx.api.create

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

NameTypeDescription
entityrequiredstringThe table name.
idrequiredstringThe row id.
patchrequiredRecord<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).
Example
await ctx.db.update("tickets", String(event.payload.id), { triaged: true });

Relatedctx.db.updateWhere

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

NameTypeDescription
entityrequiredstringThe table name.
filterrequiredFilterClause[]FilterClause[] (all must match).
patchrequiredRecord<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).
Example
const n = await ctx.db.updateWhere("sessions",
  [{ field: "expires_at", op: "lt", value: ctx.now().toISOString() }],
  { active: false });
ctx.log(`expired ${n} session(s)`);

Relatedctx.db.list

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

NameTypeDescription
entityrequiredstringThe table name.
idrequiredstringThe 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).
Example
await ctx.db.remove("drafts", String(event.payload.id));

Relatedctx.api.delete

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

NameTypeDescription
urlrequiredstringAn 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.
Example
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[] }>();

Relatedctx.secrets.getctx.http.fetchapp.call

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

NameTypeDescription
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.
Example
await ctx.notify({ role: "staff", title: "New order", body: `Order ${event.payload.id} needs packing` });

RelatedGET /__appbricx/notifications

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

NameTypeDescription
promptrequiredstringWhat 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.
Example
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() });

Relatedapp.ai.askctx.ai.ask

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

NameTypeDescription
messagesrequiredArray<{ 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.
Example
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." });

Relatedapp.ai.chat

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

NameTypeDescription
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.
Example
await ctx.email({
  to: String(event.payload.email),
  subject: "Your booking is confirmed",
  html: `<p>See you on ${event.payload.date}.</p>`,
});

Relatedctx.emailRolectx.messages.email

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

NameTypeDescription
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.
Example
const sent = await ctx.emailRole({ role: "staff", subject: "Daily digest", html: digestHtml });
ctx.log(`digest sent to ${sent} staff`);

Relatedctx.email

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

NameTypeDescription
namerequiredstringThe template name.
varsoptionalRecord<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.
Example
if (ctx.messages) {
  try { await ctx.messages.render("welcome", { name }); } catch { /* fall back to ctx.email */ }
}

Relatedctx.emailctx.messages.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

NameTypeDescription
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.
Example
await ctx.messages?.email({ to: customer.email, subject: "Receipt", html } as never);

Relatedctx.email

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

NameTypeDescription
namerequiredstringA relative path, e.g. "exports/2026-10.csv".
contentrequiredstringThe text.
contentTypeoptionalstringIgnored 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.
Example
await ctx.files.put(`exports/${month}.csv`, rows.map((r) => [r.id, r.total].join(",")).join("\n"));

Relatedctx.files.getctx.files.write

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

NameTypeDescription
namerequiredstringThe relative path.
Returns
{ content } or null when missing.
Errors
Does not throw.
Permissions
Per-app store.
Example
const cursor = (await ctx.files.get("sync/cursor.txt"))?.content ?? "0";

Relatedctx.files.putctx.vars.get

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

NameTypeDescription
prefixoptionalstringKeep names starting with this.
Returns
string[] (one level: files and folder names).
Errors
Does not throw.
Permissions
Per-app store.
Example
const exports = await ctx.files.list("exports");

Relatedctx.files.list

Source: services/api/src/platform-sdk/workflow.ts:143

ctx.files.removemethod

Delete a file.

ctx.files.remove(name: string): Promise<void>

Parameters

NameTypeDescription
namerequiredstringThe 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.
Example
await ctx.files.remove("sync/cursor.txt");

Relatedctx.files.delete

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

NameTypeDescription
namerequiredstringThe 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.
Example
const key = await ctx.secrets.get("OPENWEATHER_KEY");
if (!key) throw new Error("OPENWEATHER_KEY is not set");

RelatedMCP secretsappbricx secretsctx.secrets.get

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

NameTypeDescription
namerequiredstringThe variable name.
Returns
The string value, or null.
Errors
Does not throw.
Permissions
Runs under the workflow's identity; not exposed to app users.
Example
const since = (await ctx.vars.get("last_sync")) ?? "1970-01-01";

Relatedctx.vars.set

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

NameTypeDescription
namerequiredstringThe variable name.
valuerequiredstringThe value.
Returns
Resolves when stored.
Errors
Does not throw.
Permissions
As vars.get.
Example
await ctx.vars.set("last_sync", ctx.now().toISOString());

Relatedctx.vars.get

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

NameTypeDescription
namerequiredstringThe integration id, e.g. "slack".
payloadrequiredRecord<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.
Example
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}`);

Relatedctx.integrations.invokeIntegrations

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

NameTypeDescription
workflowIdrequiredstringThe workflow id.
payloadoptionalRecord<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.
Example
await ctx.workflows.trigger("send-invoice", { order: event.payload.id });

Relatedctx.callWorkflow

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): void

Parameters

NameTypeDescription
noterequiredstringThe line.
Returns
Nothing.
Errors
Does not throw.
Permissions
Run logs are visible to the project's builders (logs, run get).
Example
ctx.log(`charged ${order.id} ${amount} cents`);

RelatedMCP logsappbricx logs

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.
Example
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.
Example
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.
Example
ctx.log.info("started", { run: ctx.runId });

RelatedGET /__appbricx/runtime/runs/: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.
Example
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

NameTypeDescription
namerequiredstringThe query name, e.g. "reports/monthly".
paramsoptionalRecord<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.
Example
const res = await ctx.queries.run<{ total: number }>("reports/monthly", { month: "2026-10" });
if (!res.ok) throw new Error(res.error?.message);

RelatedPOST /__appbricx/queries/*Guides data

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

NameTypeDescription
sqlrequiredstringThe statement, with $1, $2… placeholders.
paramsoptionalunknown[]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.
Example
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 });

Relatedctx.db.execPOST /__appbricx/data/query

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

NameTypeDescription
sqlrequiredstringThe 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.
Example
await ctx.db.exec("REFRESH MATERIALIZED VIEW monthly_totals");

Relatedctx.db.query

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

NameTypeDescription
tablerequiredstringThe 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.
Example
const res = await ctx.api.list("orders", { filter: [{ field: "total", op: "gte", value: 100 }], sort: { field: "total", dir: "desc" }, limit: 10 });

RelatedGET /__appbricx/api/v1/:tablectx.db.list

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

NameTypeDescription
tablerequiredstringThe table name.
idrequiredstringThe 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.
Example
const res = await ctx.api.get("orders", id);
const order = res.rows[0];

RelatedGET /__appbricx/api/v1/:table/:id

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

NameTypeDescription
tablerequiredstringThe table name.
datarequiredRecord<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.
Example
await ctx.api.create("audit_log", { action: "export", at: new Date().toISOString() });

RelatedPOST /__appbricx/api/v1/:table

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

NameTypeDescription
tablerequiredstringThe table name.
idrequiredstringThe row id.
datarequiredRecord<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.
Example
await ctx.api.update("tickets", id, { escalated: true });

RelatedPATCH /__appbricx/api/v1/:table/:id

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

NameTypeDescription
tablerequiredstringThe table name.
idrequiredstringThe 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.
Example
await ctx.api.delete("drafts", id);

RelatedDELETE /__appbricx/api/v1/:table/: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

NameTypeDescription
urlrequiredstringAn https URL on a public host.
initoptionalRequestInitA 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.
Example
const res = await ctx.http.fetch("https://api.github.com/repos/acme/app/releases/latest", { headers: { Accept: "application/json" } });
const release = await res.json();

Relatedctx.http.fetch

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

NameTypeDescription
relPathrequiredstringA relative path.
Returns
The content.
Errors
  • "ENOENT: no such file — <path>" (or the fs error locally); "Invalid file path".
Permissions
Per-app store.
Example
const csv = await ctx.files.read("imports/today.csv");

Relatedctx.files.get

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

NameTypeDescription
relPathrequiredstringA relative path.
contentrequiredstringThe text.
Returns
Resolves when stored.
Errors
  • "Invalid file path".
Permissions
Per-app store.
Example
await ctx.files.write("reports/latest.json", JSON.stringify(report));

Relatedctx.files.put

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

NameTypeDescription
relPathoptionalstringThe folder (default the root).
Returns
string[].
Errors
  • "Invalid file path".
Permissions
Per-app store.
Example
const names = await ctx.files.list("reports");

Relatedctx.files.list

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

NameTypeDescription
relPathrequiredstringA relative path.
Returns
Resolves when deleted.
Errors
  • ENOENT on local disk for a missing file; "Invalid file path".
Permissions
Per-app store.
Example
await ctx.files.delete("reports/old.json");

Relatedctx.files.remove

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>): void

Parameters

NameTypeDescription
messagerequiredstringThe line.
dataoptionalRecord<string, unknown>Structured fields shown with it.
Returns
Nothing.
Errors
Does not throw.
Permissions
Visible to the project's builders.
Example
ctx.log.info("synced", { count: rows.length });

Relatedctx.log

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>): void

Parameters

NameTypeDescription
messagerequiredstringThe line.
dataoptionalRecord<string, unknown>Structured fields.
Returns
Nothing.
Errors
Does not throw.
Permissions
Visible to the project's builders.
Example
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>): void

Parameters

NameTypeDescription
messagerequiredstringThe line.
dataoptionalRecord<string, unknown>Structured fields.
Returns
Nothing.
Errors
Does not throw.
Permissions
Visible to the project's builders.
Example
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

NameTypeDescription
namerequiredstringThe topic, e.g. "app:orders".
payloadrequiredunknownAny JSON value.
Returns
Resolves when published.
Errors
Does not throw.
Permissions
Workflows are trusted publishers, including row-changed:* topics.
Example
await ctx.topics.publish("app:kitchen", { order: id, status: "ready" });

RelatedPOST /__appbricx/topics/:name/publishGET /__appbricx/topics/:name/subscribe

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): () => void

Parameters

NameTypeDescription
namerequiredstringThe topic.
handlerrequired(ev: unknown) => voidCalled with each payload.
Returns
An unsubscribe function.
Errors
Does not throw.
Permissions
Same project only.
Example
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

NameTypeDescription
namerequiredstringThe secret name.
Returns
The value or null.
Errors
Does not throw.
Permissions
Read-only; values are never logged by the platform.
Example
const token = await ctx.secrets.get("AIRTABLE_TOKEN");

Relatedctx.secrets.get

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

NameTypeDescription
promptrequiredstringWhat to ask.
optsoptional{ system?: string; maxTokens?: number }{ system?, maxTokens? }.
Returns
The answer text.
Errors
  • "ctx.ai: <reason>".
Permissions
Metered to the project.
Example
const tldr = await ctx.ai.ask(`TL;DR: ${article}`, { maxTokens: 80 });

Relatedctx.ai.ask

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

NameTypeDescription
messagesrequiredArray<{ 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.
Example
const reply = await ctx.ai.chat([{ role: "user", content: question }], { system: "Answer in one line." });

Relatedctx.ai.chat

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

NameTypeDescription
integrationIdrequiredstringThe integration id, e.g. "google_sheets".
actionrequiredstringThe action name, e.g. "insert_row".
inputoptionalRecord<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.
Example
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);

Relatedctx.integrations.callIntegrations

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

NameTypeDescription
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.
Example
await ctx.messages.email({ to: user.email, template: "receipt", vars: { name: user.name, total: "€42.00" } } as never);

Relatedctx.email

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

NameTypeDescription
optsrequired{ to: string; body: string }{ to, body }.
Returns
The integration runner's result.
Errors
Does not throw.
Permissions
Needs a Twilio connection.
Example
await ctx.messages.sms({ to: "+15555550123", body: "Your code is 4711" });

RelatedIntegrations twilio

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

NameTypeDescription
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.
Example
await ctx.messages.whatsapp({ to: "+15555550123", template: "order_ready" });

RelatedIntegrations whatsapp

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

NameTypeDescription
optsrequired{ chatId: string; text: string }{ chatId, text }.
Returns
The integration runner's result.
Errors
Does not throw.
Permissions
Needs a Telegram Bot connection.
Example
await ctx.messages.telegram({ chatId: "123456", text: "Deploy finished" });

RelatedIntegrations telegram bot

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

NameTypeDescription
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.
Example
await ctx.schedules.create({ id: "nightly-export", cron: "0 2 * * *", timezone: "Europe/Berlin", workflow: "export-orders" });

RelatedGuides workflows

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

NameTypeDescription
idrequiredstringThe schedule id.
patchrequiredPartial<{ cron: string; timezone: string; workflow: string; enabled: boolean }>{ cron?, timezone?, workflow?, enabled? }.
Returns
Resolves when saved.
Errors
Does not throw.
Permissions
Project-scoped.
Example
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.
Example
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

NameTypeDescription
idrequiredstringThe schedule id.
Returns
Resolves when deleted.
Errors
Does not throw.
Permissions
Project-scoped.
Example
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

NameTypeDescription
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.
Example
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

NameTypeDescription
idrequiredstringThe user id.
Returns
AuthUser or null.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
idrequiredstringThe user id.
patchrequiredPartial<{ name: string; email: string }>{ name?, email? }.
Returns
The updated AuthUser, or null.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
idrequiredstringThe user id.
isAdminrequiredbooleantrue to grant.
Returns
Resolves when saved.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
idrequiredstringThe user id.
disabledoptionalbooleanDefault true.
Returns
Resolves when saved.
Errors
Does not throw.
Permissions
Admin access.
Example
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.
Example
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

NameTypeDescription
namerequiredstringThe role name.
permissionsoptionalstring[]Permission strings (default []).
Returns
The RoleRecord.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
userIdrequiredstringThe user id.
roleNamerequiredstringThe role name.
Returns
Resolves when saved.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
userIdrequiredstringThe user id.
roleNamerequiredstringThe role name.
Returns
Resolves when saved.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
userIdrequiredstringThe user id.
permissionrequiredstringThe permission string.
Returns
boolean.
Errors
Does not throw.
Permissions
Admin access.
Example
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

NameTypeDescription
workflowIdrequiredstringThe workflow id.
payloadoptionalRecord<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.
Example
const { runId } = await ctx.callWorkflow("send-invoice", { order: id });
ctx.log.info("queued invoice", { runId });

Relatedctx.workflows.triggerPOST /__appbricx/runtime/workflows/:id/run

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

On this page

  1. defineWorkflow
  2. defineWorkflow
  3. ctx (defineWorkflow modules)
  4. ctx.db.list
  5. ctx.db.get
  6. ctx.db.create
  7. ctx.db.update
  8. ctx.db.updateWhere
  9. ctx.db.remove
  10. ctx.http.fetch
  11. ctx.notify
  12. ctx.ai.ask
  13. ctx.ai.chat
  14. ctx.email
  15. ctx.emailRole
  16. ctx.messages.render
  17. ctx.messages.email
  18. ctx.files.put
  19. ctx.files.get
  20. ctx.files.list
  21. ctx.files.remove
  22. ctx.secrets.get
  23. ctx.vars.get
  24. ctx.vars.set
  25. ctx.integrations.call
  26. ctx.workflows.trigger
  27. ctx.log
  28. ctx.now
  29. ctx (native .workflow.js modules)
  30. ctx.projectId
  31. ctx.runId
  32. ctx.trigger
  33. ctx.queries.run
  34. ctx.db.query
  35. ctx.db.exec
  36. ctx.api.list
  37. ctx.api.get
  38. ctx.api.create
  39. ctx.api.update
  40. ctx.api.delete
  41. ctx.http.fetch
  42. ctx.files.read
  43. ctx.files.write
  44. ctx.files.list
  45. ctx.files.delete
  46. ctx.log.info
  47. ctx.log.warn
  48. ctx.log.error
  49. ctx.topics.publish
  50. ctx.topics.subscribe
  51. ctx.secrets.get
  52. ctx.ai.ask
  53. ctx.ai.chat
  54. ctx.integrations.invoke
  55. ctx.messages.email
  56. ctx.messages.sms
  57. ctx.messages.whatsapp
  58. ctx.messages.telegram
  59. ctx.schedules.create
  60. ctx.schedules.update
  61. ctx.schedules.list
  62. ctx.schedules.delete
  63. ctx.users.list
  64. ctx.users.get
  65. ctx.users.update
  66. ctx.users.setAdmin
  67. ctx.users.disable
  68. ctx.rbac.listRoles
  69. ctx.rbac.createRole
  70. ctx.rbac.assign
  71. ctx.rbac.revoke
  72. ctx.rbac.hasPermission
  73. ctx.callWorkflow
  74. Types
Appbricx

The backend, runtime and hosting for apps your coding agent builds. On our cloud, in yours, or on your own servers.

Features

Coding agents and MCPVerify testingBuilder consolePostgres databaseData APIRealtimeRow-level securitySign-in and authPaymentsWorkflows and jobsAI in your appIntegrationsBackups and rollbackHosting and domainsSelf-host and exportAll features

Product

ShowcasePricingFor coding agentsTemplatesPrivate cloudBring your own AI keyFAQ

Compare

Appbricx vs SupabaseLovable alternativeBolt alternativeFor freelancersFor agencies

Resources

DocumentationReferenceBlogSupportContact

© 2026 Appbricx. All rights reserved.

TermsPrivacyCookiesAcceptable Use