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

Config Management

Ironflow provides environment-scoped configuration management. It allows you to store, patch, and watch JSON documents representing application settings, feature flags, or system parameters.

Config Management is built directly on top of Ironflow’s NATS JetStream architecture using system-level KV buckets (SYS_config_*), separate from user-facing KV Store buckets.

  • Durable: Every change is persisted to an internal NATS KV bucket (not visible in the KV Store dashboard).
  • Atomic: patch operations use automatic CAS (Compare-And-Swap) retries to prevent lost updates.
  • Observable: Every set, patch and delete emits a system event that can trigger workflows or real-time dashboards.
  • Self-healing: Config buckets are lazily created on first use and automatically recreated if deleted.
FeatureUse For
Config ManagementApplication settings, Feature flags, Environment toggles.
KV StoreSession data, caching, high-frequency counters.
SecretsAPI Keys, DB passwords (encrypted at rest).

import { createClient } from "@ironflow/node";
const ironflow = createClient({ apiKey: process.env.IRONFLOW_API_KEY });
const config = ironflow.config();
// Set a full document
await config.set("app-settings", { theme: "dark", retries: 3 });
// Patch a specific field (shallow merge)
await config.patch("app-settings", { retries: 5 });
// Watch for real-time changes (Node SDK also supports watch via WebSocket)
const watcher = config.watch("app-settings", {
onUpdate: (event) => console.log("New settings:", event.data),
});
// watcher.stop() to end the subscription

config.patch() automatically creates the document if it does not exist — no separate “init” call is required.

from contextlib import aclosing
from ironflow import IronflowClient
client = IronflowClient("http://localhost:9123", api_key="ifkey_...")
client.config_create("app-settings", {"theme": "dark", "retries": 3})
# Watch for real-time changes (needs: pip install 'ironflow-py[watch]')
async with aclosing(client.watch_config("app-settings")) as events:
async for ev in events:
print(ev.revision, ev.data)
import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });
// Watch for real-time changes
const sub = await ironflow.configManager().watch("app-settings", {
onUpdate: (cfg) => console.log("New settings:", cfg.data),
});

MethodPathDescription
GET/api/v1/configList all config documents.
GET/api/v1/config/{name}Get a specific document.
POST/api/v1/config/{name}Replace a document (Full Set).
PATCH/api/v1/config/{name}Shallow merge fields (creates the document if missing).
DELETE/api/v1/config/{name}Delete a document.
GET/api/v1/config/{name}/watchWebSocket stream of ConfigWatchEvents.

Successful POST and PATCH writes also publish the system event system.config.{name}.updated, which any subscriber can consume. A successful DELETE publishes system.config.{name}.deleted, with data set to null.

For a conditional write, send If-Match: <revision> using the numeric revision returned by GET. POST, PATCH and DELETE accept it. POST also accepts If-None-Match: * to create only if the name is absent. A failed condition returns 412 without changing the document. If-Match succeeds only while that revision is the live document, so a deleted config cannot be written back with its delete revision from the watch stream. A conditional PATCH merges once onto that revision and does not retry. A combined, repeated, empty or malformed condition returns 400. Without these headers, POST creates or replaces, PATCH merges, and DELETE deletes, all unconditionally.

The body of POST and PATCH must be a JSON object; null returns 400. A body larger than 4 MiB returns 413. The stored document has the NATS message limit (1 MiB by default), and a document larger than that limit also returns 413.

The Node and Go SDKs send these conditions for you:

NodeGoCondition
config.create(name, data)Config().Create(ctx, name, data)If-None-Match: *
config.update(name, data, revision)Config().Update(ctx, name, data, revision)If-Match on POST
config.patchIf(name, data, revision)Config().PatchIf(ctx, name, data, revision)If-Match on PATCH
config.deleteIf(name, revision)Config().DeleteIf(ctx, name, revision)If-Match on DELETE

In Python, pass if_match= or if_none_match= to config_create, and if_match= to config_patch or config_delete.

The dashboard uses the revision captured when editing starts. Each config keeps its own draft, so you can select another config and come back to it. If another writer saves a newer revision or deletes the config, the editor warns that saving will fail. If another writer changes or deletes the document, saving reports a conflict and keeps the draft open. Copy the draft, select Cancel, then select Edit to load the latest version. Creating a config in the dashboard also refuses to replace an existing name. The list marks each config that has an unsaved draft. Delete deletes only the revision on screen; if another writer changed the config, the delete fails and the config stays.


Config operations throw typed errors consistent with the main Ironflow client:

import { IronflowError, UnauthenticatedError, UnauthorizedError } from "@ironflow/node";
try {
const entry = await config.get("app-settings");
} catch (err) {
if (err instanceof UnauthenticatedError) {
console.error("Invalid or missing API key");
} else if (err instanceof UnauthorizedError) {
console.error("Insufficient permissions");
} else if (err instanceof IronflowError) {
console.error("Config error:", err.message);
}
}
from ironflow import IronflowError
try:
entry = client.config_get("app-settings")
except IronflowError as err:
if err.status_code == 401:
print("Invalid or missing API key")
elif err.status_code == 403:
print("Insufficient permissions")
else:
print("Config error:", err)