---
title: "Storage for your functions - Workflow Functions"
description: "Keep data between runs with ctx.storage: get, set, delete and list, limits per plan, the Storage page and files."
canonical: "https://docs.workflow-functions.app/storage"
---

# Storage for your functions

Every run starts in a fresh sandbox, so a variable does not survive until the next run. `ctx.storage` is where a function keeps what it needs next time: the position of the last sync, events it already handled, a snapshot to compare against, a cached answer from a slow API.

## The four calls

```js
export default async function (input, ctx) {
  const last = await ctx.storage.get("sync/orders")              // the value, or null
  // ... do the work ...
  await ctx.storage.set("sync/orders", { at: Date.now(), id: 42 }) // any JSON value
  await ctx.storage.delete("old/key")                             // true if it existed
  const { keys, cursor } = await ctx.storage.list({ prefix: "sync/", limit: 100 })
  return { last }
}
```

| Call | Returns |
| --- | --- |
| `get(key)` | The stored value, or `null` |
| `set(key, value)` | Nothing. Overwrites. `value` is anything JSON can hold |
| `delete(key)` | `true` if the key existed |
| `list({ prefix, limit, cursor })` | `{ keys: [{ key, size, updatedAt }], cursor }` - names and sizes, no values. Pass `cursor` back for the next page; `null` means done |

Keys are strings up to 256 characters; slashes are handy for grouping (`dedupe/`, `sync/`). Storage is shared by **all functions of your store**, so one function can write what another reads.

## What it is and is not

- **Not a cache:** nothing expires on its own. To expire a key, store a timestamp and delete old keys, as the *Skip duplicate workflow runs* template does.
- **Not a database:** there are no transactions and no atomic counters; the last write wins. Two runs at the very same moment can both read the old value.
- **About 60 to 100 ms per read or write** (a miss is faster). Run independent calls in parallel with `Promise.all`.
- **Up to 100 storage calls per run.**

## Limits per plan

| Plan | Total size | Keys | One value |
| --- | --- | --- | --- |
| Free | 5 MB | 500 | 64 KB |
| Grow | 250 MB | 25,000 | 256 KB |
| Unlimited | 1 GB | 100,000 | 512 KB |

A call over a limit throws an error that starts with `ctx.storage:`. Updating or deleting an existing key always works, so a store over its key count after a downgrade can still clean up.

## The Storage page

**Storage** in the app shows how much you use, lets you browse keys by prefix, view a value and delete a key. Values are written only by your code, inside a run.

![The Storage page with key-value usage, the key browser with one dedupe key, and the Files section](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/8bfeb36668ddf7b3.png)
*The Storage page: usage against your plan, the key browser and your files.*

![The value of a stored key shown in a dialog on the Storage page](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/2081af437411d022.png)
*View shows the value a function stored under a key.*

Over the API, `GET /api/v1/storage` returns the usage and keys, and `GET` or `DELETE /api/v1/storage/value?key=` reads or removes one value. See [Developer API and MCP](https://docs.workflow-functions.app/developer-api-and-mcp.md).

## Templates that use storage

- **Skip duplicate workflow runs** - put it first in a workflow; it remembers an id and tells the next step whether this event was already handled.
- **Limit how often something happens** - at most N times per window per key.
- **Remember a slow API answer** - reuse an answer for a few minutes.
- **Send only stock changes to your ERP** and **Save a daily orders report as CSV** - keep a snapshot or a report for another function or your ERP to pick up.

## Files

The Files section holds whole files - an import from your ERP, an export waiting to be picked up. Upload them on the Storage page or with `PUT /api/v1/files/:key`, and read or delete them the same way. Reading files from function code comes with a later runtime update; until then, code keeps text in `ctx.storage`. File storage per plan: 100 MB on Free (5 MB per file), 5 GB on Grow (25 MB per file), 50 GB on Unlimited (50 MB per file).

## Your data

Values are stored encrypted in the EU (Google Cloud, Belgium) and are deleted with the rest of your data when you uninstall the app and Shopify asks us to erase it. **Do not keep customers' personal data in storage** unless you have your own way to delete it on request: a customer's data request cannot find it inside keys you named.

## Next steps

- [How to create a function](https://docs.workflow-functions.app/how-to-create-a-function.md) - the rest of `ctx`.
- [Run a function on a schedule](https://docs.workflow-functions.app/scheduled-functions.md) - schedules that remember where they stopped.
- [Connect your ERP or warehouse system](https://docs.workflow-functions.app/erp-integration.md) - syncs that need a position.
