ServicesRemote Config

Remote Config

Environment-aware configuration and feature flags: JSON config values with optional JSON Schema validation, environments that inherit from one another, a full version history with audited rollback, and per-user targeting rules.

When to reach for it: feature flags and A/B tests, dev/staging/prod settings that mostly share values, progressive rollouts, any configuration whose changes need validation, an audit trail, and one-command rollback.

When not to: plain key-value data without environments or governance belongs to KV Store; the static, read-only project configuration that the server itself loads is the separate Config service.

Concepts

  • A config is a JSON value under a key like feature.dark-mode, with a description, optional tags, and an optional JSON Schema. The schema is sticky: set it at create time and every later update is validated.
  • Environments inherit. base always exists and is protected. Other environments name a parent via --inherits-from; reading a key walks up the chain until a value is found. Every read reports a source: default (base), environment (explicit override), or inherited.
  • Every write is history. Updates append who/when/why entries per environment, and rollback re-applies an old value as a new version, so the audit trail never rewinds.
  • Targeting rules turn a config into a feature flag evaluated per user - segments, percentage rollouts, schedules, allow/block lists. They have their own page: Feature flags and targeting.
  • Reads are open, writes are role-gated. Any authenticated user can get, list, bulk-get, validate, and evaluate. Creating and updating configs and rules requires a platform admin or editor role; deletes, rollback, and environment management require platform admin - a plain token gets 403 Admin or editor role required on writes.

Create and read

snug remote-config create -k feature.dark-mode -v '{"enabled":true,"level":3}' \
  -d "Dark mode toggle" -t ui,web
snug --output json remote-config get -k feature.dark-mode
snug remote-config get -k feature.dark-mode -e staging     # environment read
snug remote-config delete -k feature.dark-mode --force     # admin only

Over HTTP the same read is GET /api/v1/config/{key}; captured live after an override was written to a staging environment:

curl -H "Authorization: Bearer $SNUG_API_TOKEN" \
  "http://localhost:4000/api/v1/config/docs4-rc.dark-mode?environment=docs4-staging"
{
  "status": 200,
  "msg": "OK",
  "data": {
    "key": "docs4-rc.dark-mode",
    "value": { "enabled": false, "level": 1 },
    "type": "unknown",
    "version": 1,
    "source": "environment",
    "updated_at": "2026-08-28T04:33:49.885188Z",
    "environment": "docs4-staging",
    "metadata": null,
    "cache_ttl_seconds": 300
  }
}

cache_ttl_seconds is the server's hint for how long clients may cache the value (default 300).

Environments and inheritance

snug remote-config create-env -n staging -d "Staging" -i base   # admin only
snug remote-config list-envs
snug remote-config update -k feature.dark-mode -v '{"enabled":false}' -e staging
snug remote-config delete-env -n staging                        # admin only

Verified progression: before the staging update, a get -e staging returned the base value with "source": "inherited"; after it, the override with "source": "environment"; the base value itself never changed.

Two verified rules about deletion: the base environment cannot be deleted (400 invalid_operation), and delete-env is not blocked by dependents - it succeeds even if other environments inherit from the target, and it strips that environment's values from every config.

Bulk get

snug remote-config bulk-get -k feature.dark-mode,feature.beta -e staging

The response splits into configs (with per-key value, version, source) and missing_keys. Gotcha, reproduced live: bulk-get only returns values explicitly set in the requested environment - keys that would resolve through inheritance land in missing_keys - unless the request body sets include_defaults: true, which the CLI does not expose; use the HTTP endpoint when you need inherited values in bulk.

Schema validation

snug remote-config create -k limits.rps -v '{"rps":100}' -d "Rate limit" \
  -s '{"type":"object","properties":{"rps":{"type":"integer","minimum":1}},"required":["rps"]}'
snug remote-config update -k limits.rps -v '{"rps":"lots"}'
# 400 schema_validation_failed: /rps: "lots" is not of type "integer"
snug remote-config validate -k limits.rps -v '{"rps":"lots"}'   # dry-run
snug remote-config schema -k limits.rps                          # fetch schema

validate never writes; it returns per-key valid plus error strings and exits non-zero when anything is invalid, so it works as a CI gate.

History and rollback

snug --output json remote-config history -k limits.rps -l 5
snug remote-config rollback -k limits.rps -v 2 -r "500 rps melted staging"  # admin only

History is newest-first, per environment; captured live and trimmed:

[
  {
    "key": "docs4-rc.rate-limit",
    "version": 3,
    "value": { "rps": 500 },
    "previous_value": { "rps": 200 },
    "environment": "base",
    "changed_by": "docs-wave-admin",
    "changed_at": "2026-08-28T04:34:05.670046Z",
    "reason": "Update in base environment"
  },
  ...
]

Rolling back to version 2 wrote a new version 4 carrying version 2's value - history is append-only. The -r reason is required and stored in the trail; a version that never existed is 404 history_entry_not_found.

Listing

snug remote-config list --prefix feature. -t ui -v   # -v includes values

Over HTTP, GET /api/v1/config also accepts the shared query grammar's filter (filter=tags:eq:ui verified live), sort_by/sort_order, and standard pagination, alongside its own prefix, environment, and include_values parameters.

Watch the CLI's overloaded short flags: -v is --value on create and update, --version on rollback, --values on list; -r is --reason on rollback but --rules on set-rules.

Limits and configuration

Values up to 1 MiB, at most 50 environments, 100 retained history entries per config, 1000 configs per owner, and a 300-second client cache TTL by default - all tunable via the REMOTE_CONFIG_* variables in the Remote Config CONFIG reference.

Reference

On this page