# Action reference

An action responds to a component event: changing an input, clicking a button, submitting a form, or another event published by that component. Select the component, open Events, choose its event, and add the ordered actions. The [component references](/docs/builder/properties) identify each component's supported events and published state. Use [Code](/docs/builder/code) for structured action fields that need nested values.

This reference describes all five action types. The examples are app definition fragments for authors, with placeholders that must match your own declared component and source IDs. They do not grant access to a workflow, connection, or app data.

## Event chains and common settings

An event has an `event` name and an `actions` array. Use an event declared by the selected component. Event identifiers follow `on` plus an uppercase letter and 1–40 further letters or digits, such as `onClick`. Each component accepts at most 20 event entries, and each event at most 20 actions. An omitted actions array defaults to an empty array.

Actions run in order, with each awaited before the next starts. A later action reads state as changed by earlier actions. A failure ends the remaining chain and shows an error notification. Earlier state changes are not rolled back. Separate event invocations can overlap; avoid relying on an event chain as a transaction.

Every action supports `condition`, an optional string of at most 2,048 characters. Omitted, null, or empty conditions allow execution. A completed binding is evaluated against current state and converted to a boolean. A false result skips that action and continues with the next. An invalid condition ends the chain.

```json
{
  "type": "showNotification",
  "condition": "{{ len(components.Search.value ?? '') > 0 }}",
  "message": "Searching for {{ components.Search.value }}",
  "variant": "info"
}
```

Use `"{{ false }}"` for a false condition. Literal `"false"` is nonempty text and therefore truthy. Zero, empty text, null, undefined, and NaN are falsy; arrays and objects, including empty ones, are truthy. Conditions are interface behavior, not authorization rules.

Action objects accept the documented fields for their own type. Unsupported fields are rejected rather than silently becoming extra action settings. Component and source IDs start with a letter, contain only letters, digits, or underscores, and are at most 64 characters. Referenced IDs must exist in the app definition.

## triggerDataSource

Refresh records, start a workflow run, or observe an ingest-related background run.

| Field | Required, default, and limits |
| --- | --- |
| `type` | Required; `"triggerDataSource"`. |
| `dataSourceId` | Required; an existing declared source ID. |
| `mode` | `"fetch"`, `"run"`, or `"watch"`; defaults to `"fetch"`. |
| `ingestId` | Required only for watch; authored string at most 2,048 characters. Must resolve to a nonempty string at most 64 characters. Omit or use null for other modes. |
| `inputRecords` | Optional/null array of objects, at most 1,000 records; used only by run. |
| `triggerContext` | Optional/null object; at most eight container levels; used only by run. |
| `condition` | Optional/null string; at most 2,048 characters. |

### fetch: refresh existing records

```json
{ "type": "triggerDataSource", "dataSourceId": "Orders", "mode": "fetch" }
```

Fetch re-reads records using the source's current definition and evaluated query bindings. It does not start a workflow or wait for a new ingest operation. Omitting mode has the same effect. Fetch ignores `inputRecords` and `triggerContext`. Use it after a filter change or when the user explicitly requests a refresh.

A record-read failure is reported in `dataSources.Orders.error`. It can allow subsequent actions to execute while older records remain in state. If a following action requires fresh records, give it a condition such as `{{ dataSources.Orders.error == null }}` and show a separate failure message when the error is present. An older fetch superseded by a newer fetch instead stops its remaining chain silently.

The source remains subject to the viewer's permissions and the app's permitted data scope. Loading or fetching a source does not permit running its producer workflow. See [Workflow sources](/docs/builder/workflow-sources) and [App output sources](/docs/builder/app-output-sources).

### run: start and await a workflow

```json
{
  "type": "triggerDataSource",
  "dataSourceId": "SearchResults",
  "mode": "run",
  "condition": "{{ len(components.Search.value ?? '') > 0 }}",
  "inputRecords": [
    {
      "query": "{{ components.Search.value }}",
      "options": { "includeArchived": "{{ app.includeArchived ?? false }}" }
    }
  ],
  "triggerContext": {
    "origin": "search-form",
    "selection": { "documentId": "{{ app.activeDocumentId ?? null }}" }
  }
}
```

Run requires a workflow-records source with triggering enabled (`allowTrigger: true`). It cannot use a source pinned to a particular historical workflow run, and an app-output source cannot trigger another app's workflow. The viewer and runtime must also be permitted to start the run.

Templates inside `inputRecords` and `triggerContext` are evaluated recursively, including values inside nested arrays and objects. Standalone bindings keep their types: the boolean in the example is sent as a boolean, not the text `"false"`. Object keys remain literal. A failing input expression stops the chain before the workflow starts. If both input records and context are absent or empty, no explicit input envelope is supplied and normal workflow defaults apply. Empty records do not request an explicit empty input batch.

