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.
basealways 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 asource:default(base),environment(explicit override), orinherited. - 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
adminrole 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.
baseis 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 permissionOver 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 permissionlist-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 stagingThe 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 schemavalidate 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 permissionHistory 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 valuesOver 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
- Remote Config API - every endpoint, callable
- Feature flags and targeting - rule types, evaluation, analytics
- Related: KV Store for plain key-value data, Config for the static project configuration the server loads
