# Macros and roles

> Roles control who can do things; macros let YAML reference who and what dynamically. They share a syntax — a role is essentially a macro that resolves to one or more users or channels.

Canonical HTML page: https://conclude.io/doc/reference/macros-and-roles/
Last updated: 2026-11-02

---

Roles control *who* can do things; macros let YAML reference *who* and
*what* dynamically. They share a syntax — a role is essentially a macro
that resolves to one or more users or channels.

## Role expressions

A role expression is a comma-separated list of tokens that resolve to a
set of users, a set of channels, or both. The same expression syntax is
used for `roles.*`, `attribute.viewers`, `attribute.editors`,
`settings.alert`, `setAttribute.value`, `message.to`, `email.to`, and the
trigger actions `owner`, `members`, `alert`.

### Tokens

| Token | Meaning |
|---|---|
| `team` | All team members (excluding external/guest users) |
| `external` | All external/guest users on the activity |
| `@username:{{U-key}}` | A specific user (encoded form; the editor renders as `@username`) |
| `#channel:{{C-key}}` | A specific channel (encoded form; the editor renders as `#channel`) |
| `${initiator}` | The user who created the activity |
| `${owner}` | The current owner |
| `${channel}` | The activity's channel |
| `${<attribute>}` | A user-typed attribute's value (e.g. `${assignedTo}`) |
| `${__installer}` | The user who installed the app (install-time only) |
| `${__channel}` | The channel the app was installed on (install-time only) |
| `${__blank}` | Empty value (install-time only — see [`${__blank}` semantics](#__blank-semantics)) |

### `roles` block

```yaml
roles:
  owner: ${__installer}             # default owner is the installer
  members: ${__channel}             # whole channel is members
  admins: team                      # any team member is admin
  superusers: "@alice:{{U-12345}}"  # one specific user
```

| Role | Cardinality | Used for |
|---|---|---|
| `owner` | single user | Activity owner; can edit owner-restricted attributes |
| `members` | set of users/channels | Participants in each activity |
| `admins` | set | Can edit the app config and any activity |
| `superusers` | set | Elevated privileges (e.g. for approvals; see `approval.yaml`) |

`owner` is single-user (any expression that resolves to more than one user
is rejected during validation). The other three are chat roles (sets).

### Combining tokens

```yaml
members: ${__channel} ${initiator}    # everyone in the install channel + the initiator
alert: "#oncall ${owner}"             # oncall channel + the current owner
viewers: "team,external"              # everyone, including guests
editors: ${owner}                     # only the owner
```

Separators are whitespace or comma. Whitespace inside `{{...}}` keys is
not allowed.

## Install-time macros

Install-time macros (`${__installer}`, `${__channel}`, `${__blank}`) are
expanded **once**, when the app is installed (or pasted into the Expert
editor). After expansion the YAML stores concrete user/channel
references, not the macro.

| Macro | Expands to | Notes |
|---|---|---|
| `${__installer}` | `@<mention>:` (full role form) or `@<mention>` (as text) | The user who installed the app |
| `${__channel}` | `#<name>:` (role form) or `<name>` (as text) | The channel the app was installed on |
| `${__blank}` | empty string | Used to *opt out* of role/alert defaults |

Expansion happens when the app is installed on a channel. For each field:

1. If the field is empty AND there's a default for the field (e.g.
   `owner` defaults to `installer`, `members` defaults to `channel`),
   apply the default.
2. Otherwise, if the field contains `${__...}`, expand the macros.
3. The result is stored as the literal value.

### `${__blank}` semantics

A field set to `${__blank}` is treated as **user-specified empty** — the
field is cleared and the default is NOT applied. Useful for opting out of
default behavior:

```yaml
roles:
  owner: ${__blank}     # no default owner; the activity starts unowned
```

Compare to leaving the field unset, which DOES apply the default
(`installer` for `owner`).

### Fields that get install-macro expansion

- `roles.admins`, `roles.superusers`, `roles.owner`, `roles.members`
- `settings.alert`
- `settings.channelName` (as text — `${__channel}` becomes the bare
  channel name, not the role form)
- `settings.errorLog`
- `review.members`, `review.alert`, `review.ignore`, `review.include`
- For each attribute: `editors`
- For each attribute's `trigger` (and each option's `trigger`):
  `owner`, `members`, `alert`

## Runtime macros

Runtime macros are resolved every time a value is read or a trigger
fires. They reference per-activity context.

| Macro | Resolves to |
|---|---|
| `${initiator}` | The activity's creator (user reference) |
| `${owner}` | The current owner |
| `${channel}` | The activity's channel |
| `${title}` | The activity's title |
| `${body}` | The activity's body |
| `${conclusion}` | The conclusion text |
| `${status}` | The status attribute value |
| `${<attribute>}` | Any custom attribute value |
| `${<attribute>.label}` | For `select` attributes: the option's `label` |
| `${<attribute>.<sub>}` | Dotted access for compound attribute values (e.g. `${jira.id}`, `${jira.url}`) |

Used in:

- Attribute `defaultValue`
- Attribute `display` template
- Trigger actions: `setAttribute.value`, `message.body`,
  `email.subject`/`body`/`to`, `alert`, etc.
- `settings.channelName` template

The result is encoded for where it is used (plain text, Markdown or
platform-native).

## Examples

### Pre-fill an attribute with the activity's creator

```yaml
- name: assignedTo
  type: user
  defaultValue: ${initiator}
```

### Alert the owner and a fixed channel

```yaml
triggers:
  - onCreate:
      - alert: "${owner} #oncall"
```

### Channel name templated per activity

```yaml
settings:
  mode: channel
  channelName: inc-${title}
```

### Approval app — superusers only

```yaml
roles:
  owner: ${__blank}                     # no auto-owner
  members: ${__channel}
  superusers: ${__installer}            # installer becomes the first superuser

attributes:
  - name: decision
    type: select
    editors: superusers                 # only superusers can decide
    options:
      - name: approve
        label: Approve
        state: closed
      - name: reject
        label: Reject
        state: closed
```

### Display a Jira issue ID as a link

```yaml
- name: jira.id
  type: string
  display: "[${jira.id}](https://acme.atlassian.net/browse/${jira.id})"
```

