Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

KV Store

Ironflow provides a general-purpose key-value store backed by NATS JetStream. User buckets are namespaced under an internal APP_ prefix so they cannot collide with system-level state (SYS_config_*, SYS_secrets_*). Operations include CRUD with revision tracking, atomic create/update (CAS), tombstone-style delete, history purge, key listing, and real-time watch over WebSocket.

A bucket belongs to the environment that creates it. Two environments can each have a bucket named orders, and neither can list, read or write the other’s. The environment comes from the API key or the session, never from the bucket name.

Bucket names that start with SYS_, ENV_, config_env_ or MOD_ are reserved. Every KV route refuses them. All reserved names are listed in Reserved names.

  • Buckets: Namespaced containers with optional description, TTL, max value size, max bucket size, and per-key history depth.
  • Atomic operations: create uses If-None-Match: * (if-not-exists) and update uses If-Match: <revision> (compare-and-swap). Both return HTTP 412 on conflict.
  • Soft delete + purge: delete writes a tombstone (preserves history); purge removes the key and its history.
  • Key listing: listKeys with optional wildcard filter (e.g., user.*).
  • Real-time watch: bucket.watch(...) in the Node and Browser SDKs (Go and Python have their own watch calls) opens a WebSocket to /api/v1/kv/buckets/{bucket}/watch and emits kv_update events. Optional key filter scopes the stream.
  • Event-driven: Every write is a JetStream message, so audit logs and triggers can be layered on top.
interface KVBucketConfig {
name: string;
description?: string;
ttlSeconds?: number; // 0 = no expiry
maxValueSize?: number; // bytes per value
maxBytes?: number; // bucket-wide cap
history?: number; // revisions kept per key (default 1)
}
import { createClient } from "@ironflow/node";
const client = createClient({ apiKey: process.env.IRONFLOW_API_KEY });
const kv = client.kv();
// Create a bucket with 1h TTL and 5 revisions of history
await kv.createBucket({
name: "settings",
ttlSeconds: 3600,
history: 5,
});
const bucket = kv.bucket("settings");
// Unconditional put
await bucket.put("theme", { mode: "dark" });
// Atomic create — fails if key exists (HTTP 412)
await bucket.create("flags", { betaEnabled: false });
// Read with revision
const entry = await bucket.get("theme");
console.log(entry.value, entry.revision);
// Compare-and-swap update
await bucket.update("theme", { mode: "light" }, entry.revision);
// List keys with wildcard filter
const keys = await bucket.listKeys("user.*");
// Soft delete (tombstone) and full purge
await bucket.delete("flags");
await bucket.purge("flags");
// Bucket management
const buckets = await kv.listBuckets();
const info = await kv.getBucketInfo("settings");
await kv.deleteBucket("settings");
import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });
const bucket = ironflow.kv().bucket("settings");
const entry = await bucket.get("theme");
// Real-time watch over WebSocket
const watcher = bucket.watch(
{
onUpdate: (event) => console.log(event.key, event.value),
onError: (err) => console.error(err),
onClose: () => console.log("watch closed"),
},
{ key: "theme" } // optional filter
);
// Later
watcher.stop();
FeatureUse ForBucket PrefixEncrypted
KV StoreSessions, caching, counters, app dataAPP_*No
ConfigApp settings, feature flags, togglesSYS_config_*No
SecretsAPI keys, DB passwords, certificatesSYS_secrets_*Yes (AES-256-GCM)

For large files, uploads or signed URLs, use File Storage instead of KV.

The KV Store dashboard only lists APP_-prefixed buckets; SYS_* buckets are hidden from the user-facing UI.