> ## Documentation Index
> Fetch the complete documentation index at: https://motiadev-docs-deployment-guide.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Triggers

> Calling functions directly or by binding them to events in your iii project.

## Call a function directly

Call a function by its `function_id` from worker code (`worker.trigger(...)`) or from the terminal
(`iii trigger`). The engine routes the call to whatever worker registered the function; no trigger
registration is involved. The `action` field controls delivery: by default the call waits for the
function to return its result, or for the configured timeout to fire. Pass a different
`TriggerAction` to change that.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import { registerWorker, TriggerAction } from "iii-sdk";

    const url = process.env.III_URL;
    if (!url) throw new Error("III_URL must be set");
    const worker = registerWorker(url);

    const result = await worker.trigger({
      function_id: "math::add",
      payload: { a: 2, b: 3 },
      // action: TriggerAction.Void(),                       // fire-and-forget
      // action: TriggerAction.Enqueue({ queue: "math" }),   // route through iii-queue
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import os
    from iii import register_worker, InitOptions, TriggerAction

    worker = register_worker(
        os.environ.get("III_URL"),
        InitOptions(worker_name="caller"),
    )

    result = worker.trigger({
        "function_id": "math::add",
        "payload": {"a": 2, "b": 3},
        # "action": TriggerAction.Void(),                    # fire-and-forget
        # "action": TriggerAction.Enqueue(queue="math"),     # route through iii-queue
    })
    # result = await worker.trigger_async({...})             # awaitable form for asyncio callers
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{InitOptions, TriggerAction, TriggerRequest, register_worker};
    use serde_json::json;

    let url = std::env::var("III_URL").expect("III_URL must be set");
    let worker = register_worker(&url, InitOptions::default());

    let result = worker
        .trigger(TriggerRequest {
            function_id: "math::add".to_string(),
            payload: json!({ "a": 2, "b": 3 }),
            action: None,
            // action: Some(TriggerAction::Void),                                  // fire-and-forget
            // action: Some(TriggerAction::Enqueue { queue: "math".to_string() }), // route through iii-queue
            timeout_ms: None,
        })
        .await?;
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    iii trigger math::add a=2 b=3
    ```
  </Tab>
</Tabs>

Some common actions are:

* **Default (synchronous)**. No `action` set. The call waits for the function to return its result
  or for the configured timeout to fire.
* **`TriggerAction.Void()`**. Fire-and-forget. The call returns immediately; the function still runs
  but the caller doesn't see the result.
* **`TriggerAction.Enqueue({ queue })`**. Provided by
  [iii-queue](https://workers.iii.dev/workers/iii-queue). Routes the invocation through a named
  queue with retries; the call returns once the message is enqueued.

<Note>
  Workers can provide their own `TriggerAction`s. Check each [worker's
  documentation](https://workers.iii.dev) for the action types it offers.
</Note>

<Note>
  In Python, every blocking method has an awaitable twin (`trigger_async`, `shutdown_async`,
  `create_channel_async`) for use inside `asyncio`. See the [Python SDK
  reference](../sdk-reference/python-sdk#trigger--trigger_async).
</Note>

## Register a trigger

<Note>
  If you're authoring a worker, you'll want to refer to [Creating Workers /
  Triggers](../creating-workers/triggers#bind-a-function-to-an-existing-trigger-type) to learn the
  difference between registering a trigger, and registering a trigger type.
</Note>

Functions can also run when a trigger is satisfied. A trigger can be any event that happens such as
a request to an `http` endpoint, a `cron` job, a change in `state`, or any other trigger that a
worker supports. You can also [write your own](../creating-workers/triggers).

You bind triggers to functions via the `function_id`. The trigger declares its `type`, its `config`
(defined by each type), and the function to invoke.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import { registerWorker } from "iii-sdk";

    const url = process.env.III_URL;
    if (!url) throw new Error("III_URL must be set");
    const worker = registerWorker(url);

    worker.registerTrigger({
      type: "http",
      function_id: "math::add",
      config: { api_path: "/math/add", http_method: "POST" },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import os
    from iii import register_worker, InitOptions

    worker = register_worker(
        os.environ.get("III_URL"),
        InitOptions(worker_name="my-worker"),
    )

    worker.register_trigger({
        "type": "http",
        "function_id": "math::add",
        "config": {"api_path": "/math/add", "http_method": "POST"},
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{InitOptions, RegisterTriggerInput, register_worker};
    use serde_json::json;

    let url = std::env::var("III_URL").expect("III_URL must be set");
    let worker = register_worker(&url, InitOptions::default());

    worker.register_trigger(RegisterTriggerInput {
        trigger_type: "http".into(),
        function_id: "math::add".into(),
        config: json!({ "api_path": "/math/add", "http_method": "POST" }),
        metadata: None,
    })?;
    ```
  </Tab>
</Tabs>

Per-type configuration is documented in each worker's Worker Docs (e.g.
[iii-http](https://workers.iii.dev/workers/iii-http) for the `http` type).

## Handling missing triggers

When the engine cannot register a trigger, most commonly because the trigger type's worker is not
active in the project, it sends a `TriggerRegistrationResult` with an `error` body back to the
worker that initiated the request and logs it.

For known trigger types (ex. `http`, `subscribe`, `state`, `durable:subscriber`, `stream`), the
error message will include the install command for the missing worker. If it doesn't you can find
the worker that exposes the needed type at [workers.iii.dev](https://workers.iii.dev)

## Bind multiple triggers to one function

It's valid to bind multiple triggers to the same `function_id` and this can be done across any
number of types. Register a second trigger with the same `function_id` and a different type or
config; the function runs unchanged whether the call arrives over HTTP, on a cron schedule, or from
a queue message.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    // Same handler runs for an HTTP POST and a weekly cron tick.
    worker.registerTrigger({
      type: "http",
      function_id: "reports::generate",
      config: { api_path: "/reports/generate", http_method: "POST" },
    });

    worker.registerTrigger({
      type: "cron",
      function_id: "reports::generate",
      config: { expression: "0 0 9 * * 1" }, // Every Monday at 09:00
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    worker.register_trigger({
        "type": "http",
        "function_id": "reports::generate",
        "config": {"api_path": "/reports/generate", "http_method": "POST"},
    })

    worker.register_trigger({
        "type": "cron",
        "function_id": "reports::generate",
        "config": {"expression": "0 0 9 * * 1"},  # Every Monday at 09:00
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterTriggerInput;
    use serde_json::json;

    worker.register_trigger(RegisterTriggerInput {
        trigger_type: "http".into(),
        function_id: "reports::generate".into(),
        config: json!({ "api_path": "/reports/generate", "http_method": "POST" }),
        metadata: None,
    })?;

    worker.register_trigger(RegisterTriggerInput {
        trigger_type: "cron".into(),
        function_id: "reports::generate".into(),
        config: json!({ "expression": "0 0 9 * * 1" }), // Every Monday at 09:00
        metadata: None,
    })?;
    ```
  </Tab>
</Tabs>

## Gate a trigger with a condition

A trigger can carry an optional `condition_function_id` (set inside the trigger's `config`). When
the trigger fires, the engine invokes the condition function first with the same payload the handler
would receive; the target `function_id` only runs when the condition returns truthy. The condition
is a regular registered function.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    worker.registerFunction(
      "orders::is-priority",
      async (payload: { customer_tier: string }) => payload.customer_tier === "gold",
    );

    worker.registerTrigger({
      type: "http",
      function_id: "orders::expedite",
      config: {
        api_path: "/orders/expedite",
        http_method: "POST",
        condition_function_id: "orders::is-priority",
      },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    def is_priority(payload: dict) -> bool:
        return payload.get("customer_tier") == "gold"

    worker.register_function("orders::is-priority", is_priority)

    worker.register_trigger({
        "type": "http",
        "function_id": "orders::expedite",
        "config": {
            "api_path": "/orders/expedite",
            "http_method": "POST",
            "condition_function_id": "orders::is-priority",
        },
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{RegisterFunction, RegisterTriggerInput};
    use schemars::JsonSchema;
    use serde::Deserialize;
    use serde_json::json;

    #[derive(Deserialize, JsonSchema)]
    struct Payload { customer_tier: String }

    worker.register_function(RegisterFunction::new(
        "orders::is-priority",
        |input: Payload| -> Result<bool, String> {
            Ok(input.customer_tier == "gold")
        },
    ));

    worker.register_trigger(RegisterTriggerInput {
        trigger_type: "http".into(),
        function_id: "orders::expedite".into(),
        config: json!({
            "api_path": "/orders/expedite",
            "http_method": "POST",
            "condition_function_id": "orders::is-priority",
        }),
        metadata: None,
    })?;
    ```
  </Tab>
</Tabs>

## Unregister a trigger

Trigger registration returns a handle with an `unregister()` method. Call it to drop the trigger at
runtime; when the worker disconnects, all of its triggers are removed automatically.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    const trigger = worker.registerTrigger({
      type: "http",
      function_id: "math::add",
      config: { api_path: "/math/add", http_method: "POST" },
    });

    trigger.unregister();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    trigger = worker.register_trigger({
        "type": "http",
        "function_id": "math::add",
        "config": {"api_path": "/math/add", "http_method": "POST"},
    })

    trigger.unregister()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterTriggerInput;
    use serde_json::json;

    let trigger = worker.register_trigger(RegisterTriggerInput {
        trigger_type: "http".into(),
        function_id: "math::add".into(),
        config: json!({ "api_path": "/math/add", "http_method": "POST" }),
        metadata: None,
    })?;

    trigger.unregister();
    ```
  </Tab>
</Tabs>
