Documentation Index
Fetch the complete documentation index at: https://motiadev-docs-phase-2.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
What “writing a trigger” means
A worker uses triggers two ways. Most of the time, you bind the worker’s functions by registering a trigger on an existing trigger type such as:http,
cron,
queue messages,
state changes, and any other event source in the
system. Less often, you register a new trigger type from your worker so other workers can bind their
functions to events your worker emits.
This page primarily covers the latter: making your own triggers, if you want to use existing
triggers in new workers then refer to Using iii / Triggers.
For the caller-side mechanics (direct invocation with
worker.trigger or iii trigger, the
TriggerAction variants, gating with conditions, multiple bindings per function), see Using iii
/ Triggers.Bind a function to an existing trigger type
Most workers consume trigger types that other workers already publish:http from
iii-http to expose a function as an endpoint, cron
from iii-cron to run a function on a schedule, queue
triggers from iii-queue to fire a function on each
message, state from iii-state to react to data
changes. Bind one of your worker’s functions to a trigger type with
worker.registerTrigger({ type, function_id, config }). The worker that publishes the trigger type
must be connected when you register; otherwise the registration fails.
- Node / TypeScript
- Python
- Rust
config shape is defined per trigger type and documented in each publishing worker’s
Worker Docs.
Other binding mechanics are covered in Using iii /
Triggers: the unregister handle, binding multiple
triggers to the same function, gating with
condition_function_id, and the TriggerAction
variants (Void, Enqueue, etc.).Attach metadata to a trigger
Each trigger binding accepts an optionalmetadata JSON object set by the consumer at registration
time. The engine stores it as-is and surfaces it in two places:
- The publishing worker’s
TriggerHandler.registerTrigger(config)callback sees it asconfig.metadata, so the publisher can act on consumer-supplied tags (priority hints, audit labels, routing keys for the publisher’s own bookkeeping). engine::triggers::listreturns it on eachTriggerInfo, so the console and any other worker doing discovery can read it.
- Node / TypeScript
- Python
- Rust
Trigger types have no metadata field of their own. Metadata is attached per binding, not per
type.Don’t confuse trigger metadata with trigger type schemas
(
trigger_request_format and call_request_format):- Metadata is set by the consumer on each binding (ie. the
worker.registerTrigger()call). It’s a free-form tag bag the engine stores as-is, used for the publisher’s bookkeeping and for discovery. For example, a consumer binding tohttpmight attachmetadata: { team: "platform", env: "staging", on_call: "alice" }so the publishing worker can log the team, andengine::triggers::listsurfaces this information on request. - Schemas are set by the publisher when declaring the trigger type. They document the JSON
shapes the consumer interacts with. For example, the
httptype published by iii-http declares:config(what the consumer passes at bind time):{ api_path, http_method }.- invocation payload (what their bound function receives on each request):
{ method, headers, query_params, body }.
Declaring a Trigger Type
So far this documentation has focused on being the consumer: your worker’s functions get bound to trigger types other workers publish. This section flips the roles. Your worker is now the publisher, and you want functions that other workers register to fire on events your worker observes (an HTTP request, a webhook hit, a file change, a database update).Components of a Trigger Type
A trigger type is two things bundled together:- A string
idthat consumers reference when they bind to a trigger (ex.type: "mini-http"). - A per-binding routing table that your worker maintains in-process. The engine’s registry
records the binding canonically (this is what
engine::triggers::listreturns), but the engine doesn’t dispatch on it. The engine forwards every bind/unbind from any consumer worker on the network to the publisher worker as a callback, and this worker decides what to do with each one.
worker.registerTriggerType({ id, description }, handler).
The TriggerHandler interface you implement exposes two callbacks. The engine invokes them on
your publisher worker whenever a consumer binds or unbinds:
registerTrigger(config): Runs when any consumer worker binds a function to your trigger type. Theconfigcarries the trigger instance’sid, the consumer’sfunction_id, and the consumer-suppliedconfigmatching the shape your type accepts. Stash it.unregisterTrigger(config): Runs on unbind. Drop it from your table.
worker.unregisterTriggerType(...) (or worker.unregister_trigger_type(...) in Python and Rust).
See the Unregister a Trigger Type section below for per-language
signatures.
Example: A mini iii-http from scratch
The example below sketches a tiny version of iii-http,
the worker that publishes the real http trigger type. The publisher worker:
- Declares an HTTP-shaped trigger type called
mini-http - Maintains a
bindingsmap of{ trigger id → function_id, method+path }as consumers bind/unbind - Is then ready to look up the right binding when an HTTP request is received. Firing the bound function is covered in Dispatch events to bound functions below.
Example: declare a `mini-http` trigger type
Example: declare a `mini-http` trigger type
- Node / TypeScript
- Python
- Rust
Attach schemas to the trigger type
A trigger type can carry two optional JSON Schemas that describe its payloads:trigger_request_format: The schema for the per-bindingconfigconsumers pass toworker.registerTrigger(...)when they bind a function to your trigger type.call_request_format: The schema for the payload your worker delivers to bound functions when the trigger fires.
engine::trigger-types::list output so consumers
know what to send and what they’ll receive.
Runtime validation is not yet supported. Attached schemas are informational only; the engine does
not reject
config values or call payloads that don’t match them. Treat the schemas as contract
documentation for consumers, agents, and the console; same caveat as function request / response
schemas.| SDK | What you pass |
|---|---|
| Node / Browser | Raw JSON Schema objects on trigger_request_format / call_request_format. Convert Zod 4+ schemas with z.toJSONSchema(...). |
| Python | A Pydantic model class (auto-converted) or a raw dict on the same fields of RegisterTriggerTypeInput. |
| Rust | Builder methods on RegisterTriggerType: .trigger_request_format::<T>() and .call_request_format::<T>(), where T: schemars::JsonSchema. |
Unregister a Trigger Type
Tear down a trigger type at runtime when the work it routes is no longer needed. When the worker disconnects, all trigger types it advertised are removed automatically and the engine stops routing events that depended on them, so this step is only necessary if you want to drop a type while the worker stays connected. Call it any time afterregisterTriggerType while the worker stays connected (e.g. the underlying
resource went into maintenance mode, a feature flag turned the surface off, or you want to rotate
the type to a new schema without restarting). Continuing the mini-http example, here the worker
drops mini-http because its HTTP listener was disabled by config:
- Node / TypeScript
- Python
- Rust
Node’s
registerTriggerType also returns a TriggerTypeRef with an .unregister() shortcut that
delegates to worker.unregisterTriggerType(...). Python’s TriggerTypeRef only exposes
register_trigger and register_function; tear down the trigger type itself via
worker.unregister_trigger_type(...). Rust takes only the id string; Node and Python take the
full input object but only the id field is used to identify the type being torn down.Dispatch events to bound functions
There is no special “fire” API. When the underlying event source delivers something (an incoming HTTP request, a cron tick, a webhook hit), your publisher worker looks up the relevant entry in thebindings table it built inside its registerTrigger callback and invokes each matching function
via worker.trigger(...).
Continuing the mini-http example from above:
- Node / TypeScript
- Python
- Rust
config and optional
condition_function_id, then routes matching invocations to the bound function and returns the
result to the caller.