# Data Store

> Store structured tables, ordered lists (with Collect and send digests), and key-value records across runs, and start automations when rows or keys change.

**Data Store** (`data-store`) persists **Tables**, **Lists**, and **Key-value** records across runs inside your workspace. It requires no external database. Seven built-in triggers start runs when rows or keys change, or when a **Collect and send** list releases a batch.

**At a glance**

- **Use it to:** Persist structured Tables, ordered Lists (with Collect and send digests), and Key-value records across runs
- **Find it:** Dock → Apps → Data Store (or Tools → Data)
- **Cost:** 1 Step credit per action · Free for Data Store triggers
- **Limits:** Up to 1,000 rows returned per Find rows / Get all rows call · 100,000 records workspace quota
- **Output:** {{ N.row.col }}, {{ N.results }}, {{ N.existingKeyFound }}, or {{ N.value }} (no ret wrapper)
- **Instead:** Custom Variables (when data is only needed inside a single run)

Use [Custom Variables](https://docs.getglow.ai/build/action-steps/custom-variables) for single-run values. Use [Secrets and Variables](https://docs.getglow.ai/manage/workspace-settings/secrets-and-variables) for static workspace config or credentials. Use **Data Store** for live runtime data shared across runs and teammates.

## [Choose a Data Store type](https://docs.getglow.ai/build/action-steps/data-store#three-data-store-types)

**Tables and Lists** (Structured grids & digests)

- Tables store up to 100 typed columns (Text, Number, Date, Yes/no) with CSV import and live grid editing.
- Lists collect ordered items and release scheduled or threshold batches via Collect and send.
- Best for customers, leads, inventory, and daily or hourly digest queues.

**Key-Value Records** (Fast lookup & TTL expiry)

- Stores one JSON value (up to 1 MB) per key (up to 1,024 chars), with optional TTL up to 1 year.
- Create under More types in Create data store, or auto-create on a Key-value Step's first write.
- Best for webhook deduplication, sync cursors, and idempotency locks.

Open **Data stores** in the sidebar to filter by **All**, **Tables**, **Lists**, or **Key-value** and check your quota (`0 / 100,000 rows and records`). Each row shows its type badge, name, update time, usage, count, size, and TTL.

---

## Tables, Lists, Key-Value Records, and Triggers

**1. Tables and Lists (6 Actions)**

| Action                     | Key                            | What it does                                                                             |
| :------------------------- | :----------------------------- | :--------------------------------------------------------------------------------------- |
| **Add a row**              | `data-store/add-row`           | Insert or replace a row (empty `Text` keys get a UUID); returns `{ key, row, created }`. |
| **Add to collection**      | `data-store/add-to-collection` | Append `item` to a **Collect and send** list; returns `{ key, item }`.                   |
| **Find rows**              | `data-store/find-rows`         | Return up to `1,000` matching rows (oldest first) as `{ results, resultCount }`.         |
| **Get all rows**           | `data-store/get-all-rows`      | Return up to `1,000` live rows (oldest first) as `{ results, resultCount }`.             |
| **Update fields of a row** | `data-store/update-row-fields` | Atomically update filled columns on `key`; returns `{ updated, key, row }`.              |
| **Delete a row**           | `data-store/delete-row`        | Delete the row matching `key`; returns `{ deleted, key, row }`.                          |

- **Over 1,000 rows.** `Get all rows` and `Find rows` return up to `1,000` rows (oldest first). Filter larger tables by a status column (`processed is equal to false`) and mark or delete each row after processing.
- **Live collaboration.** Teammates appear with colored cell outlines and draft previews. **Yes/no** cells distinguish `Yes`, `No`, and unset states. Import CSVs up to **20 MB** (`100,000` rows) or paste up to **1,000 rows**.
- **Collect and send.** Enable **Collect and send** on a **List** to release batches (`1` to `1,000`, oldest first) **Every hour**, **Every day**, **Every week**, at a row threshold, or via **Send now**.

**2. Key-Value Records (14 Actions)**

| Action                             | Key                                      | What it does                                                                               |
| :--------------------------------- | :--------------------------------------- | :----------------------------------------------------------------------------------------- |
| **Add or update a record**         | `data-store/add-update-record`           | Save a value under `key` (optional `ttl`); returns `{ key, value, ttl }`.                  |
| **Add or update multiple records** | `data-store/add-update-multiple-records` | Batch-write records from an object, `{ key, value }` list, or `keyField` list.             |
| **Update fields of a record**      | `data-store/update-record-fields`        | Atomically patch top-level `fields` under `key`; returns `{ key, value, expiresAt, ttl }`. |
| **Get a record (or create it)**    | `data-store/get-record-or-create`        | Read or create `key`; returns `{ key, found, created, value, expiresAt, ttl }`.            |
| **Check whether a key exists**     | `data-store/has-key-or-create`           | Return `{ existingKeyFound, newKeyCreated }`, optionally creating `key`.                   |
| **Append to a record**             | `data-store/append-to-record`            | Atomically append an item (or list) to the list under `key`.                               |
| **List records**                   | `data-store/list-records`                | Return up to `1,000` records for selected `keys` (or all keys) as an object or list.       |
| **Get all records**                | `data-store/get-all-records`             | Return up to `1,000` live records as `{ [key]: value }` or an entry list.                  |
| **List keys**                      | `data-store/list-keys`                   | Return live keys in alphabetical or natural numeric order.                                 |
| **Get record keys**                | `data-store/get-record-keys`             | Search leaf values matching `query` (exact or `contains`, optional `caseInsensitive`).     |
| **Get difference**                 | `data-store/get-difference`              | Return up to `1,000` records in `dataStoreA` absent from `dataStoreB`.                     |
| **Update record expiration**       | `data-store/update-ttl`                  | Set or clear (`0`) the expiration (`ttl`) on an existing key.                              |
| **Delete a record**                | `data-store/delete-single-record`        | Delete one key; returns `{ deleted, key, value, expiresAt, ttl }`.                         |
| **Delete all records**             | `data-store/delete-all-records`          | Clear all records while keeping the empty store registered.                                |

**3. Data Store Triggers (7 Triggers)**

| Trigger                      | Key                                 | Fires when & output (`{{ 1.... }}`)                                                                                 |
| :--------------------------- | :---------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
| **Collection ready to send** | `data-store/collection-ready`       | Releases up to `1,000` waiting rows (oldest first). Outputs `{ items, count, from, to, text }`.                     |
| **Row added**                | `data-store/row-added`              | Row added to a Table or List (optional filter). Outputs `{ row, key, store, createdAt, chainDepth }`.               |
| **Row updated**              | `data-store/row-updated`            | Row changes or matches a filter. Outputs `{ row, previousRow, changedColumns, key, store, updatedAt, chainDepth }`. |
| **Row deleted**              | `data-store/row-deleted`            | Row removed (`"manual"`, `"workflow"`, `"expired"`). Outputs `{ row, key, store, reason }`.                         |
| **Row expired**              | `data-store/row-expired`            | Row TTL elapses (swept every 5m). Outputs `{ row, key, store, expiredAt, chainDepth }`.                             |
| **Key added or changed**     | `data-store/key-added-or-changed`   | Key added or updated. Outputs `{ key, value, previousValue, created, store, updatedAt, chainDepth }`.               |
| **Key deleted or expired**   | `data-store/key-deleted-or-expired` | Key deleted or expired. Outputs `{ key, value, store, reason, chainDepth }`.                                        |

- **Pacing.** Change triggers poll every **1 minute** (**5 minutes** on Free) and start up to **100 runs** for changes from the last **24 hours**. Bulk deletions, `Delete all records`, and column edits do not fire row triggers.
- **Loop protection.** **Ignore changes made by this workflow** (`ignoreSelfWorkflow`) defaults to `true`, and trigger chains stop at depth **5** (`chainDepth < 5`). A **Collection ready to send** run cannot call **Add to collection** on that same list.
- **Sample events.** Click **Fetch sample event** in **Test & Debug** to pull a read-only sample from live rows.

---

## [Setting it up & what it passes on](https://docs.getglow.ai/build/action-steps/data-store#setting-it-up)

### Pick an operation

Open **Apps → Data Store** in the dock and select a Table, List, or Key-value action.

### Choose or name the store

Select an existing **Data store** or type a name (`1` to `200` chars). Click **Open data store** to view it.

### Map columns or keys

Map row columns or `Key` / `Value` inputs. Outputs sit at the root without `ret` (`{{ 2.row.email }}`, `{{ 2.results }}`, `{{ 2.existingKeyFound }}`, `{{ 2.value }}`).

Whole-store actions (`Find rows`, `Get all rows`, `List keys`, `List records`, `Get all records`, `Get difference`, `Get record keys`, `Delete all records`, `Add or update multiple records`) run once on the store. They only loop after [Run for each item](https://docs.getglow.ai/build/action-steps/loops/run-for-each-item) when referencing `{{ item }}`.

> **Do not store credentials in a Data Store**. Workspace secrets and API keys
> (`{{ $secret.* }}` and `{{ $key.* }}`) fail with `SECRET_IN_UNSAFE_SINK`. Keep
> credentials in [Secrets and
> Variables](https://docs.getglow.ai/manage/workspace-settings/secrets-and-variables).

## [Limits](https://docs.getglow.ai/build/action-steps/data-store#limits)

| Resource                        | Limit                                                                                                 |
| :------------------------------ | :---------------------------------------------------------------------------------------------------- |
| **Store name / Record key**     | Store name: `1` to `200` chars · Record key: `1` to `1,024` chars                                     |
| **Maximum value size**          | **1 MB** (`1,048,576` bytes) of JSON per record                                                       |
| **Workspace live capacity**     | **100,000** non-expired rows and records across all Data Stores                                       |
| **Table columns / CSV import**  | Up to **100** typed columns · CSV import up to **20 MB** (`100,000` rows) · Paste `1,000`             |
| **Maximum records / rows read** | **1,000** per call (`Find rows`, `Get all rows`, `Get all records`, `List records`, `Get difference`) |
| **Time to live (`ttl`) range**  | `1` to `31,536,000` seconds (up to **1 year**); `0` or empty keeps records                            |

## [Examples to copy](https://docs.getglow.ai/build/action-steps/data-store#examples-to-copy)

  

**Deduplicate webhooks**

    1. Add **Data Store → Check whether a key exists** at Step 2 (`Data store`: `processed_webhooks`, `Key`: `{{ 1.body.eventId }}`). Enable **Create a new record if the key is not found** (`Value`: `{{ $now }}`, `Time to live`: `604800`).
    2. Add [Conditions](https://docs.getglow.ai/build/action-steps/conditions) at Step 3 to continue only when `{{ 2.existingKeyFound }}` is `false`.
  

  

**Keep a sync cursor**

    1. After a [Scheduler](https://docs.getglow.ai/build/triggers/scheduler) trigger, run **Data Store → Get a record (or create it)** (`dataStore`: `crm_sync`, `key`: `last_run_at`, default `2026-01-01T00:00:00Z`).
    2. Fetch records updated after `{{ 2.value }}`. Finish with **Data Store → Add or update a record** setting `last_run_at` to `{{ $now }}`.
  

  

**Daily digest with Collect and send**

    1. Create a **List** named `daily_digest` with **Collect and send** enabled (**Every day** at `18:00` or `25` rows).
    2. In your intake automation, run **Data Store → Add to collection** (`Collection`: `daily_digest`, `Item`: `{{ 1.body.summary }}`).
    3. In a second automation, start with **Data Store → Collection ready to send** (`Collection`: `daily_digest`) and send `{{ 1.text }}` to Slack.
  

**Verify it works**

- **Run:** Add Data Store → Add or update a record, enter a store name and key, and click Run step
- **You should see:** The Output tab returns the key and value, and the record appears on the Data stores page.

## What's Next?

- Reshape records in a single run with [Change Fields](https://docs.getglow.ai/build/action-steps/change-fields) or [Custom Variables](https://docs.getglow.ai/build/action-steps/custom-variables).
- Store API keys and static config in [Secrets and Variables](https://docs.getglow.ai/manage/workspace-settings/secrets-and-variables).
