Automations
Automations run your own JavaScript or Python in a secure sandbox whenever something happens in your app — a record changes, a button is clicked, or an agent calls them. Each automation has full access to your app's data through the built-in stacker SDK. You can build them in the Automations UI, or create and refine them in a thread — the same conversational workflow you use to build portals and agents.
Create automations in a thread
You don't need to write code by hand to get started. Open a thread, describe what should happen in plain language, and the AI writes the automation, picks a trigger, and can test runs or read execution logs when something fails. Use the portal builder thread or an agent thread with Modify workflows enabled — see Building with threads in Core Concepts for where each conversation lives.
Plain language is enough — no special syntax. Good prompts name the outcome, the trigger, and any constraints:
Example prompts
Record change
Button trigger
Fix and iterate
Chain with agents
Automations created in a thread show up in the Automations sidebar like any other — you can open them there to edit code, change triggers, or review runs by hand.
Finding Automations
Open your app and look in the left sidebar for the Automationsitem (above your agents). There you can create automations manually, edit their code, run them for testing, and review every execution's logs. The neighbouring Data item lets you browse the records your automations read and write.
Triggers
Choose how each automation runs:
- Record change — runs when records in a chosen table are created or updated. Use it to validate input, derive fields, or fan out notifications.
- Button — runs when a user clicks a button in your portal. Call
stacker.triggerWorkflow(slug, params)from a portal page. - Agent — a worker agent runs the automation as a tool (the agent needs the Run automations permission).
- Callable — a function-style automation with no automatic trigger. Agents, app pages, skills, and other automations all call it with
stacker.runWorkflow(slug, input)and it reads the passed inputs withstacker.getInput(). You can declare its inputs(name, type, required) in the automation's detail view — calls that omit a required input or pass the wrong type are then rejected. Great for shared logic you want to reuse. - Manual — runs only when you test it from the Automations UI. Pass a JSON Test inputto simulate the inputs a caller would send. Great while you're building.
The Stacker SDK
Your code runs with a pre-injected stacker object scoped to this app only. The most-used helpers:
stacker.getTrigger()— the event that started this run (the changed record, button params, or agent input).stacker.records— read, create, and update records in your app's tables.stacker.log(...)— write a line to the execution log, visible in the Automations UI.stacker.getInput()— the input object passed to this run (button params, agent params, or the input from another automation that called it). Same asstacker.getTrigger().input.stacker.runWorkflow(slug, input)— call another automation in this app like a function, passing aninputobject. The call is queued and runs independently; loop protection applies (see below).stacker.sendAgentMessage({ agentId, message, threadKey, newThread })— send a message to one of your worker agents. By default, every run of the automation posts into the same shared thread, so the conversation keeps growing. Pass a stablethreadKeyto keep a specific conversation (e.g. one thread per order), or setnewThread: trueto start a fresh thread for that call.stacker.sendEmail({ to, subject, html, text })— send a transactional email from your app's noreply address (e.g."Your App <[email protected]>").toaccepts a single address or an array; providehtmlortext. OptionalreplyTo,cc, andbcc.stacker.shareFile({ fileName, text, contentBase64, sourceUrl, contentType })— save content as a downloadable file and get a signed share link (valid for 7 days). Same capability as the agentshare_filetool and skill code'sstacker.share_file. Works in any automation run — including record-change and button triggers that have no agent thread. Provide exactly one oftext(CSV, markdown, JSON, plain text),contentBase64(binary bytes), orsourceUrl(re-host a public file). Returns aurlyou can pass tosendEmailas an attachment or include in an agent message.
// Runs on new orders, greets the customer via an agent
const { record } = await stacker.getTrigger();
stacker.log("New order received", { orderId: record.id });
// Reuse one thread per order (same threadKey = same conversation):
await stacker.sendAgentMessage({
agentId: "support-agent",
message: `A new order ${record.id} was placed for ${record.total}.`,
threadKey: `order-${record.id}`,
});
// ...or start a brand-new thread every run instead:
// await stacker.sendAgentMessage({
// agentId: "support-agent",
// message: "Daily summary ready.",
// newThread: true,
// });Languages & packages
Automations can be written in JavaScript or Python. You can declare a list of npm or pip packages for an automation; Stacker installs them ahead of time so runs start quickly. If a package can't be pre-installed, the automation still falls back to installing it at run time.
Logs, errors & loop protection
Every run is recorded with its status, duration, return value, and the lines you wrote with stacker.log(). Open any execution in the Automations UI to diagnose failures.
Stacker automatically prevents runaway loops: whether an automation re-triggers itself through record writes or calls another automation directly with stacker.runWorkflow(), the platform tracks the cause-chain and stops it when it gets too deep or the same automation appears too many times in one chain. Automations also respect your workspace's usage limits — runs are metered, and an automation is blocked before it starts if the workspace is out of credit.
Automations run server-side in an isolated sandbox and can only touch the data in their own app. They never receive credentials or data from other apps or workspaces.