# Storage (/docs/nodes/utility/storage)

Persistent key/value and list storage scoped to a workflow, for state that survives across runs.



The Storage node lets a workflow remember values between runs. State is scoped to a single workflow, so when a Cron Trigger fires every 5 minutes, each run sees the same persisted store.

The most common use is dedup. A sniper checks whether a mint has already been processed, acts only on new ones, and appends each handled mint to a list. The list survives across cron ticks without needing an external database.

Storage or Dataset? [#storage-or-dataset]

Storage is a small key/value cache scoped to one workflow, with mandatory short TTLs. It is built for dedup and per-workflow state, not for accumulating history.

If you want to capture outputs across many workflows, keep them long enough to score over time, or query and aggregate them (counts, sums, averages), use the [Dataset](/docs/nodes/utility/datasets) node instead. Datasets are account-scoped, long-retention, queryable tables, and any node can passively capture its output into one. A good rule of thumb: reach for Storage when you are asking "have I seen this before?", and for Datasets when you are asking "how did this perform?".

Prerequisites [#prerequisites]

* No API key required

Operations [#operations]

| Operation        | Purpose                                                        | Required fields                                                           |
| ---------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Get              | Read the value at a key                                        | key                                                                       |
| Set              | Write a value (creating or overwriting)                        | key, value (any JSON)                                                     |
| Has              | Check whether a key exists, or whether a list contains a value | key (membership value optional)                                           |
| Append Unique    | Append to an array, no-op if already present                   | key, value (must be a string, number, or boolean, not an array or object) |
| Remove From List | Remove a value from an array                                   | key, value (string, number, or boolean)                                   |
| Delete           | Delete the entire key                                          | key                                                                       |
| Host File → URL  | Host base64 / data-URL image bytes as a fetchable URL          | value (base64 or `data:` URL), no key                                     |

Configuration [#configuration]

| Field                 | Type               | Required | Description                                                                                                                                                                                                                                                                                                                                                 |
| --------------------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Node Label            | string             | No       | Display name shown on the canvas                                                                                                                                                                                                                                                                                                                            |
| Operation             | enum               | Yes      | One of the operations above                                                                                                                                                                                                                                                                                                                                 |
| Key                   | string             | Most ops | Identifier for the stored value (max 128 chars). Allowed characters: letters, digits, `_ - : .` Not used by Host File → URL, which has no key.                                                                                                                                                                                                              |
| Value                 | string             | Varies   | Required for Set, Append Unique, Remove From List, and Host File → URL (where it is the base64 / `data:` URL to host).                                                                                                                                                                                                                                      |
| Value Shape           | `scalar` \| `json` | No       | Controls how `Value` and `List Membership Value` are decoded. `scalar` (default) keeps the literal string. `json` parses the input. With `json`, Set stores any JSON value (arrays, numbers, booleans, objects), but Append Unique, Remove From List, and Has-with-membership only accept primitives (string, number, boolean) and reject arrays or objects |
| List Membership Value | string             | No       | Only for Has. When set, checks whether the array at the key contains this value                                                                                                                                                                                                                                                                             |
| TTL Seconds           | number             | No       | Only for Set and Append Unique. Overrides the default expiry. Must be a positive number, capped by the max TTL (see Limits below)                                                                                                                                                                                                                           |

`Key`, `Value`, and `List Membership Value` all accept template expressions. For example:

```text
seenMints
{webhook.payload.mint}
{token.address}
```

Keep Value Shape consistent across reads and writes [#keep-value-shape-consistent-across-reads-and-writes]

If you store a value with `Value Shape: json` and the input is a number like `42`, it is persisted as the number `42`. A later Has check with `Value Shape: scalar` and the same `42` compares the **string** `"42"` against the **number** `42`. The check returns `false`, and your dedup silently misses.

Rule of thumb: pick one shape (most workflows want `scalar`, since mint addresses and IDs are strings) and use the same shape for every Storage node touching that key. Switch to `json` only when you actually need numbers, booleans, or (for Set only) arrays and objects.

Limits [#limits]

Storage is available to everyone. Caps are enforced server-side and apply to every account (universal, while storage moves to credit metering):

| Cap                   | Limit    |
| --------------------- | -------- |
| Keys per workflow     | 25       |
| Array entries per key | 500      |
| Value size            | 10 KB    |
| Default TTL           | 48 hours |
| Max TTL               | 4 days   |

All values expire; there is no permanent storage yet. An hourly background sweep deletes expired entries.

Templating against the response [#templating-against-the-response]

Each Storage operation writes its result to `storageResponse` (override via the Response Name field in the config dialog):

| Operation        | Output shape                                                |
| ---------------- | ----------------------------------------------------------- |
| Get              | `{ found: boolean, value: any, expiresAt: number \| null }` |
| Set              | `{ ok: true, expiresAt: number }`                           |
| Has              | `{ exists: boolean }`                                       |
| Append Unique    | `{ added: boolean, count: number, expiresAt: number }`      |
| Remove From List | `{ removed: boolean, count: number }`                       |
| Delete           | `{ deleted: boolean }`                                      |
| Host File → URL  | `{ url: string, contentType: string, sizeBytes: number }`   |

All outputs include `success: true` and `operation`. Every operation except Host File → URL (which has no key) also includes `key`.

Operation details [#operation-details]

Get [#get]

Reads the value at `key`. Returns `found: false` when the key is missing or expired. Expiration is lazy: the row is treated as absent on read regardless of whether the cron has swept it yet.

Set [#set]

Writes `value` at `key`, creating the row or overwriting it. The `expiresAt` resets on every write. If `Value Shape` is `json`, the value is parsed before storage so `[1,2,3]` becomes a real array, not the literal string.

Has [#has]

Two modes:

* Empty `List Membership Value`: checks whether the key exists (and is not expired).
* Non-empty `List Membership Value`: checks whether the array at the key contains the value. If the stored value is not an array, falls back to scalar equality.

The List Membership Value's shape is also controlled by `Value Shape`. Pick `json` if the list contains numbers or booleans you want to match exactly. Whitespace-only resolved values (for example, a template that resolved to `""`) are treated as the empty case, so Has becomes a key-presence check.

Append Unique [#append-unique]

Appends a primitive (string, number, or boolean) to the array at `key`. Arrays and objects are rejected at runtime. If you need those, use Set with `Value Shape: json`.

On a real append, the entry's TTL is reset to `now + ttlSeconds` (your override, or the 48h default), so a sliding dedup window stays alive as long as new items keep being appended.

If the value is already present, returns `added: false` and does not refresh the TTL. This prevents repeated Append Unique calls of the same value from keeping an entry alive past its dedup window.

If the key is missing or expired, a new array is created. If the key holds a non-array value (for example, you previously called Set with a string), the operation throws.

Remove From List [#remove-from-list]

Removes a value from the array at `key`. No-op when the key is missing, expired, or the value is not present. Throws if the stored value is not an array. The TTL is **not** refreshed.

Delete [#delete]

Deletes the entire row. No-op when the key is missing. Unlike Get and Has, which treat expired rows as absent, Delete operates on any row that still exists. This is useful for reclaiming a key slot before the hourly sweep runs.

Host File → URL [#host-file--url]

Turns raw image bytes into a fetchable URL so image fields on other nodes can point at them. Unlike every other operation, it has **no key** and never touches the key/value store: it takes a base64 string or a `data:` URL in the `Value` field, hosts the bytes, and returns a signed URL as `{storageResponse.url}`.

* **Type is detected from the bytes.** PNG, JPEG, WebP, GIF, and SVG are recognized from the content itself, so a missing or wrong `data:` MIME label doesn't matter. Anything that isn't a supported image is rejected.
* **Max 16 MB** after decoding. `Value Shape` and `TTL Seconds` don't apply (the value is raw bytes, not a stored entry).
* **The URL is temporary**: a signed link that lives about an hour, and the file is reaped within a day. Consume it in the same run, for example feed `{storageResponse.url}` straight into [Pump.fun](/docs/nodes/defi/pump-fun) `createToken` `imageUrl` or [Telegram](/docs/nodes/messaging/telegram) `sendPhoto` `photoUrl`. Don't stash it for later.
* **It does not count against your Storage caps.** Keys-per-workflow, value size, and array limits below apply only to the key/value store, not to hosted files.

When your image comes from an AI node, that node already returns a hosted `imageUrl` directly (see [AI > Image generation](/docs/nodes/ai#image-generation)), so reach for this op when the bytes come from somewhere else, like an image API called through an [HTTP](/docs/nodes/utility/http) node that returns base64.

Marketplace and ownership [#marketplace-and-ownership]

Storage is scoped to `(owner, workflowId)`. Marketplace clones receive a fresh, empty store, never the original author's data, so a buyer who imports a sniper template starts with zero "seen" mints. Storage entries are never shared between two workflows even if owned by the same user.

Common use cases [#common-use-cases]

* Sniper or trading dedup: track mints you have already evaluated so a 1-minute cron does not re-process the same token forever
* "Already notified" lists for alerts and webhooks
* Per-workflow counters or recently-processed IDs

Example: dedup before buying a token [#example-dedup-before-buying-a-token]

```text
Cron Trigger -> Pump.fun (Get New) -> ForEach -> Storage (Has) -> Condition -> Pump.fun (Buy) -> Storage (Append Unique) -> ForEach End
```

Inside the loop body:

1. **Storage (Has)**: `key: seenMints`, `List Membership Value: {token.mint}`. Output: `{exists: true | false}`.
2. **Condition**: branch on `{storageResponse.exists}`. Continue only when `false`.
3. **Pump.fun (Buy)**: execute the trade.
4. **Storage (Append Unique)**: `key: seenMints`, `value: {token.mint}`, optional `TTL Seconds: 172800`. The mint is now ignored on the next 48 hours of cron ticks (the default TTL; lower it explicitly if you want a shorter dedup window).

For the inverse, marking a mint as eligible again after a sale, wire a &#x2A;*Storage (Remove From List)** node into your sell branch with the same `key` and `value`.

Next steps [#next-steps]

* [Condition](/docs/nodes/utility/condition): branch on `{storageResponse.exists}` or `{storageResponse.found}`
* [ForEach](/docs/nodes/utility/foreach): iterate over a candidate list and dedup per item
* [Code](/docs/nodes/utility/code): shape the value before storing or compute against the retrieved list
