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

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
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
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.

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