---
title: "Key-value store and Storage - Workflow Suite"
description: "Remember values between runs: counters, flags with an expiry and the time of the last sync in the key-value store, larger JSON documents in Storage."
canonical: "https://docs.workflow-suite.app/key-value-store-and-storage"
---

# Key-value store and Storage

A run forgets everything when it ends. Two steps let workflows remember: the **Key-value store** for small values, counters and flags, and **Storage** for larger JSON documents. Both belong to your shop and are shared by all your workflows and functions.

| | Key-value store | Storage | Files |
| --- | --- | --- | --- |
| Made for | Small values, counters, flags | Larger JSON documents | Whole files: CSV, XML, PDF |
| Count up | Yes | No | No |
| Expiry | Optional, per value | No | No |
| Largest value | 64 KB | 64 KB to 512 KB, by plan | 5 MB to 50 MB, by plan |
| From code | `ctx.kv` | `ctx.storage` | Not available |

Files have their own page: [Files, SFTP and downloads](https://docs.workflow-suite.app/files-sftp-and-downloads.md).

## Key-value store

Add the step **Key-value store** and choose **What to do**:

| Choice | Fields | Result |
| --- | --- | --- |
| Read a value | **Key** | `steps.<id>.output.value`, empty when the key does not exist |
| Write a value | **Key**, **Value**, optional **Forget after (seconds)** | The stored value |
| Delete a value | **Key** | |
| Count up a number | **Key**, optional **Amount**, optional **Forget after (seconds)** | The new number in `steps.<id>.output.value` |

- **Key** is free text, up to 512 characters, and takes variables: `order:{{ event.payload.id }}:notified`.
- **Value** is text, a number or JSON. Valid JSON is stored as JSON.
- **Amount**: leave empty to count up by 1. A negative number counts down.
- **Forget after (seconds)**: leave empty to keep the value until a workflow deletes it. At most one year.

Counting up is safe when many runs do it at the same moment: every run gets its own number.

### Patterns

**Do something only once.** Read the key `order:<id>:notified`. A Condition checks **is empty**. On that branch do the work, then write the key, with an expiry so the store stays tidy.

**Count, then tell.** Count up `no-results:{{ event.payload.query }}` with **Forget after** set to 86,400 seconds. A Condition on `steps.<id>.output.value` **is equal to** 10 posts to Slack exactly once per day and search term. The storefront templates work this way.

**Remember the last run.** A scheduled workflow writes the current time at its end and reads it at its start, to fetch only what changed since.

## Storage

Add the step **Storage** and choose **What to do**: **Read a document**, **Write a document**, **Delete a document** or **List keys**.

- **Key**: any characters, 256 at most. For **List keys**, **Keys that start with** narrows the list; leave it empty to list every key. A list holds up to 100 keys, names and sizes only.
- **Value**: text, a number or JSON. Valid JSON is stored as JSON. When two runs write the same key at once, the last one wins.
- It is the same store your functions use as `ctx.storage`, so a workflow step can prepare a document that code reads later, and the other way round.

## Limits by plan

| Plan | Key-value keys | Storage in total | Storage keys | Largest document |
| --- | --- | --- | --- | --- |
| Free | 1,000 | 5 MB | 500 | 64 KB |
| Starter | 10,000 | 50 MB | 5,000 | 128 KB |
| Grow | 100,000 | 250 MB | 25,000 | 256 KB |
| Unlimited | 1,000,000 | 1 GB | 100,000 | 512 KB |

## In tests

In a preview, reads and lists run for real; writes, deletes and counting are previewed and change nothing. The debugger shows the key-value keys your steps use with their current values, and what a previewed write would have made of them. See [Test, preview and debug](https://docs.workflow-suite.app/test-preview-and-debug.md).

## Good to know

- A value can hold personal data if you put it there. Prefer ids over names and addresses. When your store asks Shopify to erase its data after uninstalling, both stores are erased too.
- Over the Developer API, a list returns keys and sizes, never values. A single read returns the value as stored. See [Developer API and MCP](https://docs.workflow-suite.app/developer-api-and-mcp.md).
- A temporary failure is retried. Counting up is never retried, because it could count twice. See [Automatic retries](https://docs.workflow-suite.app/automatic-retries.md).
