Dynamic values
Typed config values — strings, numbers, booleans, JSON — that change instantly without a redeploy. Schema-validated, versioned.
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 feature flags, dynamic values are global per environment: every user in prod sees the same value. To vary by user, put a feature flag around the code that reads the value, or use a structured config with one key per cohort.
Types
Prop
Type
Reading a value
import { configure, Client } from "@shipeasy/sdk/server";
configure({ apiKey: process.env.SHIPEASY_SERVER_KEY ?? "" });
const flags = new Client(currentUser);
const limit = flags.getConfig<number>("rate-limit") ?? 100;
const theme = flags.getConfig<string>("default-theme") ?? "light";
const cfg = flags.getConfig<{ 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 { configure, Client } from "@shipeasy/sdk/client";
configure({ clientKey: process.env.NEXT_PUBLIC_SHIPEASY_CLIENT_KEY ?? "" });
const flags = new Client(currentUser);
const cfg = flags.getConfig<{ tier: string; limit: number }>("plan-defaults");Same plumbing as
getFlag() — the bundle is in memory after configure() resolves, 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 configs update rate-limit --value 200
shipeasy configs update default-theme --value '"dark"'
shipeasy configs update plan-defaults --value '{"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 configs activity rate-limit
# Roll back to a specific revision — currently dashboard-only.
# Open the config detail view and pick "Restore" on the revision you want.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":
{
"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.getConfig<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:
{
"default": "claude-haiku-4-5",
"premium": "claude-opus-4-7",
"fallback": "claude-haiku-4-5",
"timeout_ms": 30000
}{
"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.getConfig("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.
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 feature flag
| Question | Use |
|---|---|
| Should I show feature X? | Feature flag. |
| What should the value of N be? | Dynamic value. |
| Different value per user / cohort? | Feature flag 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", put it behind a feature flag. 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:
- name: List configured values
run: shipeasy configs list --json | jq '.data[].name'Pair that with a grep across your source so a CI step fails when the code references a value that no longer exists on the project.
Limits
| Plan | Values per project | JSON size per value |
|---|---|---|
| Free | 25 | 4 KB |
| Pro | 250 | 64 KB |
| Enterprise | Unlimited | 256 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".
Add targeting rules to a feature flag.
Now that you've seen typed values, see how feature flags layer rules and rollout to vary behaviour per user — deterministically, with no flicker.
Quickstart
From zero to a feature behind a feature flag in production — install the SDK, create a feature flag, roll it out, kill-switch it, and watch it in the dashboard. Five minutes, end to end.
Targeting & rollouts
Express "show this to the right users" with attribute rules, deterministic percentage rollouts, and per-feature-flag salts. The rules engine, in detail.