---
title: Attributes
description: Attach metadata to workflow runs for observability.
type: reference
summary: Add string attributes to a workflow run.
prerequisites:
  - /docs/foundations/workflows-and-steps
related:
  - /docs/observability
  - /docs/api-reference/workflow/set-attributes
  - /docs/api-reference/workflow-errors/workflow-world-error
---

# Attributes









[`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes appear in the Workflow CLI and web UI, and you can search and filter runs by them from either the [CLI](#from-the-cli) or the [Analytics API](/docs/api-reference/workflow-runtime/world/analytics).

You can also seed any attributes directly when starting a run:

{/* @skip-typecheck: abbreviated usage; orderWorkflow is defined below */}

```typescript lineNumbers
const run = await start(orderWorkflow, ["ord_123"], {
  attributes: { source: "checkout" }, // [!code highlight]
})
```

```typescript lineNumbers
import { setAttributes } from "workflow"

export async function orderWorkflow(orderId: string) {
  "use workflow"

  await setAttributes({ // [!code highlight]
    phase: "received", // [!code highlight]
    orderId, // [!code highlight]
  }) // [!code highlight]

  // ...work...

  await setAttributes({ phase: "complete" }) // [!code highlight]
}
```

## Usage

Call [`setAttributes`](/docs/api-reference/workflow/set-attributes) from a `"use workflow"` function or a `"use step"` function. Plain application code is not supported because there is no active workflow run to attach attributes to.

Values must be strings. Pass `undefined` to remove a key:

```typescript lineNumbers
import { setAttributes } from "workflow"

export async function cleanupAttributes() {
  "use workflow"

  await setAttributes({ staleKey: undefined }) // [!code highlight]
}
```

Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.

Each `setAttributes` call also writes one `attr_set` event, and that event's complete data (keys, values, and writer metadata) must fit in 8192 UTF-8 JSON bytes (8KiB). Split large updates across several calls; the calls still share the 64-attribute limit. A call that exceeds a limit rejects with [`FatalError`](/docs/api-reference/workflow/fatal-error) before anything is written, so catch it when the metadata is best-effort.

## Reserved keys

When `start()` is called from inside a running workflow or step, the new run is automatically tagged with two reserved attributes:

* `$parentRunId`: the run that started it.
* `$rootRunId`: the root of the chain. It is inherited, so every run in a daisy chain or fan-out shares one root id.

Top-level runs (started outside any workflow or step) are not tagged.

## Viewing attributes

The run details panel in the observability UI shows the run's current attributes as key-value rows. Reserved `$`-prefixed keys are marked with a badge and sorted after user keys:

<img alt="Run details panel showing the Attributes card with reserved keys badged" src={__img0} placeholder="blur" />

Each `setAttributes` call appears on the trace timeline as a diamond marker at the moment the attributes were written:

<img alt="Trace timeline with attr_set diamond markers on the run row" src={__img1} placeholder="blur" />

Expanding an `attr_set` event (in the run sidebar or the Events tab) shows the changed keys, removed keys, and whether the write came from the workflow body or a step (with the attempt number):

<img alt="Expanded attr_set events showing changes and the writer" src={__img2} placeholder="blur" />

## Searching and filtering by attributes

### From the CLI

`workflow inspect attributes` lists the keys recorded on this project's runs,
with how many runs carry each and when it was first and last seen:

```bash
workflow inspect attributes
```

Pass one or more `--attribute key=value` pairs to `inspect runs` to list the
runs carrying them. Repeatable up to 8 times:

```bash
workflow inspect runs --attribute phase=received --status running
```

Both require a backend with the analytics read path. `--attribute` is ignored
with a warning on backends without one, and `inspect attributes` reports that
it is unavailable.

### From the Analytics API

The [Analytics API](/docs/api-reference/workflow-runtime/world/analytics) can discover which attribute keys exist and filter run listings by them. The `analytics` namespace is optional on `World`, so feature-detect it before use; it is absent on local, Postgres, and other custom Worlds:

```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = await getWorld();
if (!world.analytics) {
  throw new Error("This World does not support analytics queries"); // [!code highlight]
}

// Which attribute keys exist, and on how many runs?
const keys = await world.analytics.attributes.list();

// List runs whose latest attributes match every pair
const stuck = await world.analytics.runs.list({
  attributes: { phase: "received" }, // [!code highlight]
});
```

Matching is latest-write-wins: once the run above writes `phase: "complete"`, it stops matching `phase: "received"`.

## Behavior

* Attributes require a World implementing spec version 4 or later.
* Writes from workflow and step bodies append native `attr_set` events and immediately materialize `run.attributes`.
* Storage errors surface rather than being silently ignored: transient errors on workflow-body writes are retried, and a write the World rejects as invalid (for example, exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
* Step-body storage errors throw from `setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
* Reading and querying: each run's current attributes are returned on the run objects from the [Storage](/docs/api-reference/workflow-runtime/world/storage) and [Analytics](/docs/api-reference/workflow-runtime/world/analytics) APIs, and the Analytics API supports discovering attribute keys and filtering run listings by key=value pairs (see [Searching and filtering by attributes](#searching-and-filtering-by-attributes)). On Worlds without the optional `analytics` namespace, attributes are readable on run objects but not searchable.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)