Skip to content

Remote Config

Try the interactive example ↓

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.
  • Resources start private. The creator can read, edit and share a config or named environment. A resource can instead inherit its workspace's access. Tenant membership and a token's admin role do not grant access to private data.
  • Production changes require two grants. Writing an environment value or its rules requires config-editor and environment-editor access. Reading it requires reader access to both. base is shared tenant infrastructure, so ordinary private configs remain easy to create and edit without a second grant.
  • History follows the same boundary. Config editors can read change history only for environments they can read. Deleting and recreating an environment with the same name does not expose the deleted environment's history.

See Permissions and sharing for the grant API and service accounts.

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     # requires the resource's management permission

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   # requires the resource's management permission
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                        # requires the resource's management permission

list-envs is a list, oldest first, filtering on name and created_at.

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: 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 --limit 5
snug remote-config rollback -k limits.rps -v 2 -r "500 rps melted staging"  # requires the resource's management permission

History is newest-first and pages by cursor; --filter 'environment[eq]=base' narrows it to one environment. Captured live and trimmed:

{
  "history": [
  {
    "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"
  },
  ...
  ],
  "page": { "next_cursor": "eyJ2IjoxLCJzIjoi...", "has_more": true }
}

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. --filter 'tags[eq]=ui' -v   # -v includes values

Over HTTP, GET /api/v1/config takes the shared list parameters: filter[field][op] on key, tags, created_by, created_at and updated_at (filter[tags][eq]=ui), sort (created_at or updated_at), q, limit with cursor or page, and total=true. Its own prefix, environment and include_values parameters narrow and shape the list; environment keeps only configs with a value in that environment and makes include_values return that value.

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.

Reference

Interactive example

Give environments their own settings while inheriting shared defaults. Target specific audiences and keep a version history of configuration changes.

An illustrative example that runs in your browser. No account or API calls required.