Reusable Actions
The same step usually appears in several automations. A Slack alert, an audit-log write, a call to an internal API. Copying that step into each automation means every future change has to be made in every copy.
app.actions is a library of named templates. Define the step once, with $variable placeholders where the inputs differ:
actions:
- name: notify-slack
action:
type: http
operator: post
props:
url: $env.SLACK_WEBHOOK_URL
body: { text: '$message' }Then invoke it from any automation with a ref action, passing the values:
automations:
- name: order-alert
trigger: { type: record, table: orders, events: [create] }
actions:
- name: alert
$ref: notify-slack
$vars: { message: 'New order recorded.' }Template Properties
| Property | Description |
|---|---|
name |
Unique kebab-case identifier used by $ref. Starts with a lowercase letter, max 100 characters. Required. |
action |
The step the template runs: any type and operator pair an automation step accepts. Required. |
variables |
Default values for the template's placeholders. Each call site may override them with $vars. |
aiAccess |
Exposes the template to AI clients as the MCP tool {app}_action_{name}, with parameters derived from variables. |
The template has no step name. Inside an automation, name is how later steps reference an output, so it belongs to the call site rather than the template. That is why action is a nested block: the template declares what to do, the invocation declares what to call it.
Invoking a Template
The ref action is the only action with no operator. It carries a reference and, optionally, the values for this call site:
| Property | Description |
|---|---|
$ref |
Name of the template to invoke. Must match a template in app.actions. |
$vars |
Values for this invocation. Merged over the template's variables defaults, which they override. |
name |
Step name recorded in run history. The call site's name is kept, not the template's. |
Writing type: ref is optional. A $ref is unambiguous on its own, so { name: alert, $ref: notify-slack } and the explicit { name: alert, type: ref, $ref: notify-slack } are the same action.
Variables
A placeholder is a $ followed by an alphanumeric name ($message, $channel). At invocation, the merged variables supply the values:
| Rule | Detail |
|---|---|
| Where they resolve | In any string value, at any depth inside the template's action block. |
| Precedence | $vars at the call site wins over the template's variables defaults. |
| Unknown names | Left untouched, so $env.SLACK_WEBHOOK_URL and {{trigger.data.id}} survive substitution. |
That last rule is what lets a template mix all three reference families. Declare the defaults you want, and leave environment references and automation template variables to be resolved later by their own engines.
The same template with different variables produces different steps:
actions:
- name: notify-team
variables:
channel: general
message: Something happened.
action:
type: http
operator: post
props:
url: $env.SLACK_WEBHOOK_URL
body: { channel: '$channel', text: '$message' }
automations:
- name: order-alert
trigger: { type: record, table: orders, events: [create] }
actions:
- { name: alert, $ref: notify-team, $vars: { message: 'New order.' } }
- { name: escalate, $ref: notify-team, $vars: { channel: ops, message: 'Check stock.' } }Exposing a Template to AI
A template with an aiAccess block becomes a directly-invocable MCP tool. Its variables become the tool's parameters, so the same declaration that makes a step reusable also makes it callable:
actions:
- name: archive-order
variables:
reference: ''
action:
type: record
operator: update
props:
table: orders
filter:
conditions: [{ field: reference, operator: equals, value: '$reference' }]
data: { archived: true }
aiAccess:
description: Archive one order by its reference.
annotations: { readOnly: false, destructive: false, idempotent: true }Whether the server actually mounts these tools is an operator decision, controlled by MCP_ENABLED. See MCP Server.
Validation
| Rule | Detail |
|---|---|
| Unique names | A duplicate would make a $ref ambiguous, and it is rejected when the config is decoded. |
ref is reserved |
It collides with the context.actions.ref() method exposed to code action bodies, and is rejected by name. |
| References resolve | A $ref naming a template that does not exist is caught at startup, before the automation can run. |
Templates versus automation steps. app.actions holds steps you invoke from more than one place. A step used by exactly one automation belongs inline in that automation. A template referenced from a single call site adds indirection without removing duplication.
Related Pages
- Actions Overview — the action model and the ~22 families a template can wrap.
- Automations Overview — the trigger and actions anatomy.
- Reusable Components — the same pattern for a page's component tree.
- MCP Server — action templates exposed as AI tools.
- Environment Variables — the
$env.NAMEreferences a template body carries.
Last updated August 11, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.