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 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");
getConfig() is synchronous, like getFlag()

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":

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.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:

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.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.

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 feature flag

QuestionUse
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:

.github/workflows/ci.yml
- 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

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 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.

Set a value
$shipeasy configs update rate-limit --value 200
Audit changes
$shipeasy configs activity rate-limit
Was this page helpful?
✎ Edit this page

On this page