Introducing JSON Flags: Structured Config Through the Flag Pipeline You Already Use
Flags can now hold a JSON value — objects and arrays — alongside boolean, string, and number. Ship structured config through the same targeting, rollouts, and SDKs as every other flag.
If you have used Zenmanage for remote configuration, you have probably hit this wall: a number flag is great for a timeout, and a string flag is fine for a theme name. But the moment your config needs more than one related value — a retry policy with a max attempt count and a backoff, a feature's rate-limit tiers, a block of onboarding copy with three strings in it — a single primitive stops being enough.
Up to now, the workaround was to either split the config into a pile of separate number and string flags and stitch them back together in your code, or skip flags entirely and hardcode the object — which means a deploy every time the config changes. Neither is great. The first fragments a single logical setting across several flags with no shared identity. The second gives up the whole point of a flag: changing behavior without shipping code.
What Shipped
Json joins boolean, string, and number as a first-class flag type. A json flag's value is any valid JSON document — an object or an array — and it flows through everything a flag already does: default values, targeting rules, rollouts, the debug console, audit history, and every SDK.
In the dashboard, creating a json flag gets you a dedicated editor instead of a plain text box — syntax highlighting, bracket matching, a format action, and inline error markers. Save is blocked on invalid JSON, so a stray trailing comma never makes it into a target value your SDKs will try to parse.
The Management API works the same way it does for every other type:
curl -X POST https://api.zenmanage.com/management/v1/projects/your-project/flags \
-H "Authorization: Bearer mgt_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"key": "checkout-retry-policy",
"name": "Checkout Retry Policy",
"type": "json",
"description": "Retry attempts and backoff for the checkout payment call."
}'
Setting the target value for that flag in an environment takes a JSON document instead of a boolean or a string, and it's canonicalized on save — two documents that differ only in whitespace are treated as the same value, so you don't end up with duplicate-looking values in the history for a formatting change:
curl -X POST https://api.zenmanage.com/management/v1/projects/your-project/flags/checkout-retry-policy/values \
-H "Authorization: Bearer mgt_your_token_here" \
-H "Content-Type: application/json" \
-d '{"display": "{\"maxAttempts\": 3, \"backoffMs\": 250}"}'
Reading JSON Flags in Your SDK
Every Zenmanage SDK ships an asJson()-equivalent accessor (as_json() in Python) alongside asBool(), asString(), and asNumber(). In JavaScript and TypeScript, reading a json flag looks exactly like reading any other flag — pass an inline default, get a typed result back:
const retryPolicy = await zenmanage.flags().withContext(context).single('checkout-retry-policy', {});
const { maxAttempts, backoffMs } = retryPolicy.asJson();
Pass an object or array as the inline default, the same way you'd pass false or "classic" for other types, and the SDK infers a json flag and falls back to that default if Zenmanage is unreachable or the flag doesn't exist yet. That accessor and the inline-default inference work identically across PHP, Laravel, JavaScript, Python, Go, .NET, and Java — so the pattern you learn in one language carries straight over to the next.
Guardrails
A published environment payload contains every flag in that environment, and every connected SDK downloads it. One bloated JSON value would inflate that payload for everyone, so json values are capped at 64 KB and 10 levels of nesting. A value over either limit is rejected at write time with the limit and the actual size named in the error — nothing over-limit ever reaches a connected SDK.
Why This Matters
- One flag, one setting: a related group of values gets a single key, a single history, and a single target — not five number and string flags you have to remember to change together.
- Ship config changes without a deploy: a pricing table, a set of UI copy strings, or a provider's retry policy can change from the dashboard the moment you need it to, with the same audit trail as any other flag change.
- No new mental model: targeting rules, rollouts, the debug console, and webhooks all already understand json values. If you know how to work with a boolean flag, you already know how to work with a json flag.
- Safe by default: an application default of
{}or[]is a safe fallback for most call sites, and older SDK versions that predate json flags degrade to that fallback instead of throwing.
Get Started
Json flags are available now for every Zenmanage project and every SDK. Here's where to go next:
- Flags and Values reference — the full picture of json values, wire format, and size limits.
- Remote Config capability page — see how json flags fit alongside string and numeric config.
- JavaScript quickstart — includes a worked example reading a json value.
- Start your free trial — create an account and set up your first json flag in minutes.
We built json flags because we kept hearing the same thing from teams already using Zenmanage for remote config: eventually, a single value isn't enough. If you're splitting one logical setting across a handful of flags today, we'd like to hear about it — and see what a single json flag does for your setup.