End-to-end guide to creating feature flags — choosing types, naming, setting defaults, and environment scoping
Creating Flags
This guide walks through everything you need to know to create feature flags in Flaggr. By the end you'll understand flag types, naming conventions, default values, environment scoping, and how creation interacts with the approval workflow.
Prerequisites
Before creating a flag you need:
- A project — the top-level tenant that groups services
- A service — the application or microservice the flag belongs to
- At least write permission on the project
If you're starting from scratch, see the Quick Start to create a project and service first.
Creating a Flag via the Dashboard
- Navigate to your project in the console
- Select the service the flag belongs to
- Click Create Flag in the flag dashboard toolbar
- Fill in the form fields described below
- Click Create
The flag is created in the currently selected environment (shown in the environment selector in the header). It starts disabled by default.
Creating a Flag via the API
curl -X POST https://flaggr.dev/api/flags \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"key": "new-checkout-flow",
"name": "New Checkout Flow",
"type": "boolean",
"defaultValue": false,
"enabled": false,
"serviceId": "svc-abc123",
"environment": "development",
"description": "Redesigned checkout experience with single-page layout"
}'Response: 201 Created with the full flag object.
Flag Fields
Required Fields
| Field | Type | Description |
|---|---|---|
key | string | Unique identifier. Must start with a letter, contain only a-z, A-Z, 0-9, -, _. Max 100 characters. |
name | string | Human-readable display name. Max 200 characters. |
type | enum | One of boolean, string, number, object. Cannot be changed after creation. |
defaultValue | varies | Must match the declared type. This is the value returned when no targeting rule matches. |
serviceId | string | The service this flag belongs to. |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
description | string | "" | Explain what the flag controls. Max 1000 characters. |
environment | string | "development" | Which environment to create the flag in. |
enabled | boolean | false | Whether the flag is active. Disabled flags always return defaultValue. |
isPublic | boolean | false | If true, the flag can be evaluated without authentication. |
tags | string[] | [] | Labels for organization. Max 20 tags, each max 50 characters. |
variants | array | [] | Multi-variant configuration. See Variants. |
targeting | array | [] | Targeting rules. Usually added after creation via the UI. |
overrides | array | [] | Per-user value overrides. Usually added after creation. |
Choosing a Flag Type
The flag type determines what values the flag can return. Choose based on the decision your code needs to make:
| Type | Use When | Default Value Example | Code Pattern |
|---|---|---|---|
boolean | On/off feature toggle | false | if (flag) { showFeature() } |
string | Choosing between named options | "control" | switch (flag) { case "v2": ... } |
number | Tuning a numeric parameter | 10 | const limit = flag |
object | Complex configuration | {"theme": "light"} | const config = flag |
Boolean flags
The most common type. Boolean flags have special toggle behavior — when you toggle a boolean flag in the UI, both enabled and defaultValue are updated together. This means:
- Toggle on →
enabled: true, defaultValue: true - Toggle off →
enabled: false, defaultValue: false
For non-boolean flags, toggling only changes the enabled field.
String flags
Use string flags when you need to switch between named alternatives. Common patterns:
- UI variants:
"control","treatment_a","treatment_b" - Algorithm selection:
"v1","v2","v3" - Theme selection:
"light","dark","system"
String flags work well with the variants system for A/B testing.
Number flags
Use number flags to tune parameters without code changes:
- Rate limits:
100requests per minute - Timeouts:
5000milliseconds - Batch sizes:
50items per page - Rollout thresholds:
25percent
Object flags
Use object flags when a single feature requires multiple configuration values:
{
"maxRetries": 3,
"timeoutMs": 5000,
"enableFallback": true,
"fallbackUrl": "https://backup.example.com"
}Object values must be valid JSON. They are stored and returned as-is.
Naming Conventions
Good flag keys are self-documenting. Follow these patterns:
Do
| Pattern | Example | Why |
|---|---|---|
| Feature name | new-checkout-flow | Describes what it controls |
| Component + behavior | sidebar-collapsed-default | Specific and scannable |
| Experiment name | pricing-page-v2 | Ties to a known initiative |
Don't
| Pattern | Example | Why Not |
|---|---|---|
| Generic | test, flag-1, new-thing | Impossible to understand later |
| Too long | enable-the-new-redesigned-checkout-flow-with-single-page-layout | Hard to type in code |
| Dates or tickets | JIRA-1234, 2026-03-feature | Breaks when the ticket moves or the date passes |
| Negatives | disable-old-flow | Double negatives when checking if (!flag) |
Key format rules
- Start with a letter (
a-zorA-Z) - Only letters, numbers, hyphens, and underscores
- Maximum 100 characters
- Case-sensitive:
my-flagandMy-Flagare different flags
Environment Scoping
Every flag is scoped to a specific environment. The same key can exist independently in development, staging, and production — they are completely separate records with separate values, targeting rules, and enabled states.
How environments work
Project: "my-app"
└─ Service: "web-frontend"
├─ development / new-checkout-flow → enabled: true, defaultValue: true
├─ staging / new-checkout-flow → enabled: true, defaultValue: false
└─ production / new-checkout-flow → enabled: false, defaultValue: false
Creating a flag in development does not automatically create it in staging or production. Each environment must be populated separately, either by:
- Creating manually in each environment
- Promoting from one environment to another via the API:
POST /api/projects/{id}/environments/development/promote
{
"targetEnvironment": "staging",
"serviceId": "svc-abc123",
"flagKeys": ["new-checkout-flow"]
}Default environments
Every project starts with three environments:
| Environment | Color | Typical Use |
|---|---|---|
development | Blue | Local development, feature work |
staging | Yellow | Pre-production testing, QA |
production | Red | Live traffic |
You can create custom environments (e.g., canary, preview) via the project settings.
Protected environments
Environments can be configured with requireApproval: true and protectedEnvironment: true. When this is set, any flag creation, update, or toggle in that environment returns HTTP 202 Accepted with a change request instead of applying the change immediately. The change must be approved before it takes effect.
Variants
Variants let a single flag return different values for different users, making them essential for A/B testing and multi-armed experiments.
Defining variants at creation
curl -X POST https://flaggr.dev/api/flags \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"key": "pricing-page-layout",
"name": "Pricing Page Layout",
"type": "string",
"defaultValue": "control",
"serviceId": "svc-abc123",
"environment": "production",
"variants": [
{ "name": "control", "value": "grid", "weight": 50 },
{ "name": "treatment", "value": "comparison-table", "weight": 50 }
]
}'How variant assignment works
When a flag has variants and a targetingKey is provided in the evaluation context:
- Flaggr computes a hash:
hash(flagKey + targetingKey) % 100 - The hash falls into one variant's weight bucket
- The same user always gets the same variant (sticky assignment)
Weights are relative — they don't need to sum to 100, but it's a best practice to make them sum exactly.
Variants vs targeting rules
- Variants assign users randomly by weight — use for experiments
- Targeting rules assign users by attributes — use for conditional rollouts
You can combine both: a targeting rule can return a specific variant name via the variant field.
What Happens After Creation
When a flag is created, Flaggr:
- Validates the request against the schema (type checking, key format, value/type consistency)
- Checks for duplicates — the same
key + serviceId + environmentcannot exist twice (HTTP 409) - Stores the flag in Firestore (production) or the local JSON file (development)
- Saves a version snapshot — the initial state is recorded for version history and rollback
- Logs an audit event — who created the flag, when, and with what configuration
- Invalidates caches — the service's flag list cache is cleared so the new flag appears immediately
- Publishes a real-time event — SSE clients subscribed to the service receive a
CREATEDevent
The flag starts disabled — it will return the defaultValue with reason DISABLED for all evaluations until you enable it.
Common Patterns
Feature gate (boolean)
The simplest pattern — hide a feature behind a boolean flag:
const showNewUI = await client.evaluateFlag("new-checkout-flow", {
targetingKey: user.id
});
if (showNewUI) {
renderNewCheckout();
} else {
renderOldCheckout();
}Configuration flag (object)
Ship tunable configuration without deploys:
const config = await client.evaluateFlag("search-config", {
targetingKey: "global"
});
const results = await search(query, {
maxResults: config.maxResults,
timeout: config.timeoutMs,
algorithm: config.algorithm
});Experiment flag (string with variants)
Run an A/B test with variant assignment:
const variant = await client.evaluateFlag("onboarding-flow", {
targetingKey: user.id
});
switch (variant) {
case "wizard": return <OnboardingWizard />;
case "checklist": return <OnboardingChecklist />;
default: return <OnboardingClassic />;
}Troubleshooting
"Flag with key already exists" (409)
The combination of key + serviceId + environment must be unique. Check if the flag already exists in the target environment. If you need the same flag in another environment, the existing one is a separate record — you can create it there independently.
"Type cannot be changed after creation"
Flag type is immutable. If you need to change a flag's type, create a new flag with the correct type and migrate your code to use the new key.
Default value doesn't match type
The server validates that defaultValue matches the declared type. For example, a number flag with defaultValue: "ten" will fail validation. For object flags, the value must be valid JSON.
Flag created but not visible
Check the environment selector in the UI header — the flag was created in whichever environment was selected at the time. Switch environments to find it.
Related
- Managing Flags — editing, toggling, targeting, and overrides
- Monitoring Flags — health scoring, impact analysis, and debugging
- Flag Lifecycle — planning through cleanup
- Targeting Rules — condition operators and rollout strategies
- Environments — environment configuration and promotion