Data Stores
Data stores provide managed key-value storage for your workflows. Use them to persist data across executions, maintain state between steps, and build lookup tables — all scoped to your organization and accessible from any workflow.
How Data Stores Work
Section titled “How Data Stores Work”Data is organized into tables, each containing key-value entries. Tables are shared across all workflows in your organization, so any workflow can read or write to any table.
| Concept | Description |
|---|---|
| Table | A named collection of key-value entries (like a database table) |
| Key | A unique identifier within a table (up to 1,024 characters) |
| Value | Any JSON-serializable data — strings, numbers, booleans, objects, or arrays |
Creating a Table
Section titled “Creating a Table”Tables are created automatically when you first write to them using a data store step. You can also create tables from the Data Store page in the sidebar, or from the data store step configuration — the table name dropdown lets you select existing tables or type a new name to create one.
Data Store Steps
Section titled “Data Store Steps”QuickFlo provides five step types for working with data stores. Each step is configured through the visual form builder in the workflow editor.
Retrieve a single value by key.
| Field | Description |
|---|---|
| Table | Select or type a table name |
| Key | The key to look up (supports templates, e.g., user-{{ initial.userId }}) |
Output available to later steps:
{{ get-user.value }} // the stored value (or null if not found){{ get-user.found }} // true or false{{ get-user.value.email }} // access nested fields from stored objectsStore or update a value. If the key already exists, its value is replaced.
| Field | Description |
|---|---|
| Table | Select or type a table name |
| Key | The key to store under (supports templates) |
| Value | Key-value pairs defining the data to store |
| Expiration | Optional TTL — the entry is auto-deleted after this time |
Expiration Settings
Section titled “Expiration Settings”| Option | Description |
|---|---|
| No expiration | Entry persists until manually deleted (default) |
| Custom TTL | Set a duration from 1 minute to 1 year |
Output:
{{ set-user.success }} // true if stored successfully{{ set-user.key }} // the key that was writtenList all keys in a table with optional prefix filtering.
| Field | Description |
|---|---|
| Table | The table to list keys from |
| Prefix | Optional — only return keys containing this text |
| Limit | Max results to return (1–1,000, default 100) |
| Offset | Skip this many results (for pagination) |
Output:
{{ list-keys.keys }} // array of key strings{{ list-keys.total }} // total matching keys{{ list-keys.hasMore }} // true if more results existSearch entries by their stored values using field-level filters — like a simple database query.
| Field | Description |
|---|---|
| Table | The table to query |
| Filters | One or more conditions on value fields |
| Filter Mode | All (every filter must match) or Any (at least one must match) |
| Limit | Max results (1–1,000, default 100) |
| Offset | Skip results for pagination |
Each filter has three parts:
| Part | Description | Example |
|---|---|---|
| Field | Dot-notation path into the stored value | status, user.email, tags |
| Operator | Comparison type | equals, contains, greater than, etc. |
| Value | The value to compare against | active, 100, true |
Available operators:
| Operator | Description |
|---|---|
equals | Exact match |
not_equals | Not equal |
contains | Text contains substring |
array_contains | Array includes a value |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
Output:
{{ query-users.entries }} // array of { key, value } objects{{ query-users.entries[0].key }} // first result's key{{ query-users.entries[0].value }} // first result's value{{ query-users.total }} // total matches{{ query-users.hasMore }} // true if more results existDelete
Section titled “Delete”Remove a single entry by key.
| Field | Description |
|---|---|
| Table | The table to delete from |
| Key | The key to delete (supports templates) |
Output:
{{ delete-user.deleted }} // true if the key existed and was deleted{{ delete-user.key }} // the key that was deletedBrowsing Data in the UI
Section titled “Browsing Data in the UI”The Data Store page lets you browse, search, edit, and manage your data store entries directly from the QuickFlo UI.
Tables Sidebar
Section titled “Tables Sidebar”The left sidebar lists all tables in your organization with record counts. Use the search field at the top to filter tables by name. You can also create new tables or refresh the list from the toolbar icons.
Records Table
Section titled “Records Table”Selecting a table shows all its entries in a sortable table with Key, Created, and Updated columns. Each row has edit and delete action buttons. Click any row to open the detail panel.
Detail Panel
Section titled “Detail Panel”
Clicking a record opens a detail panel showing:
- Metadata — size, created date, updated date, and expiry countdown (if set)
- Value preview — the full stored value with syntax highlighting and expandable tree view
- Clickable values — click any primitive value in the tree to quickly filter by it
From the detail panel you can edit, delete, or copy the key.
Searching and Filtering
Section titled “Searching and Filtering”The search bar supports two modes:
Key search — type any text to filter entries by key (case-insensitive contains match):
user-123Value search — use field:value syntax to filter by stored values. Click the Search button or press Enter to execute:
| Syntax | Description | Example |
|---|---|---|
field:value | Field contains value (partial match) | status:active |
field:=value | Field equals value (exact match) | email:=john@example.com |
field:!value | Field does not contain value | status:!deleted |
field:!=value | Field does not equal value | status:!=inactive |
[].field:value | Array element field contains value | [].level:123 |
field[]:value | Array field contains primitive value | tags[]:important |
Active filters appear as badges below the search bar that you can dismiss individually.
Editing Records
Section titled “Editing Records”
Click the edit icon on any record (or from the detail panel) to open the edit dialog. The editor supports two modes:
- Properties mode — visual path/value pairs with an “Add Property” button
- JSON mode — raw JSON editor for complex values
You can also toggle Set expiration to add or remove a TTL on the entry.
Import and Export
Section titled “Import and Export”
Export
Section titled “Export”Export an entire table as a JSON file from the table toolbar. The export contains an array of key-value objects.
Import
Section titled “Import”
Import data from JSON or CSV files into a table. The import dialog supports drag-and-drop file upload and shows a preview of entries before importing. Existing keys are updated (upsert) and new keys are created.
Common Patterns
Section titled “Common Patterns”Storing Workflow State
Section titled “Storing Workflow State”Persist data across workflow executions — for example, tracking which records have been processed to prevent duplicates:
| Step | Configuration |
|---|---|
| Get | Table: processed-records, Key: {{ $item.id }} |
| If | Condition: {{ get-record.found }} equals false |
| Set (in Then branch) | Table: processed-records, Key: {{ $item.id }}, Value: {{ $util.now }} |
Building Lookup Tables
Section titled “Building Lookup Tables”Store reference data that multiple workflows can use:
| Step | Configuration |
|---|---|
| Set | Table: config, Key: region-mapping, Value: JSON with region mappings |
| Get (in another workflow) | Table: config, Key: region-mapping |
Access nested values: {{ get-config.value.US }}
Counters and Accumulators
Section titled “Counters and Accumulators”Increment a counter across workflow executions:
| Step | Configuration |
|---|---|
| Get | Table: metrics, Key: daily-count |
| Set | Table: metrics, Key: daily-count, Value: {{ get-count.value | default: 0 | plus: 1 }} |
Caching Expensive Operations
Section titled “Caching Expensive Operations”Use a data store with a TTL to avoid repeating expensive API calls across executions:
- Get step: Look up the cache key
- If step: Check
{{ get-cache.found }}- Then branch: Use the cached value
- Else branch: Call the API, then Set the result with a TTL (e.g., 1 hour)
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Key length | 1,024 characters |
| Table name length | 255 characters |
| Minimum TTL | 1 minute |
| Maximum TTL | No expiration (entries persist indefinitely) |
| Default TTL (when expiration enabled) | 30 days |
| Query limit per request | 1,000 entries |
| Bulk import per request | 10,000 entries |