Blocks

Get from store

Fetch one row by key, or list a batch of rows still missing a field as a work queue. The found flag, the data envelope, and the drain-the-queue pattern.

Read from a workspace data store, the durable tables your workflows share. Two modes:

  • by_key: fetch one row by its key.
  • list: fetch a batch of rows still missing a field: the work-queue read.

What it does

In by_key mode the step renders the key template against the upstream payload and looks the row up. A hit returns the row's data; a miss returns found: false and the run keeps going. That is deliberate: "have we seen this before?" is a question, not a failure, and the first time a key shows up is usually the interesting case.

In list mode the step asks the store for rows whose data has no value for one field (the key is absent, or present with null), sorted by key ascending and cut off at the batch size. Because the sort is stable and the filter shrinks as rows get filled in, each run of the workflow picks up the next slice of unfinished work without any cursor to manage.

Either way the store is resolved through the run's workspace. A store id or slug from another workspace resolves to not found.

Config

The get-from-store step in the Inspector Store, mode, and the key (or the missing-field filter).

  • Store: pick the store (created in Settings → data). The AI builder writes the store's slug here; both work.
  • Mode: by_key (default) or list. The fields below change with it.
  • Row key (by_key): template-rendered, {{ payload.id }}. A key that renders empty fails the step with a hint to check the reference.
  • Missing field (list): a literal field name, not a template; returns only rows whose data lacks it. It can't be a field the schema marks sensitive, because encrypted values are never empty in the database.
  • Batch size (list): defaults to 50, cap 500, keyed ascending so batches are deterministic.

Output

// by_key, hit:
{ "found": true, "key": "41051",
  "data": { "fee_cents": 7600, "source_url": "https://…" },
  "updated_at": "2026-08-08T12:00:00Z" }

// by_key, miss (NOT an error):
{ "found": false, "key": "41051", "data": null, "updated_at": null }

// list:
{ "rows": [ { "key": "41051", "data": { "fee_cents": 7600 } },
            { "key": "41053", "data": { "fee_cents": 8100 } } ],
  "count": 2 }

After by_key, row fields live under data: {{ payload.data.fee_cents }}. After list, loop over payload["rows"] and read {{ payload.item.key }} and {{ payload.item.data.fee_cents }} inside the body.

Patterns

  • Lookup + branch: by_key → if on payload["found"], for cache-style "have we seen this?" checks.
  • Work the queue: list with a missing field → loop over payload["rows"] → do the work → save to store keyed {{ payload.item.key }}. Each run drains one batch; count: 0 means done.
  • Drain in one run: wrap the queue pattern in a loop until with the stop condition payload["count"] == 0, so one scheduled run works the whole backlog batch by batch.
  • Config table: a store of per-customer settings, read by key at the top of a run so the rest of the workflow can template on {{ payload.data.channel }}.

Gotchas

  • A missing row returns found: false, never a failure. Branch on it when absence matters; don't rely on the step stopping the run.
  • Row fields live under data: reference {{ payload.data.fee_cents }}, not {{ payload.fee_cents }}.
  • list only sees rows where the field is absent or null. A row where a previous run wrote an empty string is "done" as far as the filter is concerned; write null or omit the field for work that still needs doing.
  • A step that fills the missing field in replace mode must include every other field too, or it wipes the row while completing it. Use save to store in merge mode for queue work.

Related