> ## Documentation Index
> Fetch the complete documentation index at: https://docs.servflow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Key-Value Storage

> Store and retrieve values by key for caching and workflow state

Key-value actions store and retrieve values by key. This is useful for caching frequently accessed data, passing state between workflow executions, and implementing cache-aside patterns.

<Note>
  This storage is **persistent** — values survive a server restart. It is a simple key-value store, not a replacement for a database: for structured, queryable data use [Data Operations](/concepts/actions/data-operations).
</Note>

<Note>
  This is unrelated to an agent's `accessMemory` setting, which gives an agent file tools over its workspace. See [AI Agents](/concepts/actions/ai-agents#access-memory).
</Note>

***

## store\_key

Stores a key-value pair in persistent storage.

### Key

The unique identifier for storing the data. Use template syntax to create dynamic keys.

|              |        |
| ------------ | ------ |
| **YAML Key** | `key`  |
| **Type**     | string |
| **Required** | Yes    |

<Tip>
  Include unique identifiers in your keys to avoid collisions. For example: `user_{{ param "user_id" }}` or `cache_{{ .request_hash }}`.
</Tip>

### Value

The data to store. Can be any value, including JSON objects.

|              |         |
| ------------ | ------- |
| **YAML Key** | `value` |
| **Type**     | string  |
| **Required** | Yes     |

Use `{{ jsonout .action_result }}` to store complex objects from previous actions.

### Output

`{{ .step_id }}` holds the value that was stored.

### Example

Store a user object:

```yaml theme={null}
actions:
  cache_user:
    type: store_key
    config:
      key: "user_{{ param \"user_id\" }}"
      value: "{{ jsonout .fetch_user }}"
    next: response.success
```

Store a computed value:

```yaml theme={null}
actions:
  store_token:
    type: store_key
    config:
      key: "auth_token_{{ .user.id }}"
      value: "{{ .generate_token }}"
    next: action.use_token
```

***

## get\_key

Retrieves a value from persistent storage by key.

### Key

The key to look up.

|              |        |
| ------------ | ------ |
| **YAML Key** | `key`  |
| **Type**     | string |
| **Required** | Yes    |

### Fail If Empty

Whether to fail the step when the key is not set. When `false`, a missing key yields an empty result and the workflow continues.

|              |               |
| ------------ | ------------- |
| **YAML Key** | `failIfEmpty` |
| **Type**     | boolean       |
| **Required** | No            |
| **Default**  | `false`       |

### Output

`{{ .step_id }}` holds the stored value, or is empty when the key is not set.

### Example

Retrieve a cached user:

```yaml theme={null}
actions:
  get_cached_user:
    type: get_key
    config:
      key: "user_{{ param \"user_id\" }}"
    next: conditional.cache_hit
```

Require the key to exist:

```yaml theme={null}
actions:
  load_config:
    type: get_key
    config:
      key: "app_config"
      failIfEmpty: true
    next: action.apply_config
    fail: response.not_configured
```

***

## Common Patterns

### Cache-Aside Pattern

Check the cache first, fetch from the database if not found, then update the cache:

```yaml theme={null}
actions:
  check_cache:
    type: get_key
    config:
      key: "data_{{ param \"id\" }}"
    next: conditional.is_cached

conditionals:
  is_cached:
    expression: "{{ notempty .check_cache \"\" }}"
    onTrue: response.from_cache
    onFalse: action.fetch_from_db

actions:
  fetch_from_db:
    type: fetch
    config:
      integration: my_database
      table: data
      filters:
        - field: id
          operator: eq
          value: "{{ param \"id\" }}"
      single: true
    next: action.update_cache

  update_cache:
    type: store_key
    config:
      key: "data_{{ param \"id\" }}"
      value: "{{ jsonout .fetch_from_db }}"
    next: response.success

responses:
  from_cache:
    statusCode: 200
    body:
      source: "cache"
      data: "{{ .check_cache }}"

  success:
    statusCode: 200
    body:
      source: "database"
      data: "{{ .fetch_from_db }}"
```

### Session Storage

Store and retrieve user session data:

```yaml theme={null}
actions:
  store_session:
    type: store_key
    config:
      key: "session_{{ param \"session_id\" }}"
      value: |
        {
          "user_id": "{{ .authenticated_user.id }}",
          "login_time": "{{ now }}",
          "preferences": {{ jsonout .user_preferences }}
        }
    next: response.session_created
```

```yaml theme={null}
actions:
  get_session:
    type: get_key
    config:
      key: "session_{{ param \"session_id\" }}"
    next: conditional.session_valid

conditionals:
  session_valid:
    expression: "{{ notempty .get_session \"\" }}"
    onTrue: action.process_request
    onFalse: response.session_expired
```

### Rate Limiting Counter

Track request counts for rate limiting:

```yaml theme={null}
actions:
  get_request_count:
    type: get_key
    config:
      key: "rate_{{ param \"api_key\" }}"
    next: action.increment_count

  increment_count:
    type: javascript
    config:
      script: |
        function servflowRun(vars) {
          const current = parseInt(vars.get_request_count) || 0;
          return current + 1;
        }
    next: action.store_count

  store_count:
    type: store_key
    config:
      key: "rate_{{ param \"api_key\" }}"
      value: "{{ .increment_count }}"
    next: conditional.check_limit

conditionals:
  check_limit:
    expression: "{{ lt .increment_count 100 }}"
    onTrue: action.process_request
    onFalse: response.rate_limited
```

### Temporary Token Storage

Store and validate temporary tokens:

```yaml theme={null}
actions:
  generate_reset_token:
    type: javascript
    config:
      script: |
        function servflowRun(vars) {
          return Math.random().toString(36).substring(2, 15);
        }
    next: action.store_reset_token

  store_reset_token:
    type: store_key
    config:
      key: "reset_{{ .generate_reset_token }}"
      value: |
        {
          "user_id": "{{ .fetch_user.id }}",
          "email": "{{ .fetch_user.email }}",
          "created_at": "{{ now }}"
        }
    next: action.send_reset_email
```

```yaml theme={null}
actions:
  validate_reset_token:
    type: get_key
    config:
      key: "reset_{{ param \"token\" }}"
    next: conditional.token_valid

conditionals:
  token_valid:
    expression: "{{ notempty .validate_reset_token \"\" }}"
    onTrue: action.allow_password_reset
    onFalse: response.invalid_token
```

***

## Best Practices

<Tip>
  **Key Naming**: Use consistent key naming conventions with prefixes to organize your data. For example: `user_`, `session_`, `cache_`, `rate_`.
</Tip>

<Note>
  **Data Size**: Keep stored values reasonably sized. For large datasets, consider storing only essential fields or IDs and fetching full data when needed.
</Note>

<Warning>
  **No TTL**: This store doesn't support automatic expiration. Implement your own expiration logic by storing timestamps and checking them during retrieval.
</Warning>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Data Operations" icon="database" href="/concepts/actions/data-operations">
    Use a database for structured, queryable data.
  </Card>

  <Card title="Transformation" icon="wand-magic-sparkles" href="/concepts/actions/transformation">
    Process and transform cached data with JavaScript.
  </Card>

  <Card title="Flow Control" icon="code-branch" href="/concepts/actions/flow-control">
    Combine caching with parallel data fetching.
  </Card>

  <Card title="Actions Overview" icon="play" href="/concepts/actions/overview">
    Learn the fundamentals of ServFlow actions.
  </Card>
</CardGroup>