The 1,000-record cap applies to the authored array; evaluated input is also validated when the run is requested. Default request limits are 1,000 input records, 1,048,576 bytes of serialized input records, and 65,536 bytes of serialized trigger context. Byte size is measured as UTF-8 JSON, so it differs from string character count. Deployment limits can vary; an oversized input is rejected before work starts. The context depth count starts with the top object as level one and counts nested objects and arrays; scalar leaves do not add container levels. Bindings that expand into larger nested structures must still fit the accepted runtime input.

The action waits for the observed run outcome, then refreshes the source on success before continuing. Check the source's `error` before relying on refreshed records: a successful run and a successful follow-up read are separate outcomes. A read failure can leave the run marked succeeded while the chain continues with the source error set. Failed or cancelled runs stop the chain. A source's `runDeadlineMs` controls how long the app waits after the run is accepted: default 300,000 milliseconds, minimum 1,000, maximum 7,200,000. When this observation deadline passes without a terminal outcome, the app reports a warning and stops the remaining actions. The outcome is unknown; the deadline does not cancel the workflow. Check its activity before starting a duplicate operation. Normal observation checks about every two seconds initially, then every five seconds after a minute; a source with step-progress enabled backs off after 20 seconds. Rate-limit waits consume the observation deadline. Workflow concurrency policy can attach a request to an already-active run, so repeated clicks are not a guarantee of independent new runs.

### watch: observe work started by ingestion

```json
{
  "type": "triggerDataSource",
  "dataSourceId": "IngestResults",
  "mode": "watch",
  "ingestId": "{{ components.Uploader.lastIngestId }}"
}
```

Watch does not start work. It follows background runs correlated to a completed upload's ingest ID. Attach it to an uploader event only when that component publishes an ingest ID at that point; use its component reference to choose the correct event. Watch is read-only and does not require triggering to be enabled. It observes the app's own workflow-records source; an app-output source exposes records without access to the producer's run observation.

The authored `ingestId` can be a binding or a literal correlation string. Its evaluated result must be a nonempty string no longer than 64 characters. Missing, null, numeric, or oversized results fail, including in environments where observation is unavailable. Use `lastIngestId` with the file uploader's `onUploaded` event. For a resumed ingest batch, use `resumeIngestId` with `onProcessingResumed` instead of an unrelated previous upload's ID. See [File upload](/docs/components/file-upload) for the complete published-state and event contract.

Watch normally checks about every three seconds. It allows 60 seconds for the first correlated run to appear and five minutes total for observed runs to reach a terminal state, with at most 100 polling attempts. Slow requests and rate-limit waits consume these windows rather than extending them. Optional progress display can make later polling less frequent. The observer requests up to ten correlated runs per check.

When all returned runs succeed, the source refreshes before the next action. As with run, check the source's `error` before claiming the records refreshed successfully. If any returned run fails or is cancelled, or the observation window expires, the chain stops with an error. This does not cancel background processing. When the runtime does not provide observation, watch performs an ordinary fetch after validating the ID; it does not imply that background processing finished. Account for this in [Preview](/docs/builder/preview), where running real workflows is unavailable.

A newer run or watch for the same source supersedes the earlier operation's remaining action chain silently. A new operation may still be running; do not interpret the absence of an old notification as completion. Record fetches also protect newer data from being overwritten by an older response.

## setStateValue

Write a value into app state for other bindings and later actions to read.

| Field | Required, default, and limits |
| --- | --- |
| `type` | Required; `"setStateValue"`. |
| `key` | Required; dot-separated app state path, 1–256 characters. Each segment begins with a letter or underscore and then contains letters, digits, or underscores. |
| `value` | Optional; literal value or a top-level binding; omitted means `undefined`. |
| `condition` | Optional/null string; at most 2,048 characters. |

```json
{
  "type": "setStateValue",
  "key": "selection.orderId",
  "value": "{{ components.Orders.selectedRow?.id ?? null }}"
}
```

The path is relative to `app`: read this result as `app.selection.orderId`. Do not prefix the key with `app.` unless you intentionally want a nested state property named app. Missing intermediate objects are created. An intermediate scalar or array is replaced with an object to permit the nested write, so choose a path that does not collide with existing data.

Segments named `__proto__`, `constructor`, or `prototype` are forbidden; empty segments, bracket syntax, spaces, and hyphens are invalid. The action changes app interface state. It does not save a record, change the app definition, or persist a user preference automatically.

