ShipEasy
Flags & ExperimentsConfigs

Dynamic values

Typed config values — strings, numbers, booleans, JSON — that change instantly without a redeploy. Schema-validated, versioned.

Production readyOn this page · 8 min readUpdated · May 3, 2026Works with · Server SDK · Browser SDK

A dynamic value is a typed key/value pair stored on your project. Use one when the answer to "what should this be right now?" is not boolean — pricing, copy, limits, feature options, API endpoints, model names, anything you'd otherwise hard-code as a constant or stash in an environment variable.

Unlike gates, dynamic values are global per environment: every user in prod sees the same value. To vary by user, gate the code that reads the value, or use a structured config with one key per cohort.

Types

Field
Type
Description
stringrequired
string
Copy, theme name, feature variant. Example: "v2", "USD", "claude-haiku-4-5".
numberrequired
number
Limits, prices, timeouts. Example: 9.99, 100, 30000.
booleanrequired
boolean
Site-wide on/off, separate from a gate when no targeting is needed.
jsonrequired
object | array
Structured config — see below. Up to 256 KB per value (Enterprise).

Reading a value

import { configureShipeasy, flags } from "@shipeasy/sdk/server";

configureShipeasy({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
await flags.init();

const limit = flags.value<number>("rate-limit") ?? 100;
const theme = flags.value<string>("default-theme") ?? "light";
const cfg = flags.value<{ tier: string; limit: number }>("plan-defaults");

The optional generic types the return value. Always provide a fallback — your code should never crash because the SDK hasn't initialised yet, or because someone deleted the value in the dashboard.

Browser:

import { configureShipeasy } from "@shipeasy/sdk/client";
const client = configureShipeasy({ apiKey });
const cfg = client.getValue<{ tier: string; limit: number }>("plan-defaults");
value() is synchronous, like gate()

Same plumbing as

flags.gate()

— the bundle is in memory after init(), so reads are a hash table lookup. There is no async, no Promise, no fetch.

Updating a value

Dashboard: Configs → Dynamic values → click → edit value → Save. The change is in KV in <100 ms and visible to your SDK on the next poll (default 30s; faster on Pro).

CLI:

shipeasy values set rate-limit 200
shipeasy values set default-theme "dark"
shipeasy values set plan-defaults --json '{"tier":"pro","limit":50}'

A new value creates a revision; you can roll back from the dashboard at any time. Every revision records the author, source (dashboard / CLI / API), and a diff against the previous value.

# View revision history
shipeasy values history rate-limit

# Roll back to a specific revision
shipeasy values rollback rate-limit --revision 7

Structured configs

A single JSON value can encode an entire decision tree. This is great for things like plan limits, where the answer is "a struct" rather than "a value":

plan-defaults
{
  "free": { "max_seats": 3, "history_days": 7 },
  "pro": { "max_seats": 25, "history_days": 90 },
  "ent": { "max_seats": null, "history_days": 365 }
}
type PlanCfg = Record<string, { max_seats: number | null; history_days: number }>;
const cfg = flags.value<PlanCfg>("plan-defaults");
const seats = cfg?.[user.plan]?.max_seats ?? 3;

This collapses N flags into one JSON config that's atomically updated — no race conditions where the user sees half a new config and half the old one. The KV write is a single object, and the SDK either has the old version or the new one.

Other classic shapes:

ai-models
{
  "default": "claude-haiku-4-5",
  "premium": "claude-opus-4-7",
  "fallback": "claude-haiku-4-5",
  "timeout_ms": 30000
}
copy/hero
{
  "headline": "Ship features 10× faster",
  "sub": "Flags, configs, and experiments — one SDK.",
  "cta_label": "Start free"
}

Validation

When you create a value you can attach a schema:

  • Type: one of string / number / boolean / json.
  • For json: optionally a Zod schema, stored as a JSON Schema document on the project.

If a write would violate the schema, the dashboard, the CLI, and the API all reject it before it reaches KV. Bad config never goes live.

The SDK still applies a runtime decoder you supply, as a defence-in-depth:

import { z } from "zod";

const Schema = z.object({ tier: z.string(), limit: z.number() });

const cfg = flags.value("plan-defaults", {
  decode: (raw) => Schema.parse(raw),
});

The SDK calls decode once per fetched blob — invalid payloads are dropped (with a console warning) and the previous good value is kept. Your code never sees a malformed config.

Schema-validated, atomically updated, versioned

Three properties together that JSON-on-disk doesn't give you. A bad value never reaches production. A good value is in production in seconds. A great value can be reverted in one click.

When to use a value vs. a gate

QuestionUse
Should I show feature X?Gate.
What should the value of N be?Dynamic value.
Different value per user / cohort?Gate around the read, or structured value keyed by cohort.
Need an audit trail?Both. Every write is versioned.
Need a kill-switch?Killswitch.

A useful rule: if you'd say "show feature X", gate it. If you'd say "set feature X to value Y", use a dynamic value.

CI snippet

Validate every value reference in your code resolves on the project:

.github/workflows/ci.yml
- name: Validate dynamic values
  run: shipeasy values validate ./src

This catches typos like rate_limit vs rate-limit and stale references after a value has been deleted.

Limits

PlanValues per projectJSON size per value
Free254 KB
Pro25064 KB
EnterpriseUnlimited256 KB

If your JSON is approaching the limit, you're probably storing data that belongs in a database, not a config. Configs are designed for "a few KB of decisions per project", not "the user's shopping cart".

NEXT

Add targeting rules to a gate.

Now that you've seen typed values, see how gates layer rules and rollout to vary behaviour per user — deterministically, with no flicker.

Set a value
$shipeasy values set rate-limit 200
Roll one back
$shipeasy values rollback rate-limit --revision 7
Was this page helpful?✎ Edit on GitHub

On this page