---
title: "Attribute"
description: "An Attribute is durable data that belongs to one Flow run. Steps and"
canonical_url: "https://docs.superdurable.io/concepts/primitives/attribute/"
source: "docs/content/concepts/primitives/attribute.mdx"
locale: "en"
---

# Attribute

An **Attribute** is durable data that belongs to one Flow run. Steps and
RPCs read and write it. Dex stores it with the Flow, so you do not need a
database for intermediate state.

An Attribute lives as long as its Flow. Dex deletes it when it deletes the Flow,
after the retention period that follows Flow closure. To keep the data longer,
see [Keep data after the Flow](https://docs.superdurable.io/concepts/primitives/attribute.md#attribute-store-projection).

## When to use an Attribute

Use an Attribute for state that the Flow decides on: an order status, a
counter, a customer profile, or the result of an earlier Step. Use a
[Channel](https://docs.superdurable.io/concepts/primitives/channel.md) for messages that a Step waits for and consumes.
Use a [Stream](https://docs.superdurable.io/concepts/primitives/stream.md) for live progress that a user can afford to
lose.

<a id="define-an-attribute"></a>

## Use an Attribute

Declare each Attribute in the Flow's persistence schema. A Step or RPC can then
read or write it. The Worker rejects an Attribute that the schema does not
declare. Setting an Attribute to null deletes it.

Attribute writes take effect only when the Step method or RPC succeeds. See
[Step guarantees](https://docs.superdurable.io/concepts/step.md#guarantees).

**Python**

```python
class AttributeStep(Step[str]):
    def __init__(self, flow: "AttributeFlow") -> None:
        self.flow = flow

    def wait_for(self, context: Context, input: str) -> Wait:
        self.flow.status.set(context, "processing")
        return Wait.skip_immediately()

    def execute(self, context: Context, input: str) -> StepDecision:
        self.flow.status.set(context, "completed")
        return graceful_complete(input)


class AttributeFlow(Flow[str]):
    status = Attribute(
        "primitive-attribute-status",
        str,
        index=AttributeIndex(IndexType.KEYWORD, "keyword1"),
    )

    def __init__(self) -> None:
        self.start = AttributeStep(self)

    def get_steps(self) -> StepList[str]:
        return StepList.start_step(self.start)

    def get_persistence_schema(self) -> PersistenceSchema:
        return PersistenceSchema.of(self.status)
```

Example: [examples/python/dex_examples/primitives/attribute/attribute_flow.py](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/primitives/attribute/attribute_flow.py)

**Go**

```go
var Status = dex.DefineAttribute[string](
	"primitive-attribute-status",
	dex.Indexed(dex.AttributeIndex{Type: dex.IndexKeyword, IndexKey: "keyword1"}),
)

type AttributeFlow struct {
	dex.FlowDefaults
}

func (*AttributeFlow) GetPersistenceSchema() dex.PersistenceSchema {
	return dex.PersistenceSchema{Attributes: []dex.AttributeDef{Status}}
}

type attributeStep struct {
	dex.StepDefaults
}

func (attributeStep) WaitFor(ctx dex.Context, input string) (*dex.Wait, error) {
	if err := Status.Set(ctx, "processing"); err != nil {
		return nil, err
	}
	return dex.SkipWaitImmediately(), nil
}

func (attributeStep) Execute(ctx dex.Context, input string) (*dex.StepDecision, error) {
	if err := Status.Set(ctx, "completed"); err != nil {
		return nil, err
	}
	return dex.GracefulComplete(input), nil
}
```

Example: [examples/go/primitives/attribute/workflow.go](https://github.com/superdurable/dex/tree/main/examples/go/primitives/attribute/workflow.go)

**Java**

```java
public final class AttributeFlow implements Flow<String> {
    private final Attribute<String> status =
            Attribute.define(
                    "primitive-attribute-status",
                    String.class,
                    new AttributeIndex(AttributeIndex.Type.KEYWORD, "keyword1"));
    private final AttributeStep start = new AttributeStep();

    @Override
    public StepList<String> getSteps() {
        return StepList.startStep(start);
    }

    @Override
    public PersistenceSchema getPersistenceSchema() {
        return PersistenceSchema.of(status);
    }

    final class AttributeStep implements Step<String> {
        @Override
        public Wait waitFor(final Context context, final String input) {
            status.set(context, "processing");
            return Wait.skipImmediately();
        }

        @Override
        public StepDecision execute(final Context context, final String input) {
            status.set(context, "completed");
            return StepDecision.gracefulComplete(input);
        }
    }
}
```

Example: [examples/java/src/main/java/io/superdurable/dex/primitives/attribute/AttributeFlow.java](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/primitives/attribute/AttributeFlow.java)

**TypeScript**

```typescript
class AttributeStep implements Step<string> {
  public readonly inputCodec = stringCodec;

  public constructor(private readonly flow: AttributeFlow) {}

  public getStepType(): string {
    return "AttributeStep";
  }

  public waitFor(context: Context, input: string): Wait {
    this.flow.status.set(context, "processing");
    return Wait.skipImmediately();
  }

  public execute(context: Context, input: string): StepDecision {
    this.flow.status.set(context, "completed");
    return gracefulComplete(input);
  }
}

export class AttributeFlow implements Flow<string> {
  public readonly status = new Attribute("primitive-attribute-status", stringCodec, {
    type: IndexType.KEYWORD,
    indexKey: "keyword1",
  });
  private readonly start = new AttributeStep(this);

  public getSteps() {
    return StepList.startStep(this.start);
  }

  public getPersistenceSchema(): PersistenceSchema {
    return { attributes: [this.status] };
  }
}
```

Example: [examples/typescript/src/primitives/attribute/attribute-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/primitives/attribute/attribute-flow.ts)

**Rust**

```rust
pub struct AttributeFlow {
    status: Attribute<String>,
    email: Attribute<String>,
    progress: AttributeMap<String>,
    start: AttributeStep,
}

impl Default for AttributeFlow {
    fn default() -> Self {
        let status = Attribute::new("primitive-attribute-status")
            .indexed(AttributeIndex::keyword().with_key("keyword1"));
        let email = Attribute::new("primitive-attribute-email").sync_to_attribute_store();
        let progress = AttributeMap::new("primitive-attribute-progress")
            .indexed(AttributeIndex::keyword().with_key("keyword2"));
        Self {
            start: AttributeStep {
                status: status.clone(),
                progress: progress.clone(),
            },
            status,
            email,
            progress,
        }
    }
}

impl Flow for AttributeFlow {
    type StartInput = String;

    fn steps(&self) -> StepList<'_, Self::StartInput> {
        StepList::start(&self.start)
    }

    fn persistence(&self) -> PersistenceSchema {
        PersistenceSchema::new()
            .attribute(&self.status)
            .attribute(&self.email)
            .attribute_map(&self.progress)
    }
}

struct AttributeStep {
    status: Attribute<String>,
    progress: AttributeMap<String>,
}

impl Step for AttributeStep {
    type Input = String;

    fn wait_for(&self, context: &mut Context, _input: Self::Input) -> HandlerResult<Wait> {
        self.status.set(context, "processing".to_owned())?;
        self.progress
            .set(context, "payment", "authorized".to_owned())?;
        Ok(Wait::skip_immediately())
    }

    fn execute(&self, context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        self.status.set(context, "completed".to_owned())?;
        Ok(StepDecision::graceful_complete(input))
    }
}
```

Example: [examples/rust/src/primitives/attribute/flow.rs](https://github.com/superdurable/dex/tree/main/examples/rust/src/primitives/attribute/flow.rs)

<a id="attributemap"></a>

## AttributeMap

An **AttributeMap** is a set of Attributes with keys chosen at run time. Use it
for values that grow with your data, such as the status of each payment in an
order. Dex stores each key separately, so a write does not rewrite the whole
collection.

**Python**

```python
progress = AttributeMap("primitive-attribute-progress", str)

progress.set(context, "payment", "authorized")
```

Example: [examples/python/dex_examples/primitives/attribute/attribute_flow.py](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/primitives/attribute/attribute_flow.py)

**Go**

```go
var Progress = dex.DefineAttributeMap[string]("primitive-attribute-progress")

if err := Progress.Set(ctx, "payment", "authorized"); err != nil {
	return nil, err
}
```

Example: [examples/go/primitives/attribute/workflow.go](https://github.com/superdurable/dex/tree/main/examples/go/primitives/attribute/workflow.go)

**Java**

```java
private final AttributeMap<String> progress =
        AttributeMap.define("primitive-attribute-progress", String.class);

progress.set(context, "payment", "authorized");
```

Example: [examples/java/src/main/java/io/superdurable/dex/primitives/attribute/AttributeFlow.java](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/primitives/attribute/AttributeFlow.java)

**TypeScript**

```typescript
public readonly progress = new AttributeMap("primitive-attribute-progress", stringCodec);

this.flow.progress.set(context, "payment", "authorized");
```

Example: [examples/typescript/src/primitives/attribute/attribute-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/primitives/attribute/attribute-flow.ts)

**Rust**

```rust
let progress = AttributeMap::new("primitive-attribute-progress");

self.progress
    .set(context, "payment", "authorized".to_owned())?;
```

Example: [examples/rust/src/primitives/attribute/flow.rs](https://github.com/superdurable/dex/tree/main/examples/rust/src/primitives/attribute/flow.rs)

<a id="initial-attribute-values"></a>

<a id="read-from-outside-a-flow"></a>

<a id="indexed-attributes"></a>

## Read and search Attributes

Code outside the Flow reads Attributes through the [Client](https://docs.superdurable.io/concepts/client.md).
See [when a read needs an RPC](https://docs.superdurable.io/concepts/client.md#related-primitives) and
[Read Attributes from outside a Flow](https://docs.superdurable.io/reference/server/runtime-data.md#read-attributes-from-outside-a-flow).
To set values when the Flow starts, see [Initial values](https://docs.superdurable.io/reference/server/runtime-data.md#initial-attribute-values).

An indexed Attribute can be used in **SearchFlows** queries to find Flow
runs. The example above indexes the status Attribute. See
[Indexes](https://docs.superdurable.io/reference/server/runtime-data.md#attribute-indexes) for index types,
index keys, and AttributeMap indexes.

<a id="locking"></a>

## Locks

Two Steps or RPCs can write the same Attribute at the same time. Without a
lock, two that read and then write it can overwrite each other's update. See
[Step guarantees](https://docs.superdurable.io/concepts/step.md#guarantees). Use an
**Attribute lock** to run them one after the other. Dex acquires the lock before
the Step method or RPC starts and releases it when it finishes. A Step waits for
its lock. An RPC that cannot get a lock fails, and you retry it.

A lock affects only the Steps and RPCs that declare it. A Step or RPC that does
not declare it can still write the Attribute. Steps get a lock in the order they started waiting for it. An RPC does
not queue. It takes a free lock even when Steps are waiting for it. So a steady
stream of locked RPCs can delay those Steps. Keep locked RPCs short.

This Step locks a singleton Attribute and one AttributeMap instance while
**Execute** runs:

**Python**

```python
def get_step_options(self) -> StepOptions:
    return StepOptions(
        execute_lock_attributes=(
            self.flow.status.lock(),
            self.flow.progress.lock("payment"),
        ),
    )
```

Example: [examples/python/dex_examples/primitives/attribute/attribute_flow.py](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/primitives/attribute/attribute_flow.py)

**Go**

```go
func (attributeStep) GetStepOptions() *dex.StepOptions {
	return &dex.StepOptions{
		ExecuteLockAttributes: []dex.AttributeLock{
			dex.LockAttribute(Status),
			dex.LockAttributeMap(Progress, "payment"),
		},
	}
}
```

Example: [examples/go/primitives/attribute/workflow.go](https://github.com/superdurable/dex/tree/main/examples/go/primitives/attribute/workflow.go)

**Java**

```java
return StepOptions.newBuilder()
        .addExecuteLock(AttributeLock.of(status))
        .addExecuteLock(AttributeLock.of(progress, "payment"))
        .build();
```

Example: [examples/java/src/main/java/io/superdurable/dex/primitives/attribute/AttributeFlow.java](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/primitives/attribute/AttributeFlow.java)

**TypeScript**

```typescript
return {
  executeLockAttributes: [this.flow.status.lock(), this.flow.progress.lock("payment")],
};
```

Example: [examples/typescript/src/primitives/attribute/attribute-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/primitives/attribute/attribute-flow.ts)

**Rust**

```rust
StepOptions::new()
    .execute_lock(self.status.lock())
    .execute_lock(self.progress.lock("payment"))
```

Example: [examples/rust/src/primitives/attribute/flow.rs](https://github.com/superdurable/dex/tree/main/examples/rust/src/primitives/attribute/flow.rs)

<a id="whole-map-coordination"></a>

<a id="lock-waitfor"></a>

<a id="lock-execute"></a>

<a id="lock-an-rpc"></a>

<a id="rpc-lock-conflicts"></a>

See [Locks](https://docs.superdurable.io/reference/server/runtime-data.md#attribute-locks) for lock timing and **WaitFor**,
**Execute**, and RPC locks. See [RPC lock conflicts](https://docs.superdurable.io/reference/server/runtime-data.md#rpc-lock-conflicts)
and [whole-map coordination](https://docs.superdurable.io/reference/server/runtime-data.md#whole-map-coordination) for the rest.

<a id="hard-delete-attributes"></a>

## Short-lived secrets

A **hard-delete Attribute** holds a short-lived secret, such as an API key that
a Flow uses a few times and then discards. Deleting it erases the stored value
permanently. Neither Dex APIs nor Flow history can recover it. Unlike other
writes, a hard-delete write takes effect before Dex saves the handler's result.
See
[Hard-delete Attributes](https://docs.superdurable.io/reference/server/runtime-data.md#hard-delete-attributes)
for the requirements and their consequences.

<a id="how-it-fits-with-the-other-components"></a>

<a id="configuration"></a>

<a id="delivery-and-retries"></a>

<a id="attribute-store-local-development"></a>

<a id="attribute-store-projection"></a>

## Keep data after the Flow

Attribute Store sync copies selected Attributes to PostgreSQL tables that you
own. Each destination is called an **Attribute Store**. The tables keep the data
after Dex deletes the Flow. Your application and your reports query them with
SQL, and you can index them for your own queries.

To sync an Attribute:

1. Create the table before the server starts. Dex never creates, migrates, or
   backfills a table.
2. Configure the store in the server configuration.
3. Mark the Attribute or AttributeMap with **syncToAttributeStore**.
4. List the store name in the Flow's **FlowConfig** when you start the Flow.

What to expect:

- The Flow stays the source of truth. A table row holds the latest values, not
  a history. Do not make Flow decisions from the table.
- Dex writes in the background. A write never delays a Step, an RPC, or a
  SetAttributes call. Writes for the same Flow ID keep their order.
- When a write fails, Dex retries it for a limited time. It then drops the
  write and logs an error. Dex does not replay old values or fill a newly
  configured store.
- When a Flow completes or fails on its own, it stays running until its
  pending writes are written or dropped. StopFlow does not wait.
- Deleting an Attribute writes NULL to its column. Dex does not delete the row
  when the Flow closes.
- A store can split the Flow ID into several key columns, and can write each
  AttributeMap instance as a row of its own table.

The [Attribute Store sync](https://docs.superdurable.io/reference/server/attribute-store-sync.md) reference
covers configuration, table requirements, row keys, AttributeMap tables,
retries, metrics, and local development. The
[Entity Store](https://docs.superdurable.io/design-patterns/entity-store.md) pattern shows a complete example.

## Reference

The [Attribute](https://docs.superdurable.io/reference/python/attribute.md) page of each SDK reference lists the
API names in that language.

[Data primitive semantics](https://docs.superdurable.io/reference/server/runtime-data.md#attributes) covers
initial values, Client reads, indexes, hard-delete Attributes, and locks in
detail.
