Core concepts
How FeatureToggle models features, environments, and resolution.
Hierarchy
| Entity | What it is |
|---|---|
| Organization | Team workspace. Members are org-scoped. |
| Project | A set of features and up to three environments. |
| Environment | A deployment target: development, staging, or production. |
| Feature | Defined at project level with a default value. Activation is per-environment. |
| Override | An optional per-environment value that differs from the project default (usesDefault: false). |
| API key | Read-only credential scoped to one environment. |
Environments
Each project has up to three fixed roles:
| Role | When available | Key prefix | API scope |
|---|---|---|---|
development | Seeded on project create | ft_test_ | Loopback only (localhost, 127.0.0.1, ::1) |
staging | Add staging in the project sidebar (optional, once) | ft_live_ | Any deployed origin |
production | Add production in the project sidebar (optional, once) | ft_live_ | Any deployed origin |
developmentis permanent. You cannot rename or delete it.stagingandproductionare optional add-ons (at most one each); require a paid plan to add.- Both
stagingandproductionare production-tier from an API perspective; the names are organizational.
Test keys return 403 from origins that are not loopback:
.localhostnames (e.g.myapp.local)- Private LAN IPs (RFC1918):
10.x.x.x,172.16.x.x-172.31.x.x,192.168.x.x
On the free plan, RFC1918 origins are always blocked. Phone-on-WiFi or homelab testing needs a paid plan plus an ft_live_ key in staging or production.
Features
Types
| Type | Default for new features | Example value |
|---|---|---|
boolean | false (disabled) | true / false |
string | empty string | "dark" |
number | 0 | 42 |
json | empty object | { "limit": 100 } |
Key rules
- Unique per project (case-sensitive).
- Immutable after create. Choose a stable name client code will use forever.
- Format: starts with a letter; then letters, digits,
_, or-; 1-64 characters.
Project default vs environment activation
Features are defined at project level with a defaultValue. Activation (whether the feature is on) is set per environment. Per-environment value overrides are optional (usesDefault: false).
Two levels in the dashboard:
- Feature definitions: define keys, types, and default values, and deprecate features you are retiring.
- Environment → Features: use the switch to activate, the edit icon to change the value, and the … menu for Turn off, Use default, or Clear.
Resolution
For each environment, FeatureToggle computes an effective value and effective enabled state:
- Off when there is no
FeatureValuerow orenabled: false. - On when
FeatureValue.enabled === true. - When on and
usesDefault: true, value is the live projectdefaultValue. - When on and
usesDefault: false, value is the stored override.
value and enabled are independent for all types (including boolean).
Public API behavior:
- Only enabled features are returned.
- Disabled features are omitted from bulk reads and return 404 on single-feature lookup.
- The SDK caches only enabled features.
isEnabled('missing')returnsfalse;getValue('missing')returnsundefined.
Feature lifecycle
Features are never individually deleted. Remove features by deleting the parent project (cascades everything).
| State | Dashboard | Public API / SDK |
|---|---|---|
| Enabled | Normal | Returned when enabled |
| Disabled | Shown (optional hide filter) | Excluded / 404 |
| Deprecated | Deprecated badge; overrides read-only | Returned when enabled, with "deprecated": true |
Disable and deprecate are independent controls. A feature can be disabled, deprecated, or both.
Deprecation
Mark a feature deprecated (Deprecate on Feature definitions) when you are migrating away but still need it to resolve during rollout.
- Optional Replacement feature (
inFavorOf) points to another feature in the same project. - Enabled deprecated features are included in default API responses.
- Pass
deprecated=falseonGET /v1/featuresto exclude them. - The SDK logs a one-time warning per deprecated key when accessed; includes the replacement when set.
Example API payload:
{
"key": "old-checkout",
"type": "boolean",
"value": true,
"enabled": true,
"deprecated": true,
"inFavorOf": "new-checkout"
}API keys
| Property | Detail |
|---|---|
| Scope | One environment in one project |
| Access | Read-only access to all enabled features for that environment |
| Storage | Only a hash is stored server-side; plaintext shown once at creation |
| Prefix | ft_test_ (development) or ft_live_ (staging/production) |
| Revocation | Sets inactive; SDK clears cache on 401 |
Create keys in Environment → API keys (Create key). Multiple Active keys per environment are allowed.
Version and freshness
Each environment has a monotonic version number that increments when feature data affecting that environment changes. The public API exposes this as an ETag on bulk reads.
When you toggle a feature:
- The environment version bumps.
- Connected SDKs receive an SSE
features-changedevent. - The SDK refreshes and picks up new values.
Details: Caching and syncs
What the client app cannot do via API
The public feature API is read-only. There is no HTTP API to:
- Create, update, or delete features
- Manage projects, environments, or keys
- Access Billing or organization profile
All management happens in the dashboard.