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

# storage Operation

> Key-value and object storage: KV (key-value), R2 (objects), Cache (edge cache)

Access Cloudflare storage primitives for simple data persistence.

<Note>
  For SQL databases and structured queries, see the [data operation](/conductor/operations/data).
</Note>

## Configuration

```yaml theme={null}
config:
  type: string       # kv, r2, cache
  action: string     # get, put, delete, list
  [type-specific options]
```

## KV (Key-Value Store)

Global key-value cache with eventual consistency. Perfect for configuration, sessions, and caching.

### GET Operation

```yaml theme={null}
operations:
  - name: get-user
    operation: storage
    config:
      type: kv
      action: get
      key: user-${input.id}
```

**Output**:

```typescript theme={null}
{
  value: any | null     // Parsed JSON value
  found: boolean        // true if key exists
  metadata: object      // Optional metadata
}
```

### PUT Operation

```yaml theme={null}
operations:
  - name: cache-user
    operation: storage
    config:
      type: kv
      action: put
      key: user-${input.id}
      value: ${input.data}
      expirationTtl: 3600  # Expires in 1 hour
```

**Options**:

* `expirationTtl` (number) - Seconds until expiration
* `expiration` (number) - Unix timestamp for expiration
* `metadata` (object) - Custom metadata (max 1KB)

### DELETE Operation

```yaml theme={null}
operations:
  - name: invalidate-cache
    operation: storage
    config:
      type: kv
      action: delete
      key: user-${input.id}
```

### LIST Operation

```yaml theme={null}
operations:
  - name: list-users
    operation: storage
    config:
      type: kv
      action: list
      prefix: user-
      limit: 100
```

**Options**:

* `prefix` (string) - Filter keys by prefix
* `limit` (number) - Max results (default: 1000, max: 1000)
* `cursor` (string) - Pagination cursor

**Output**:

```typescript theme={null}
{
  keys: Array<{
    name: string
    expiration?: number
    metadata?: object
  }>
  list_complete: boolean
  cursor?: string
}
```

## R2 (Object Storage)

S3-compatible object storage for files, images, backups, and large data.

### GET Object

```yaml theme={null}
operations:
  - name: get-file
    operation: storage
    config:
      type: r2
      action: get
      key: documents/${input.filename}
```

**Output**:

```typescript theme={null}
{
  body: ReadableStream | string | ArrayBuffer
  httpMetadata: {
    contentType?: string
    contentLanguage?: string
    contentDisposition?: string
    contentEncoding?: string
    cacheControl?: string
    cacheExpiry?: Date
  }
  customMetadata: Record<string, string>
  size: number
  etag: string
  uploaded: Date
}
```

### PUT Object

```yaml theme={null}
operations:
  - name: upload-file
    operation: storage
    config:
      type: r2
      action: put
      key: documents/${input.filename}
      value: ${input.content}
      httpMetadata:
        contentType: ${input.contentType}
      customMetadata:
        uploadedBy: ${input.userId}
        category: ${input.category}
```

**Options**:

* `httpMetadata` (object) - Standard HTTP metadata
* `customMetadata` (object) - Custom key-value metadata (max 2KB)

### DELETE Object

```yaml theme={null}
operations:
  - name: delete-file
    operation: storage
    config:
      type: r2
      action: delete
      key: documents/${input.filename}
```

### LIST Objects

```yaml theme={null}
operations:
  - name: list-files
    operation: storage
    config:
      type: r2
      action: list
      prefix: documents/
      limit: 1000
```

**Options**:

* `prefix` (string) - Filter by prefix
* `limit` (number) - Max results (default: 1000)
* `delimiter` (string) - Directory delimiter
* `cursor` (string) - Pagination cursor
* `include` (string\[]) - Include metadata: `['httpMetadata', 'customMetadata']`

**Output**:

```typescript theme={null}
{
  objects: Array<{
    key: string
    size: number
    etag: string
    uploaded: Date
    httpMetadata?: object
    customMetadata?: object
  }>
  truncated: boolean
  cursor?: string
  delimitedPrefixes: string[]
}
```

### HEAD Object (Metadata Only)

```yaml theme={null}
operations:
  - name: check-file
    operation: storage
    config:
      type: r2
      action: head
      key: documents/${input.filename}
```

Returns object metadata without downloading the body.

## Cache API

Edge cache for ultra-fast data access across Cloudflare's global network.

### GET Operation

```yaml theme={null}
operations:
  - name: get-cached-data
    operation: storage
    config:
      type: cache
      action: get
      key: api-response-${input.id}
```

### PUT Operation

