namespace.toml
The namespace descriptor declares the flag namespace's identity, an optional roster of named environments shared by every flag in the namespace, and global settings such as telemetry. The file is optional.
When absent, the linter runs the flag namespace in untyped-env mode: slug is inferred from the directory name, every other field takes its documented default, and the SDK accepts any pattern-valid env slug at evaluation time. Declaring namespace.toml is what earns you a typo-checked env list, dashboard metadata, and per-env public-evaluate gating.
The descriptor does not declare the tenant — tenant ownership is server-side metadata assigned at namespace-creation time and never expressed in the manifest.
When to declare namespace.toml
The minimum viable manifest is a directory containing one flags/<key>.toml and no namespace.toml. Declare namespace.toml once any of these becomes true:
- You have more than one environment whose behavior diverges, and want lint to catch typos like
prodinstead ofproduction(E010only fires in typed-env mode). - You want a dashboard
display_nameordescriptionfor the flag namespace. - You want any environment to opt into public evaluation via
public_evaluate = true(requires a typed env list). - You want to suppress telemetry (
telemetry_enabled = false), declare per-namespaceprivate_attributes, or setraw_entity_ids = true.
Until then, the file's absence is the right shape. The linter does NOT warn about a missing namespace.toml.
Complete example
schema_version = "0.1"
[namespace]
slug = "payments"
display_name = "Payments Team"
description = "Feature flags for the payments service and checkout domain"
telemetry_enabled = true
[namespace.environments]
development = { display_name = "Development" }
staging = { display_name = "Staging" }
production = { display_name = "Production" }
Top-level fields
schema_version
See schema-versioning. MUST be present and well-formed; otherwise E001.
[namespace] table
| Field | Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|
slug | string | no | the directory name | Pattern [a-z][a-z0-9-]*, max 63 chars; when present MUST equal the directory name | The flag-namespace identifier. Unique within its tenant. |
display_name | string | no | the slug | Non-empty when present (W010 on empty string) | Human-readable name shown in dashboards and CLI output. |
description | string | no | "" | Free-form prose | Description of the flag namespace's purpose and owning team. Used for ownership tracking and dashboards. |
telemetry_enabled | bool | no | true | — | When false, the server skips telemetry recording for every flag in this flag namespace. |
raw_entity_ids | bool | no | false | — | When true, the SDK emits the raw bucketing identifier in unit_id_hash instead of its SHA-256 digest. |
private_attributes | array of strings | no | [] | Each entry MUST be a string | Names of context attributes the SDK MUST strip from context_attributes and secondary_unit_ids before record emission. Applies to every flag in the flag namespace. |
slug
The slug is the identifier for this flag namespace within its tenant. It appears in:
- The manifest directory name (
<slug>/). - The server URL path (
/api/v1/tenants/<tenant>/namespaces/<slug>/...). - SDK initialization (
namespace_uri = "https://.../<slug>/manifest.tar.gz"). - Audit log entries.
The slug is immutable. To rename a flag namespace, create a new one with the new slug, migrate flags and segments, update SDK consumers, and delete the old one. Direct rename is not supported.
namespace.slug MAY be omitted; when omitted, the slug is inferred from the directory containing namespace.toml. When present, it MUST equal the directory name — E017 fires on mismatch so a stale slug after a directory rename is surfaced rather than silently honored. A slug that is present but malformed raises E030 independently of the directory-match check. The directory name itself MUST also be a valid slug; otherwise lint of an inferred slug fails with E030.
display_name and description
Both are optional metadata. They have no semantic effect on flag evaluation. They appear in dashboards, CLI listings, and server API responses (GET /api/v1/tenants/{tenant}/namespaces/{namespace} returns them as declared).
display_name, when declared, MUST NOT be empty — display_name = "" raises W010 (a blank dashboard label is almost always an oversight). To accept the default (the slug), omit the field entirely. description has no non-empty constraint.
telemetry_enabled
namespace.telemetry_enabled is the only telemetry switch defined by the manifest format. There is no per-flag telemetry_enabled; setting namespace.telemetry_enabled = false suppresses telemetry for every flag in the flag namespace.
telemetry_enabled = false is intended for flag namespaces handling regulated or privacy-sensitive data where evaluation-rate telemetry would itself be sensitive. It does NOT affect SDK-side OpenTelemetry spans or metrics; those are controlled by the SDK's own configuration.
raw_entity_ids
Controls how the bucketing identifier is rendered in evaluation records emitted by the SDK.
- Default (
false): the SDK hashes the value referenced by[segment.bucket].entity_id_attributewith SHA-256 and emits the lowercase-hex digest inunit_id_hash. true: the SDK emits the raw identifier verbatim in the same field. The field nameunit_id_hashis preserved for record-shape stability.
raw_entity_ids = true is intended for flag namespaces whose bucketing identifier is not personally identifying — internal account IDs, B2B tenant slugs, opaque request tokens — where the analytical convenience of joining records to upstream systems on the raw identifier outweighs the lack of pseudonymization. It SHOULD NOT be set on flag namespaces whose bucketing context could carry PII.
The field has no effect on evaluation behavior. It is read only at record-emission time and is independent of telemetry_enabled (when telemetry is disabled, raw_entity_ids is irrelevant — no records are produced).
private_attributes
Declares context-attribute names the SDK MUST strip from context_attributes and from secondary_unit_ids before emitting an evaluation record. The match is byte-exact on the attribute name; values are not inspected.
[namespace]
slug = "payments"
private_attributes = ["user.email", "user.phone", "request.ip"]
The list applies to every flag in the flag namespace. Per-flag additions live in flag.private_attributes (flag § private_attributes); the effective set is the union of the namespace list and the flag list. Removing an attribute from either list at record-emission time requires editing the manifest — there is no SDK or runtime override.
The linter validates only shape: a non-array value or any non-string entry produces E001. The linter does NOT validate that the named attributes appear in any predicate or bucket — listing an unused attribute is accepted (telemetry diagnostic T011 surfaces the inverse condition: a non-listed attribute that should have been listed).
This field has no effect on evaluation behavior.
[namespace.environments] table
Declares every environment that flag files may reference. Every key is an environment slug; every value is an inline table (or sub-table) of environment metadata.
[namespace.environments]
development = { display_name = "Development" }
staging = { display_name = "Staging" }
production = { display_name = "Production" }
Typed vs. untyped env mode
The [namespace.environments] table is OPTIONAL. Its presence selects one of two modes:
| Mode | Trigger | Effect |
|---|---|---|
| Typed | [namespace.environments] declared with ≥ 1 entry | Valid env slugs are exactly the table's keys. Flag files referencing any other env slug fail with E010. Per-env metadata (display_name, public_evaluate) is honored. |
| Untyped | [namespace.environments] absent, or namespace.toml itself absent | Any env slug matching [a-z][a-z0-9-]* is accepted; no typo protection. public_evaluate is unavailable; the server rejects namespace-client tokens against untyped-env flag namespaces with 403. |
A [namespace.environments] table declared but empty (zero entries) raises E023 — that shape is almost always an in-progress edit and is more useful as an error than a silent reversion to untyped mode. To run untyped, omit the table entirely.
Environment metadata fields
Each environment value supports:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
display_name | string | no | the env slug | Human-readable label for this environment shown in dashboards. |
public_evaluate | bool | no | false | When true, the server's evaluation endpoints accept namespace-client (public) tokens bound to this environment. |
Unknown fields inside an environment value are silently ignored — this is the one forward-compatible site in the schema (see schema-versioning § Forward-compat policy).
public_evaluate — semantic effect
public_evaluate = true opts the environment in to public, browser-side evaluation through namespace-client tokens. These tokens are designed to be embedded in browser bundles or other untrusted clients: their secret value is assumed visible to any user of the application, so secrecy is not a defense.
When public_evaluate = true:
- The server accepts
namespace-clienttokens bound to this(tenant, namespace, environment)triple onPOST /api/v1/tenants/{tenant}/namespaces/{namespace}/evaluate{,/all}. - All other endpoints (manifest download, version history, lint, admin) reject
namespace-clienttokens with403 Forbidden. - Evaluation requests bearing a
namespace-clienttoken are subject to a stricter per-(tenant, namespace, environment, Origin)rate limit and are taggedprincipal_type = "client"in telemetry.
When public_evaluate = false (the default), the server rejects every namespace-client token bound to this environment with 403 Forbidden regardless of the token's status. Toggling the field therefore acts as a kill switch even when issued tokens are still in circulation.
public_evaluate is only meaningful in typed-env mode. An untyped-env flag namespace cannot opt into public eval; the server rejects namespace-client tokens against such flag namespaces.
Authoring guidance
- Treat any rule that branches on a request-supplied attribute (e.g.,
user.is_admin,user.entitlement_tier) as a public claim when the env haspublic_evaluate = true. Anyone holding the embedded client token can craft an evaluation context with arbitrary attributes and observe the resulting variant. - Production environments typically set
public_evaluate = falseand route browser traffic to a separateproduction-publicenvironment that mirrors the relevant flag set.
Environment ordering
The TOML serialization preserves order within a single document (alphabetical when serialized back). The server canonicalizes environments by slug. Environment ordering has no semantic meaning at evaluation time — every flag's per-environment block looks up the environment by name.
Cross-file consistency
In typed-env mode, every environment slug referenced in a flag file's [flag.environments.<env>] block MUST appear as a key in [namespace.environments]. References to undeclared environments produce E010 on the offending flag file. Names are byte-exact case-sensitive.
The reserved name _ (the catch-all; see resolution § The _ catch-all) is always accepted in [flag.environments._] regardless of mode and is never reported by E010.
In untyped-env mode, E010 does not fire — any env slug matching [a-z][a-z0-9-]* is accepted. Pattern violations still raise E024 on the flag-side env block.
Common diagnostics
| Scenario | Diagnostic |
|---|---|
namespace.toml missing | none — untyped-env mode, defaults applied, slug = directory name |
namespace.toml empty or unparseable | E001 |
namespace.toml present, [namespace] table missing | none — equivalent to empty namespace.toml under untyped-env mode |
namespace.slug missing inside a present [namespace] table | none — slug inferred from directory name |
namespace.slug declared but does not match directory name | E017 |
namespace.slug violates pattern or exceeds 63 chars | E030 |
namespace.display_name = "" | W010 |
[namespace.environments] declared but empty | E023 |
| Environment slug malformed (typed mode) | E024 |
| Flag-side env block uses unrecognized env (typed mode only) | E010 |
Unknown field at [namespace] | E016 |
private_attributes non-array, or any entry non-string | E001 |
Recommended practice
- Skip
namespace.tomlfor the first hour of a new flag namespace. Drop a single flag file underflags/and iterate. Reach for the descriptor only when you cross one of the lines in § When to declarenamespace.toml. - Once you declare
namespace.toml, also declare at least the standard tier of environments (development,staging,production). Declared-but-unused environments cost nothing and let agents add per-env configuration without further editing ofnamespace.toml. - Keep
public_evaluate = false(the default) on every environment unless you have a deliberate need to serve flag values to browsers or other untrusted clients. When you do enable it, prefer a dedicated environment (e.g.,production-public) so the flag set exposed to browsers is reviewable in isolation from the server-sideproductionflag set. - Use
descriptionfor ownership and contact information rather than embedding it elsewhere — it is the first field humans read when investigating an unfamiliar flag namespace.
See also
- 05-flag.md — what env slugs declared here let flag files reference.
- 09-resolution.md — how the catch-all
_interacts with named environments. - evaluation-context — the attribute names
private_attributestargets. - diagnostics —
E010,E016,E017,E023,E024,E030,W010. - Tokens spec —
namespace-clienttokens, the consumer ofpublic_evaluate.