Solaris AISolaris AI FlowDocs
Node ReferenceUtility

Storage

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

View as Markdown

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

  • No API key required

Operations

OperationPurposeRequired fields
GetRead the value at a keykey
SetWrite a value (creating or overwriting)key, value (any JSON)
HasCheck whether a key exists, or whether a list contains a valuekey (membership value optional)
Append UniqueAppend to an array, no-op if already presentkey, value (must be a string, number, or boolean, not an array or object)
Remove From ListRemove a value from an arraykey, value (string, number, or boolean)
DeleteDelete the entire keykey
Host File → URLHost base64 / data-URL image bytes as a fetchable URLvalue (base64 or data: URL), no key

Configuration

FieldTypeRequiredDescription
Node LabelstringNoDisplay name shown on the canvas
OperationenumYesOne of the operations above
KeystringMost opsIdentifier for the stored value (max 128 chars). Allowed characters: letters, digits, _ - : . Not used by Host File → URL, which has no key.
ValuestringVariesRequired for Set, Append Unique, Remove From List, and Host File → URL (where it is the base64 / data: URL to host).
Value Shapescalar | jsonNoControls 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 ValuestringNoOnly for Has. When set, checks whether the array at the key contains this value
TTL SecondsnumberNoOnly for Set and Append Unique. Overrides the default expiry. Must be a positive number, capped by your plan max (see Plan limits below)

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

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

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.

Plan limits

Storage is available on every plan. Caps differ by tier and are enforced server-side:

CapFree (beta)Pro / Ultra
Keys per workflow550
Array entries per key2501,000
Value size5 KB10 KB
Default TTL48 hours48 hours
Max TTL48 hours (no permanent storage)7 days

Free-tier values always expire. There is no permanent storage on the free plan. An hourly background sweep deletes expired entries.

Templating against the response

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

OperationOutput 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

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

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

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

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

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

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

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 createToken imageUrl or 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), so reach for this op when the bytes come from somewhere else, like an image API called through an HTTP node that returns base64.

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

  • 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

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 (free default; lower it explicitly if you want a shorter dedup window).

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

Next steps

  • Condition: branch on {storageResponse.exists} or {storageResponse.found}
  • ForEach: iterate over a candidate list and dedup per item
  • Code: shape the value before storing or compute against the retrieved list

On this page