For this action, a binding is evaluated only when the entire `value` is a binding-containing string. A literal object whose nested property contains `"{{ app.count }}"` keeps that nested text literally. To produce a calculated object, use one typed expression:

```json
{
  "type": "setStateValue",
  "key": "filters",
  "value": "{{ ({ search: components.Search.value, active: true }) }}"
}
```

Values are copied into JSON-safe state. Non-finite numbers become null; undefined object properties are omitted and undefined array entries become null. A top-level omitted value remains undefined, which clears the usable value without deleting the key.

## setComponentValue

Set a component's published `value` field.

| Field | Required, default, and limits |
| --- | --- |
| `type` | Required; `"setComponentValue"`. |
| `componentId` | Required; an existing component ID. |
| `value` | Optional; literal value or a top-level binding; omitted means `undefined`. |
| `condition` | Optional/null string; at most 2,048 characters. |

```json
{ "type": "setComponentValue", "componentId": "Search", "value": "" }
```

Use this to clear an input or copy a selected value into an editable component. Match the value's type to the component: text for a text input, a boolean for a boolean control, and the appropriate option value for a select. The action addresses `value` specifically; it does not set arbitrary properties or selection fields such as `selectedRow`.

A calculated value uses the same top-level binding and JSON-safe copying rules as `setStateValue`. Nested bindings inside a literal object are not recursively expanded. Setting a component value is a state update, not a simulated click or guaranteed submission event; wire the next required action explicitly.

## showNotification

Display a notification to the current viewer.

| Field | Required, default, and limits |
| --- | --- |
| `type` | Required; `"showNotification"`. |
| `message` | Required; authored string of 1–2,048 characters; can contain bindings. |
| `variant` | `"info"`, `"success"`, `"warning"`, or `"error"`; defaults to `"info"`. |
| `condition` | Optional/null string; at most 2,048 characters. |

```json
{
  "type": "showNotification",
  "message": "Loaded {{ len(dataSources.Orders.data ?? []) }} orders",
  "variant": "success"
}
```

Mixed text creates a readable message. A standalone binding is evaluated, then converted to ordinary string text; null or undefined becomes empty text. An object returned by a standalone binding uses ordinary string conversion, so use `string(...)` or `json(...)` explicitly when you need object contents in the message.

The authored length cap applies before evaluation; expression and produced-string limits also apply to bindings. Notifications do not send email, contact another user, or indicate that a previous operation succeeded unless your chain actually awaited that operation. Place success notifications after run or watch actions, and remember that an unavailable watch can fall back to fetch.

## navigate

Navigate the current viewer to a destination.

| Field | Required, default, and limits |
| --- | --- |
| `type` | Required; `"navigate"`. |
| `to` | Required; authored string of 1–2,048 characters; can contain bindings. |
| `target` | `"self"` for the current browsing context or `"blank"` for a new one; defaults to `"blank"`. |
| `condition` | Optional/null string; at most 2,048 characters. |

```json
{
  "type": "navigate",
  "to": "https://example.com/help",
  "target": "blank"
}
```

The destination must resolve to a nonempty string. Relative destinations and HTTP or HTTPS destinations are accepted; schemes such as `javascript:`, `data:`, `blob:`, and `file:` are blocked. A null, object, number, or empty evaluated target fails the action. Validate dynamic destinations and provide the intended target explicitly when staying in the same window matters.

Navigation changes location. It does not grant access to the destination; a linked app or protected page still applies its own authentication and permissions. A current-context navigation can leave the app, so put necessary state changes or awaited work before it.

## Complete sequence example

This chain remembers a search, runs its workflow, and reports the refreshed result count. The run reads the state saved by the first action; the notification requires a successful follow-up record read.

```json
{
  "event": "onClick",
  "actions": [
    {
      "type": "setStateValue",
      "key": "lastSearch",
      "value": "{{ components.Search.value }}"
    },
    {
      "type": "triggerDataSource",
      "dataSourceId": "SearchResults",
      "mode": "run",
      "inputRecords": [{ "query": "{{ app.lastSearch }}" }]
    },
    {
      "type": "showNotification",
      "message": "Found {{ len(dataSources.SearchResults.data ?? []) }} results",
      "variant": "success",
      "condition": "{{ dataSources.SearchResults.error == null }}"
    }
  ]
}
```

If the run fails, times out, or is superseded, the notification does not execute, while `app.lastSearch` retains the earlier write. Offline operations that are unavailable stop the remaining chain with an informational notification. Test valid input, false conditions, missing selections, failures, and repeated clicks in Preview and the permitted live app. See [Events and actions](/docs/builder/events-and-actions), [Expression reference](/docs/builder/expression-reference), and [Troubleshooting](/docs/builder/troubleshooting).
