Wires and automations
A wire connects something that happens to something that should happen: when a trigger fires, run an action. Concepts -> Wires covers the idea and the composer; this page is the vocabulary a wire is actually made of, for when you write one through the API or read one back.
A wire is a stored binding. It carries a trigger, an action to run, an optional condition, an optional target, and optional arguments and message templates.
Triggers
A wire's trigger_type is one of:
event: fires on a named event. The event name goes intrigger_event, for exampleinventory.stock.loworpurchases.order.arrived.on-create,on-update,on-delete: fire on the lifecycle of a record.schedule: fires on a repeating rule. The rule is an RRULE string intrigger_schedule(the iCal recurrence format), which is how "every Monday at 9" or "the first of the month" is expressed. This is the same machinery behind recurring tasks and scheduled reminders.user-invoked: not automatic. It puts the action on a record as a button the user taps.
Actions
The action is identified by its id (inventory:adjust-stock,
projects:mark-task-done, and so on). Each action declares which record kinds it
applies to, so a wire can only be pointed at an action the target record supports.
Arguments passed to the action live in args. String argument values are
rendered as templates before the action runs, so an argument can pull a value out
of the event, for example {{event.delta}}.
Target: what the action runs on
By default an action runs on the record that triggered the wire. The target
field changes that:
self(or unset): run on the source record. This is the common case.{ "rel": "...", "dir": "in|out", "kind": "..." }: walk the links from the source record and run the action once on each record it reaches. This is how a wire on one record acts on the records related to it.none: run with no record context, for triggers that do not originate from a record (an inbound webhook). The action locates its own target from its arguments.
Conditions
A wire can carry a condition, checked each time it would fire; if the check fails, that firing is skipped silently (a condition doing its job is not an error). A condition is a structured predicate, not an expression language:
{
"all": [
{ "path": "event.newQty", "op": "lte", "value": 5 },
{ "path": "material", "op": "eq", "value": "PLA" }
]
}
Every clause in all must hold. A clause names a path into the same data the
templates see (the target record's fields at the top level, plus an event.*
block for the event payload), an operator, and either a static value or a
valuePath that reads another live value.
The operators are:
| Operator | Meaning |
|---|---|
eq, neq | equal / not equal |
lt, lte, gt, gte | numeric or string comparison |
contains, not_contains | substring match |
empty, not_empty | the value is / is not present |
Comparison is numeric when both sides parse as numbers, otherwise string, compared
case-insensitively. A valuePath clause is how a wire fires only when a value
changed: compare event.after.location_id against event.before.location_id with
op: "neq".
Templates
A wire can render a message and its string arguments from template data. The data
includes the target record's fields by name, plus an event block:
{{event.delta}}, {{event.reason}}, and {{event.actor.display_name}} all
resolve. Filters like | default: "..." and | slug work here too; see
computed fields for the filter list.
| relative is the exception: it only applies in computed fields, and in a wire
it renders the raw value instead of a relative time.
Chains and the depth guard
An action can emit further events, and those can fire more wires, which is how a
multi-step chain composes. To stop a runaway loop (a wire whose action re-triggers
the same wire), the engine refuses to fire past a depth of 8, records one
wire_depth_exceeded entry, and lets the chain settle. Deployed bundles chain at
most two steps, so the ceiling sits far above any real use.
Seeing what fired
Every firing writes to the activity log: wire_fired
on success, wire_failed when an action throws (the failure is logged, never
re-raised, so events stay best-effort), and wire_depth_exceeded when the guard
trips. Filter activity by action to see a wire's history.
Who can create them
Creating, editing, and deleting wires requires an editor, admin, or owner. A wire runs its action with the authority its author set up, so a member creating a record can legitimately trip an admin-authored wire; the privilege check sits at wire creation, not at firing.