Build Stateful Workflows with the KV Store
For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append.mdto anykestra.io/docs/*URL for plain Markdown.
The KV Store lets you persist and share key-value data across executions and flows — beyond what task outputs alone can carry.
Kestra’s execution model is stateless by design — task runs are isolated and data moves between tasks via explicit outputs. The KV Store extends this with a namespace-scoped key-value layer for cases where you need to persist state across executions or share values between flows. Values are stored in Kestra’s internal storage (your private cloud bucket); the database holds only metadata such as the key, TTL, and timestamps.
Keys and values
Keys are arbitrary strings containing uppercase or lowercase letters and standard ASCII characters.
Values are stored as ION files in Kestra’s internal storage and are strongly typed. Supported types:
- string
- number
- boolean
- datetime
- date
- duration
- JSON
Each KV pair can have a Time to Live (TTL) to automatically expire data that is only relevant for a limited period.
Namespace binding
KV pairs are scoped to a namespace. Access them under Namespaces → [namespace] → KV Store or under Tenant → KV Store in the sidebar for a tenant-wide view.
You can create and read KV pairs across namespaces as long as those namespaces are allowed.
Managing KV pairs
KV pairs can be managed from the UI, in flows via tasks, through the REST API, via Terraform, or from the command line:
- Kestra UI: create, edit, and delete KV pairs directly (see Namespace binding for navigation paths).
- Tasks in a flow: use
io.kestra.plugin.core.kv.Set,Get, andDeletetasks. - REST API: create, read, and delete KV pairs via the HTTP API.
- Terraform: use the
kestra_kvresource. - Pebble function: use
kv()to retrieve a value inline in a flow expression. - GitHub Actions: manage KV pairs in your CI/CD pipeline.
- kestractl: use
kestractl kvto list, set, get, and delete from the command line. See the kestractl docs for setup.
Create a KV pair from the UI
Open the KV Store tab and click New Key-Value. Enter a key name, select a type (string, number, boolean, datetime, date, duration, or JSON), and enter the value. Optionally set a TTL — choose a standard duration from the dropdown or select Custom duration to enter an ISO 8601 duration string. Click Save.
Update, delete, and copy KV pairs
Edit, delete, or copy any KV pair using the action buttons on the right. The copy option copies the Pebble expression for the KV pair ({{ kv('YOUR_KEY') }}) ready to paste into a flow.
KV tasks in flows
Create a KV pair with the Set task
Use io.kestra.plugin.core.kv.Set to create or update a KV pair from a flow:
id: add_kv_pairnamespace: company.team
tasks: - id: download type: io.kestra.plugin.core.http.Download uri: https://huggingface.co/datasets/kestra/datasets/raw/main/csv/orders.csv
- id: set_kv type: io.kestra.plugin.core.kv.Set key: my_key value: "{{ outputs.download.uri }}" namespace: company.team # the current namespace of the flow is used by default overwrite: true # whether to overwrite or fail if a value for that key already exists; default true ttl: P30D # optional Time to Live (TTL) for the KV pair
- id: set_simple_kv type: io.kestra.plugin.core.kv.Set key: simple_string value: hello from Kestra
- id: set_json_kv type: io.kestra.plugin.core.kv.Set key: json_kv value: | { "author": "Rick Astley", "song": "Never Gonna Give You Up" }
- id: get_kv type: io.kestra.plugin.core.output.OutputValues values: my_key: "{{ kv('my_key') }}" simple_string: "{{ kv('simple_string') }}" favorite_song: "{{ fromJson(kv('json_kv')).song }}"Set overwrite: false to fail instead of silently replacing an existing value. The default is true.
Read KV pairs with Pebble
Use {{ kv('YOUR_KEY') }} to retrieve a value inline. The full signature is:
{{ kv(key='your_key_name', namespace='your_namespace_name', errorOnMissing=false) }}When reading from the same namespace as the flow, the namespace argument is optional:
id: read_kv_pairnamespace: company.teamtasks: - id: log_key type: io.kestra.plugin.core.log.Log message: "{{ kv('my_key') }}"To read from another namespace, pass it as the second argument:
id: read_kv_pair_from_another_namespacenamespace: company.teamtasks: - id: log_key_from_another_namespace type: io.kestra.plugin.core.log.Log message: "{{ kv('my_key', 'kestra.engineering.myproject') }}"By default, referencing a missing key causes the task to fail. Set errorOnMissing=false to return null instead:
id: read_non_existing_kv_pairnamespace: company.teamtasks: - id: log_key_from_another_namespace type: io.kestra.plugin.core.debug.Return format: "{{ kv('non_existing_key', errorOnMissing=false) }}"Read KV pairs with the Get task
The Get task produces a value output you can reference in downstream tasks — useful when you need to pass the same KV value to multiple steps:
id: get_kv_pairnamespace: company.team
tasks: - id: get type: io.kestra.plugin.core.kv.Get key: my_key namespace: company.team errorOnMissing: false
- id: log_key_get type: io.kestra.plugin.core.log.Log message: "{{ outputs.get.value }}"Read and parse JSON-type values from KV pairs
To parse JSON values in Kestra’s templated expressions, wrap the kv() call in the fromJson() function: "{{ fromJson(kv('your_json_key')).json_property }}".
This example sets a JSON KV pair and reads individual fields using fromJson():
id: kv_json_flownamespace: company.team
tasks: - id: set_json_kv type: io.kestra.plugin.core.kv.Set key: favorite_song value: | { "author": "Rick Astley", "song": "Never Gonna Give You Up", "album": { "name": "Whenever You Need Somebody", "release_date": "1987-11-16" } }
- id: parse_json_kv type: io.kestra.plugin.core.log.Log message: - "Author: {{ fromJson(kv('favorite_song')).author }}" - "Song: {{ fromJson(kv('favorite_song')).song }}" - "Album name: {{ fromJson(kv('favorite_song')).album.name }}" - "Album release date: {{ fromJson(kv('favorite_song')).album.release_date }}"
- id: get type: io.kestra.plugin.core.kv.Get key: favorite_song
- id: parse_json_from_kv type: io.kestra.plugin.core.log.Log message: "Album name: {{ fromJson(outputs.get.value).album.name }}"Read keys by prefix with the GetKeys task
Search for keys matching a prefix with GetKeys:
id: get_keys_by_prefixnamespace: company.team
tasks: - id: get type: io.kestra.plugin.core.kv.GetKeys prefix: "test_" namespace: company.team
- id: log_key_prefix type: io.kestra.plugin.core.log.Log message: "{{ outputs.get.keys }}"The output is a list of matching keys, or an empty list if none are found.
Delete a KV pair with the Delete task
Delete produces a boolean deleted output confirming whether the key existed and was removed:
id: delete_kv_pairnamespace: company.team
tasks: - id: kv type: io.kestra.plugin.core.kv.Delete key: my_key namespace: company.team errorOnMissing: false
- id: check_if_deleted type: io.kestra.plugin.core.log.Log message: "{{ outputs.kv.deleted }}"REST API
Create a KV pair
Use a PUT request to set a KV pair:
curl -X PUT -H "Content-Type: application/json" http://localhost:8080/api/v1/main/namespaces/company.team/kv/my_key -d '"Hello World"'The API returns no response body on success.
Read all keys in a namespace
curl -G "http://localhost:8080/api/v1/main/kv" \ --data-urlencode "filters[namespace][EQUALS]=company.team" \ -H "Authorization: Bearer <API-TOKEN>"The response is a JSON array of keys with their creation and update timestamps:
[ {"key":"my_key","creationDate":"2024-07-27T06:10:33.422Z","updateDate":"2024-07-27T06:11:08.911Z"}, {"key":"test_key","creationDate":"2024-07-27T04:37:18.196Z","updateDate":"2024-07-27T04:37:18.196Z"}]Your Kestra instance exposes an interactive API reference at https://<your-kestra-host>/api listing all available endpoints.
Delete a KV pair
curl -X DELETE -H "Content-Type: application/json" http://localhost:8080/api/v1/main/namespaces/company.team/kv/my_keyReturns true if the key existed and was deleted, false if it did not exist.
Terraform
Use the kestra_kv resource to create or update a KV pair:
resource "kestra_kv" "my_key" { namespace = "company.team" key = "my_key" value = "Hello World" type = "STRING"}Use the kestra_kv data source to read a KV pair:
data "kestra_kv" "new" { namespace = "company.team" key = "my_key"}Run terraform apply to create, update, or delete KV pairs from your Terraform state.
Was this page helpful?