How Flaggator works
This page explains the product model: what you store, who you evaluate for, and how a decision is chosen. For HTTP paths and status codes, see the API. For a full curl walkthrough, see the example.
Introduction
Flaggator is a feature-flag service you run in your own network. One process serves a versioned HTTP API. Applications ask that API which variation a subject should receive. Operators use the same API to create projects, environments, flags, segments, and targeting.
There is no hosted control plane and no license fee tied to traffic. Flag rules are data. The evaluator interprets them; it does not run code that callers send.
Core ideas
- API first. Behavior is the HTTP API. A UI, if you add one, is a client of that API.
- Two planes. Management edits configuration. Evaluation resolves a context to values.
- Deterministic decisions. The same configuration and the same context always produce the same result. Time and randomness are not inputs to evaluation.
- Scoped targeting. Variations belong to the project. On/off, rules, targets, and segments belong to one environment.
- No user accounts in v1. A bearer token is the caller: a management token or an SDK token.
Terminology
| Term | Meaning |
|---|---|
| Project | Container for flags and environments. Identified by a stable key. |
| Environment | A stage inside a project, such as dev or prod. Targeting and segments live here. |
| Flag | A named switch in a project. The key is immutable. |
| Variation | One possible JSON value of a flag. Identity is the index in the ordered list. |
| Configuration | Targeting of one flag in one environment: on/off, targets, rules, fallthrough, prerequisites, version. |
| Context | Who you evaluate for: a subject plus attributes. |
| Segment | A named set of subjects in one environment, reusable across flags. |
| Target | An explicit pin of subjects (and/or segments) to a variation index. |
| Rule | An ordered predicate. The first matching rule wins. |
| Clause | One comparison inside a rule. Every clause in the rule must match. |
| Rollout | A weighted split across variations, bucketed by a hash of the subject. |
| Fallthrough | What is served when the flag is on and no target or rule matches. |
| Prerequisite | Another flag that must evaluate to a given variation before this flag’s rules run. |
| Decision | The result: value, variation index, reason, config version. |
| Salt | A stable string mixed into the rollout hash so buckets stay put when unrelated rules change. |
| Principal | The authenticated caller: management token or SDK token. |
Projects and environments
A project holds the catalog: flags and their variation lists. An environment holds how those flags behave in that stage.
Turning a flag on in prod does not turn it on in dev. Segments also belong to one environment: membership in prod is separate from membership in dev.
Flags and variations
Every flag has an ordered list of JSON values. The index is the variation’s identity. A boolean flag is a normal flag whose variations are false (index 0) and true (index 1). Numbers, strings, arrays, and objects are all ordinary values.
Flag keys are immutable. In v1 you can append variations; you cannot remove or reorder ones that configurations still use.
{
"key": "checkout-v2",
"name": "Checkout v2",
"variations": [false, true]
}
Subjects and context
Evaluation always answers a question about one context: who is asking, and which attributes they carry.
{
"subject": "user-123",
"attributes": {
"country": "IT",
"plan": "pro",
"age": 30
}
}
- subject is a non-empty string. Targets, segments, and rollouts use it.
- attributes is a flat map of scalars or arrays of scalars. Nested objects are out of scope in v1.
- Attribute names are case-sensitive.
- A missing attribute does not match a clause — including negative operators such as neq. Absence is not a value.
Without a subject, individual targets and percentage rollouts cannot assign a bucket. Rules that only look at attributes can still match.
Segments
A segment is a named set of subjects in one environment. Many flags can cite the same segment, so you maintain a list of beta users once instead of copying names into every flag.
{
"key": "beta-testers",
"name": "Beta testers",
"subjects": ["alice", "carol", "dave"]
}
- key is what targets cite.
- name is a display label. Evaluation does not read it.
- subjects is the membership. A write replaces the whole list. An empty list matches nobody.
- At most 1000 subjects per segment. No duplicates.
Segments hold subjects only. They do not hold clauses. Rules do not use a segment_match operator yet; that can be added later without changing evaluation order.
Deleting a segment that a target in the same environment still cites is a conflict. Changing membership updates who is targeted the next time you evaluate.
Configuration
For one flag in one environment, targeting is a single document. New flags start disabled in every existing environment.
| Field | Role |
|---|---|
| enabled | When false, serve the off variation immediately. |
| off_variation | Variation when the flag is off, or when a prerequisite fails. |
| targets | Map of variation index → subjects and/or segment keys. |
| rules | Ordered list. First match wins. |
| fallthrough | Variation or rollout when nothing else matched. |
| prerequisites | Other flags that must evaluate to a given variation first. |
| version | Integer bumped on every successful write of this document. |
Rules and clauses
Rules run in order. A rule matches when every clause matches (logical AND). The first matching rule serves either a fixed variation or a rollout.
{
"clauses": [
{ "attribute": "country", "operator": "in", "values": ["IT", "FR"] },
{ "attribute": "plan", "operator": "eq", "values": ["pro"] }
],
"serve": { "variation": 1 }
}
Operators
| Operator | Matches when |
|---|---|
| eq | Attribute equals the single value. |
| neq | Attribute is present and equals none of the values. |
| in / not_in | Membership in the value list (with presence rules for negatives). |
| starts_with, ends_with, contains | String comparisons against one or more values. |
| gt, gte, lt, lte | Numeric comparisons against a single value. |
A type mismatch does not match and is not an error. A string compared with gt simply fails the clause. Regex and semantic-version operators are not in v1.
Targets
Targets pin specific people (or segments) to a variation before rules run. They are the place for testers, allow-lists, and shared audiences.
A value for one variation index is either a list of subjects, or an object with both lists:
{
"1": {
"subjects": ["alice"],
"segments": ["beta-testers"]
}
}
Inline subjects and cited segments are a union for that index. Walk indexes in stored order; the first match wins with reason target_match.
- Inline subject lists on one flag must not share a subject.
- One segment key must not be cited by two indexes of the same flag.
- A variation may cite at most 20 segments.
- A missing segment is rejected when targeting is saved.
Editing a segment later can put one subject under two indexes across the union. Evaluation still uses the first matching index.
Rollouts and bucketing
A rollout splits the audience by weight. Weights are integers that sum to 100000 (one unit is 0.001%). A 10% / 90% split is 10000 and 90000.
material = flag_key + ":" + salt + ":" + subject digest = SHA-256(material) bucket = first 4 bytes as uint32 big-endian % 100000
Walk variations in stored order, accumulating weight. The first whose cumulative weight is greater than the bucket wins. The same inputs always land in the same bucket. Editing weights keeps the salt; replacing the salt is a deliberate reshuffle.
A rollout needs a subject. Without one, a rule that serves a rollout does not match, and walking continues.
Prerequisites
A flag can require another flag in the same environment to evaluate to a given variation before its own targets and rules run. If a prerequisite fails, this flag serves its off variation with reason prerequisite_failed.
{
"prerequisites": [
{ "flag_key": "checkout-enabled", "variation_index": 1 }
]
}
Writes reject cycles. Evaluation also guards depth so a racing pair of writes cannot recurse without bound.
Evaluation order
Inputs: the flag’s variations, the environment configuration, the context, and the environment’s segment membership. Output: a decision.
- Off. If the flag is disabled, return the off variation. Reason off.
- Prerequisites. Evaluate each required flag. On failure, return the off variation. Reason prerequisite_failed.
- Targets. If there is a subject, walk variation indexes. Match by inline subject or cited segment. Reason target_match.
- Rules. First rule whose clauses all match. Serve a variation or a rollout. Reason rule_match.
- Fallthrough. Variation or rollout. Reason fallthrough. Without a subject, a fallthrough rollout becomes the off variation.
An unknown flag on evaluate still returns 200: the caller’s default, reason flag_not_found. Management reads of a missing flag stay 404.
| Reason | When |
|---|---|
| off | Flag disabled. |
| prerequisite_failed | A prerequisite missed, or a cycle/depth guard tripped. |
| target_match | Subject pinned by name or by a cited segment. |
| rule_match | First matching rule served this variation. |
| fallthrough | No target or rule selected a variation. |
| flag_not_found | Evaluate asked for a key that is not in the project. |
Credentials and access
Every route except GET /health requires Authorization: Bearer <token>.
| Token | May do |
|---|---|
| Management token | Create and edit projects, environments, flags, segments, and targeting. May also evaluate (and then must name project and environment). |
| SDK token | Bound to one environment. Evaluate and read that environment’s evaluation config. Cannot mutate. |
Secrets are stored as hashes and shown in plaintext only once, when the token is created. The application should hold an SDK token, not the management token.
Concurrency and audit
Replacing targeting sends the version the client read. If the stored version has moved, the write is a conflict (409). The client reads again and retries. That avoids silent overwrites when two operators edit at once.
Successful mutations are audited: actor (credential id and kind, never the secret), action, and before/after documents. Segment changes use segment.create, segment.update, and segment.delete.
Worked example
Flag checkout-v2 in prod, variations false (0) and true (1), enabled.
- Off variation: 0
- Target: subject alice → variation 1
- Optional: segment beta-testers also cited on variation 1
- Rule: country in ["IT", "FR"] serves a 10% / 90% rollout with salt s1
- Fallthrough: variation 0
| Context | Decision | Reason |
|---|---|---|
| Flag disabled | false | off |
| alice, any country | true | target_match |
| Subject in beta-testers | true | target_match |
| bob, country IT | bucket of checkout-v2:s1:bob | rule_match |
| bob, country US | false | fallthrough |
| Country IT, no subject | false | fallthrough (rollout needs a subject) |
Step through the same story with curl on the example page, or try the interactive evaluator on the home page.