
# 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:

```yaml
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:

```yaml
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`. |

:::callout
**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:

```yaml
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:

```yaml
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](/en/docs/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.         |

:::callout
**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](/en/docs/automation-actions-overview) — the action model and the ~22 families a template can wrap.
- [Automations Overview](/en/docs/automations-overview) — the trigger and actions anatomy.
- [Reusable Components](/en/docs/reusable-components) — the same pattern for a page's component tree.
- [MCP Server](/en/docs/mcp-server) — action templates exposed as AI tools.
- [Environment Variables](/en/docs/automation-env-vars) — the `$env.NAME` references a template body carries.