```yaml theme={null}
operations:
  - name: cache-api-response
    operation: storage
    config:
      type: cache
      action: put
      key: api-response-${input.id}
      value: ${previous.api-call.data}
      ttl: 3600  # 1 hour
```

**Options**:

* `ttl` (number) - Seconds until expiration
* `cacheControl` (string) - Custom Cache-Control header

### DELETE Operation

```yaml theme={null}
operations:
  - name: invalidate-cache
    operation: storage
    config:
      type: cache
      action: delete
      key: api-response-${input.id}
```

## Choosing Between Storage Types

| Type      | Use Case                      | Durability                | Speed              | Global |
| --------- | ----------------------------- | ------------------------- | ------------------ | ------ |
| **KV**    | Config, sessions, cache       | High (replicated)         | Fast (\<1ms local) | ✅ Yes  |
| **R2**    | Files, images, backups        | Very High (S3-compatible) | Medium (10-50ms)   | ✅ Yes  |
| **Cache** | Temporary data, API responses | None (can evict)          | Very Fast (\<1ms)  | ✅ Yes  |

## When to Use storage vs data

Use **storage** when you need:

* ✅ Simple key-value lookups
* ✅ Object/file storage
* ✅ Temporary caching
* ✅ Global edge caching

Use **data** when you need:

* ❌ SQL queries
* ❌ Relational data
* ❌ Transactions
* ❌ Complex queries with JOINs

See [data operation](/conductor/operations/data) for database queries.

## Examples

### Session Management with KV and Cookies

Combine KV storage with the [cookies operation](/conductor/operations/cookies) for complete session management:

```yaml theme={null}
agents:
  # Read session ID from cookie
  - name: get-session
    condition: ${input.cookies.session_id}
    operation: storage
    config:
      type: kv
      action: get
      key: session-${input.cookies.session_id}

  # Create new session if none exists
  - name: create-session
    condition: ${!input.cookies.session_id}
    operation: code
    config:
      handler: |
        const id = crypto.randomUUID()
        return { sessionId: id, data: { created: Date.now() } }

  # Store new session in KV
  - name: save-session
    condition: ${!input.cookies.session_id}
    operation: storage
    config:
      type: kv
      action: put
      key: session-${create-session.output.sessionId}
      value: ${create-session.output.data}
      expirationTtl: 86400  # 24 hours

  # Set session cookie
  - name: set-session-cookie
    condition: ${!input.cookies.session_id}
    operation: cookies
    config:
      action: set
      name: session_id
      value: ${create-session.output.sessionId}
      httpOnly: true
      secure: true
      sameSite: lax
      maxAge: 86400
      purpose: essential

output:
  session: ${get-session.output.value || create-session.output.data}
```

### File Upload to R2

```yaml theme={null}
operations:
  - name: upload-file
    operation: storage
    config:
      type: r2
      action: put
      key: uploads/${input.userId}/${input.filename}
      value: ${input.fileData}
      contentType: ${input.mimeType}
```

### API Response Caching

```yaml theme={null}
operations:
  # Try cache first
  - name: check-cache
    operation: storage
    config:
      type: cache
      action: get
      key: api-${input.endpoint}

  # Call API if cache miss
  - name: call-api
    operation: http
    when: ${previous.check-cache.found === false}
    config:
      url: https://api.example.com/${input.endpoint}

  # Cache the response
  - name: cache-response
    operation: storage
    when: ${previous.call-api.success}
    config:
      type: cache
      action: put
      key: api-${input.endpoint}
      value: ${previous.call-api.data}
      ttl: 300  # 5 minutes
```

## Bindings Setup

Configure storage bindings in `wrangler.toml`:

```toml theme={null}
# KV binding
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-id"

# R2 binding
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "my-bucket"
```

## Performance Tips

### KV Best Practices

* Keep values under 25MB (max limit)
* Use expiration to auto-cleanup
* Leverage eventual consistency model
* Cache frequently accessed keys

### R2 Best Practices

* Use multipart upload for files >100MB
* Set appropriate content types
* Use prefixes for organization
* Leverage custom metadata for search

### Cache Best Practices

* Set reasonable TTLs
* Invalidate on writes
* Use cache keys carefully
* Monitor cache hit rates

## Storage Limits

| Storage   | Max Value Size  | Max Keys  | Operations     |
| --------- | --------------- | --------- | -------------- |
| **KV**    | 25 MB           | Unlimited | 1000 write/sec |
| **R2**    | 5 TB per object | Unlimited | Unlimited      |
| **Cache** | Varies by plan  | N/A       | Unlimited      |

## Related Operations

* [data](/conductor/operations/data) - SQL queries and database operations
* [http](/conductor/operations/http) - HTTP requests
* [queue](/conductor/operations/queue) - Message queues
