Define an audience
Describe membership once using attributes your application supplies.
Segments → createFlagDash is a feature flag and remote configuration platform. Deploy safely, test in production, and roll out gradually without redeploying code.
Boolean flags with per-environment states and targeting rules.
Typed configuration — string, number, boolean, or JSON — that updates instantly across all your environments.
Separate flag states for dev, staging, and production with independent API keys.
FlagDash uses two types of credentials:
API requests authenticate via API keys in the Authorization header. The MCP server and CLI also support OAuth 2.1 browser-based login.
The MCP server supports OAuth 2.1
with PKCE for browser-based login.
MCP clients (Claude Code, Cursor, Windsurf) handle the flow automatically.
The CLI uses device authorization — run flagdash
and approve the login in your browser.
Create API Keys
(sk_) from the
API Keys
page — these are scoped to a specific project and environment with configurable permissions.
Create Personal Access Tokens
(pat_) from
Settings > Personal Access Tokens
— these are user-scoped with full account access.
Authorization: Bearer YOUR_API_KEY
An sk_
API Key is enough for most use cases.
All SDKs (including Go, Python, and Node.js) work with API keys
to read evaluated flag values, config values, and AI configs.
You only need a pat_
Personal Access Token for CLI login, management API operations, and account-wide automation.
| Credential | Prefix | Scope | Permissions | Use case |
|---|---|---|---|---|
| API Key | sk_ | Project + environment | Configurable scopes (flags:read, configs:write, etc.) | SDKs, frontend, backend services |
| Personal Access Token | pat_ | User-scoped, account-wide | Full access to all projects and resources | CLI, management API, automation |
https://your-flagdash-instance.com/api/v1
Credentials have per-minute rate limits:
| Credential | Rate Limit | Scope |
|---|---|---|
| sk_ | 1,000 req/min | Read/write per configured scopes |
| pat_ | 100 req/min | Full access to all resources |
If an account is suspended, flag and config reads keep working.
Your production applications are not affected — GET /api/v1/flags,
/configs
and /ai-configs
continue to return 200.
What is blocked is change. Writes to the
Management API return 403
with an account_suspended
error, and MCP write tools return the
same. GET
requests to the Management API still work, so
you can read your configuration while resolving the suspension.
{
"error": "account_suspended",
"message": "Your account is suspended after repeated failed payments. Update your payment method to restore full access. Your feature flags are still being served to your applications."
}
Suspension normally follows repeated failed payments; updating your payment method lifts it. Contact support if you believe an account was suspended in error.
Get up and running with FlagDash in under 5 minutes.
Sign in to the dashboard and create a new project. Default environments will be created automatically based on your plan.
Go to the Flags page and create your first flag. Choose a key (e.g. new-checkout) and type.
Go to API Keys and generate an API Key
(sk_) for your project and environment.
import { FlagDash } from '@flagdashio/sdk';
const client = FlagDash.init({
sdkKey: 'sk_...',
});
// Read a flag value
const enabled = await client.flag('new-checkout');
if (enabled) {
showNewCheckout();
}
import flagdash "github.com/flagdash/flagdash-go"
client, _ := flagdash.NewClient("sk_...")
enabled, _ := client.Flag(ctx, "new-checkout", nil, false)
if enabled.(bool) {
showNewCheckout()
}
from flagdash import FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
enabled = client.flag("new-checkout", default=False)
if enabled:
show_new_checkout()
# Evaluate all flags
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://your-app.com/api/v1/flags
# Evaluate a single flag
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://your-app.com/api/v1/flags/new-checkout
# Evaluate with user context (targeting + rollout)
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://your-app.com/api/v1/flags/new-checkout?user_id=usr_123&plan=pro"
Connect a real application to a safely disabled flag, then verify the evaluation before changing production behavior.
New dashboard users open Connect your application. If you leave early, use the Connect your app launcher in the dashboard sidebar. Progress belongs to your user, so invited teammates receive guidance appropriate to their role.
Run one real evaluation
The dashboard completes only after this flag is evaluated with the generated key.
The flag remains disabled while the SDK connection is verified.
flags:read scope.import { FlagDash } from '@flagdashio/sdk';
const client = FlagDash.init({ sdkKey: 'sk_...' });
const enabled = await client.flag('new-checkout');
console.log({ enabled });
The waiting state changes to Your release path is live
without a refresh.
The returned flag value is still false
because disabled evaluation runs before targeting,
variations, rollout, and the configured default. This proves connectivity without releasing the feature.
How FlagDash decides what value a feature flag returns. The order below is what makes targeting rules, rollouts, and A/B tests predictable.
When a flag is evaluated, FlagDash checks conditions in this exact order. The first match wins — once a condition is met, evaluation stops and returns that value.
Disabled check
If the flag is disabled (toggled OFF in the dashboard),
return the default_value immediately.
Nothing else is checked. Reason: disabled
Targeting rules
If any targeting rule matches the user context, return that rule's variation value.
Rules are checked in order — the first matching rule wins.
Reason: rule_match
A/B Variations
If variations are configured, select one using weighted consistent hashing
(salted with the flag ID to decorrelate across tests).
Reason: variation
Percentage rollout
If the user falls within the rollout percentage, return the flag's active value.
Uses consistent hashing on user_id so the same user
always gets the same result.
Reason: rollout
Default
If nothing else matched, return the default_value.
Reason: default
Targeting rules let you return different values to different users based on context attributes. Each rule has an attribute, an operator, values to compare against, and a variation value to return when matched.
country) — this must match a context key you send from your app
eq)
US) — comma-separated for multiple values
Now when your app sends country=US as context,
the flag returns the rule's variation value instead of the default.
# Without context — returns default (0% rollout = false)
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
"https://your-app.com/api/v1/flags/my-flag"
# → {"key":"my-flag","value":false,"reason":"default"}
# With context — targeting rule matches country=US → true
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
"https://your-app.com/api/v1/flags/my-flag?country=US"
# → {"key":"my-flag","value":true,"reason":"rule_match"}
| Operator | Description | Example |
|---|---|---|
| eq | Equals (value in list) | country eq [US, CA] |
| neq | Not equals | plan neq [free] |
| contains | String contains substring | email contains [@company.com] |
| starts_with | String starts with | email starts_with [admin] |
| ends_with | String ends with | email ends_with [.edu] |
| in | Value in list | tier in [pro, enterprise] |
| not_in | Value not in list | tier not_in [free] |
| gt / lt / gte / lte | Numeric comparison | age gt [18] |
| regex | Regex pattern match | email regex [^.*@acme\.com$] |
Rollout percentage controls what fraction of users see the flag's active value.
It uses consistent hashing
on the user_id
context key, so:
user_id always gets the same result
Important:
Without a user_id
in the context, rollouts are non-deterministic
— a random value is generated each evaluation.
Always send a stable identifier for consistent results.
Targeting rules are checked before rollout. A common pattern is to set rollout to 0% and use targeting rules to enable the flag for specific users:
country eq US
→ true
country=US
see the flag as true
false (default value)Context is how you tell FlagDash about the current user. It's a key-value map sent as query parameters on the API call, or passed directly in SDKs.
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://your-app.com/api/v1/flags/checkout-v2?user_id=alice&country=US&plan=pro"
// Browser SDK (client-side)
const value = await client.flag('checkout-v2', {
user: { id: 'alice' },
country: 'US',
plan: 'pro',
});
// With full detail (value + reason + variation key)
const detail = await client.flagDetail('checkout-v2', {
user: { id: 'alice' },
country: 'US',
});
// → { key: 'checkout-v2', value: true, reason: 'rule_match', variationKey: null }
ctx := context.Background()
// Simple evaluation with context
value, err := client.Flag(ctx, "checkout-v2", flagdash.EvaluationContext{
"user_id": "alice",
"country": "US",
"plan": "pro",
}, false)
// Full detail (value + reason + variation key)
detail, err := client.FlagDetail(ctx, "checkout-v2", flagdash.EvaluationContext{
"user_id": "alice",
"country": "US",
})
// detail.Value = true, detail.Reason = "rule_match"
from flagdash import FlagDashClient, EvaluationContext
client = FlagDashClient(sdk_key="sk_...")
# Simple evaluation with context
value = client.flag(
"checkout-v2",
default=False,
context=EvaluationContext(user_id="alice", country="US", plan="pro"),
)
# Full detail (value + reason + variation key)
detail = client.flag_detail(
"checkout-v2",
context=EvaluationContext(user_id="alice", country="US"),
)
# detail.value = True, detail.reason = "rule_match"
| Context Key | Purpose | Used For |
|---|---|---|
| user_id | Stable user identifier | Consistent rollouts, A/B bucketing |
| User email | Targeting rules (e.g. internal users) | |
| country | Country code | Geo-targeting rules |
| plan | Subscription plan | Plan-based feature gating |
| any key | Custom attribute | Any key-value pair works for targeting |
The single flag endpoint returns the evaluated value plus the
reason
explaining why that value was chosen.
{
"key": "checkout-v2",
"value": true,
"reason": "rule_match"
}
{
"key": "checkout-v2",
"value": true,
"reason": "rollout"
}
{
"key": "checkout-v2",
"value": "variant-b",
"reason": "variation",
"variation_key": "b"
}
{
"key": "checkout-v2",
"value": false,
"reason": "default"
}
| Reason | Meaning |
|---|---|
| disabled | Flag is toggled OFF — always returns default value |
| rule_match | A targeting rule matched the user context |
| variation | Selected via A/B variation (weighted consistent hashing) |
| rollout | User fell within the rollout percentage |
| default | No rules, variations, or rollout matched |
Ship code independently from release decisions. Each environment owns its enabled state, value, targeting, and rollout.
Open Dashboard → Flags, select your project, open a flag, and choose its environment pill. Add an environment before configuring targeting or delivery. A flag without an environment shows only Lifecycle and Settings.
plan=enterprise
Choose who gets a feature with user lists and audience rules.
Per environmentCompare experiences with weighted, consistent assignments.
Per environmentSet a date and time for your next flag change.
Per environmentIncrease traffic in steps, with health checks along the way.
Per environmentAssign ownership, track cleanup, and retire finished flags.
Across environmentscheckout-v2
Manage the name and description. Review the key and type.
Across environmentsTargeting, Traffic split, Schedule, and Release affect the selected environment. Lifecycle and Settings belong to the flag across environments. Save edits with the panel footer before switching environments; Discard restores saved values. The environment enabled switch is a separate action. If approval is pending, ask an eligible colleague to review it before enabling.
Verify with the environment key your application actually uses. A disabled flag's configured value and an SDK fallback on a missing key or failed request are different cases; choose both deliberately. After deletion, test the application's missing-key fallback.
Pick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("checkout-v2", {}, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("checkout-v2", false, {});
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("checkout-v2", {}, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "checkout-v2", nil, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("checkout-v2", default=False, context=None)
curl -X GET "$FLAGDASH_URL/api/v1/flags/checkout-v2" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Open Flags, choose New flag, and set a stable key. Keys are application contracts, so use a name you can keep through the entire release.
Production
Default value: false
Start disabled in production. The fallback in code should preserve the behavior that existed before the flag.
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return (
<FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Checkout />
</FlagDashProvider>
);
}
function Checkout() {
const checkoutV2 = useFlag("checkout-v2", false);
return checkoutV2 ? <CheckoutV2 /> : <CheckoutV1 />;
}
Return different values using attributes your application supplies, such as plan, country, organization, or app version.
user_id
(the JavaScript SDK accepts user.id). Exclusion wins if an identifier is in both lists. Remove temporary tester identities after verification.
company_plan, equals, enterprise, and true. Separate rules are alternatives, evaluated in order. Missing attributes do not magically acquire application data.
{"user_id":"usr_123","company_plan":"enterprise"}
Pick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("priority-support", { user: { id: "usr_123" }, company_plan: "enterprise" }, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("priority-support", false, { user: { id: "usr_123" }, company_plan: "enterprise" });
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("priority-support", { user: { id: "usr_123" }, company_plan: "enterprise" }, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "priority-support", flagdash.EvaluationContext{"user_id": "usr_123", "company_plan": "enterprise"}, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("priority-support", default=False, context=EvaluationContext(user_id="usr_123", attributes={"company_plan": "enterprise"}))
curl -X GET "$FLAGDASH_URL/api/v1/flags/priority-support?user_id=usr_123&company_plan=enterprise" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Use stable, normalized names and values. Keep company attributes at the top level and the person identity under user.
export function buildFlagContext(session: Session) {
return {
user: { id: session.user.id, email: session.user.email },
company_id: session.company.id,
company_country: session.company.countryCode,
company_plan: session.company.plan,
};
}
Rules are evaluated from top to bottom. Put specific rules before broad rules.
import { FlagDashProvider, useFlag } from "@flagdashio/react";
import { buildFlagContext } from "./flag-context";
export function App({ session }: { session: Session }) {
return (
<FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<EnterpriseFeature session={session} />
</FlagDashProvider>
);
}
function EnterpriseFeature({ session }: { session: Session }) {
const context = buildFlagContext(session);
const enabled = useFlag("priority-support", false, context);
return enabled ? <PrioritySupport /> : <StandardSupport />;
}
// company_plan === "enterprise" matches the dashboard rule.
Define an audience once, then reference it from many flags without copying targeting rules.
company_country
equals
DE
Describe membership once using attributes your application supplies.
Segments → createExclusions win over inclusions, which win over segment conditions.
Segment membershipSelect the segment in a flag rule, then evaluate with matching context.
Flags → TargetingPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("german-checkout", { user: { id: "usr_123" }, company_country: "DE" }, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("german-checkout", false, { user: { id: "usr_123" }, company_country: "DE" });
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("german-checkout", { user: { id: "usr_123" }, company_country: "DE" }, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "german-checkout", flagdash.EvaluationContext{"user_id": "usr_123", "company_country": "DE"}, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("german-checkout", default=False, context=EvaluationContext(user_id="usr_123", attributes={"company_country": "DE"}))
curl -X GET "$FLAGDASH_URL/api/v1/manage/segments/german-companies?project_id=$PROJECT_ID" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Open Segments, create german-companies, and add the condition below. Segment conditions are ORed, so a context matching any condition is included.
Explicit exclusions win over inclusions. Explicit inclusions win over conditions. Segment rules cannot reference another segment.
In the flag's Targeting section, add is in segment, choose German companies, and serve the enabled value.
This is the same targeting-rule component used in the flag editor, rendered read-only for the guide.
import { FlagDashProvider, useFlagDetail } from "@flagdashio/react";
export function App({ session }: { session: Session }) {
return (
<FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<GermanExperience session={session} />
</FlagDashProvider>
);
}
function GermanExperience({ session }: { session: Session }) {
const result = useFlagDetail("german-checkout", false, {
user: { id: session.user.id },
company_id: session.company.id,
company_country: session.company.countryCode, // "DE"
});
return result.value ? <GermanCheckout /> : <DefaultCheckout />;
}
Use weighted variations when you need lightweight value allocation, or a rollout percentage when you only need controlled exposure. Use the experimentation platform when you need exposure tracking, metrics, and a decision.
checkout-layout
and open Flags → checkout-layout → Development → Traffic split.
control, Display Name to Control, Return Value to control, and Traffic % to 90.
candidate, return value compact, and traffic 10. Identifiers label assignments; Return Value is what your application renders.
The actual dashboard component, shown as a non-interactive example. Changes here are not saved.
Remove All confirms removal of the traffic split and returns the flag to simple evaluation. Test its ordinary rollout and default before using this in production. For a simple percentage release, leave Traffic split empty and set Targeting → Rollout Percentage. Variations are evaluated before that percentage, so combining a 10% rollout with a traffic split does not limit the split to 10% of users.
Do not infer percentage accuracy from a handful of identities. Verify repeatability for the same identity, then distribution over a representative population. Use Experimentation for exposure tracking, business metrics, and a recorded decision.
Pick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({ sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY });
const result = await client.flagDetail("checkout-layout", { user: { id: "usr_123" } }, "control");
import { FlagDashProvider, useFlagDetail } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const result = useFlagDetail("checkout-layout", "control", { user: { id: "usr_123" } });
return <pre>{JSON.stringify(result)}</pre>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
async function main() {
const result = await client.flagDetail("checkout-layout", { user: { id: "usr_123" } }, "control");
console.log(result);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
result, err := client.FlagDetail(context.Background(), "checkout-layout", flagdash.EvaluationContext{"user_id": "usr_123"})
if err != nil { log.Fatal(err) }
log.Printf("%+v", result)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
result = client.flag_detail("checkout-layout", context=EvaluationContext(user_id="usr_123"), default="control")
curl -X GET "$FLAGDASH_URL/api/v1/flags/checkout-layout?user_id=usr_123" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Use control and treatment values with weights that total 100. Read variationKey when recording outcomes.
Use one active value for a stable percentage of users. Everyone else receives the default.
import { FlagDashProvider, useFlagDetail } from "@flagdashio/react";
export function App({ session }: { session: Session }) {
return (
<FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<CheckoutExperiment session={session} />
</FlagDashProvider>
);
}
function CheckoutExperiment({ session }: { session: Session }) {
const { value, variationKey, isLoading } = useFlagDetail(
"checkout-layout",
"control",
{ user: { id: session.user.id } }
);
if (isLoading) return <CheckoutSkeleton />;
return <Checkout layout={value} variationKey={variationKey} />;
}
FlagDash connects deterministic assignment, exposure evidence, application events, and a reviewable decision in one workspace.
Current checkout
Compact checkout
checkout_completed
Define a hypothesis, layer, variants, and measurable outcomes.
Experiments → SetupUse one stable unit for assignment and metrics. Assignment records exposure.
Application SDKRead results and guardrails before recording a decision and cleaning up.
Experiments → ResultsPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({ sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY });
const result = await client.experiment("checkout-flow-v2", { user: { id: "usr_123" } });
import { FlagDashProvider, useExperiment } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const result = useExperiment("checkout-flow-v2", { user: { id: "usr_123" } });
return <pre>{JSON.stringify(result)}</pre>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
async function main() {
const result = await client.experiment("checkout-flow-v2", { user: { id: "usr_123" } });
console.log(result);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
result, err := client.Experiment(context.Background(), "checkout-flow-v2", flagdash.EvaluationContext{"user_id": "usr_123"})
if err != nil { log.Fatal(err) }
log.Printf("%+v", result)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
result = client.experiment("checkout-flow-v2", EvaluationContext(user_id="usr_123"))
curl -X GET "$FLAGDASH_URL/api/v1/experiments/checkout-flow-v2?user_id=usr_123" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Use an experiment when you need to compare multiple experiences against a measurable outcome. Use a rollout when you only need controlled exposure, and use a normal flag when the question is availability rather than causal impact.
Open Dashboard → Experiments, create a hypothesis, keep a control group, select a stable randomization unit, attach a reusable layer, map the variants to flags or remote config, and define primary, secondary, and guardrail metrics.
Automation can use the management API or the MCP tools list_experiments, get_experiment, create_experiment, and update_experiment. The CLI/TUI has an Experiments tab for creating a safe 50/50 draft, reviewing configuration, and recording the final decision. Experiments are available on every plan; MCP and CLI writes enforce the same project access and suspension rules as the dashboard.
Use Experiments → Timeline for the project-wide Month, Quarter, or Year schedule. Individual experiment workspaces contain Setup, Assignments, and Results; the shared timeline stays outside their slide-over panels. The Assignments tab shows the privacy-safe identity fingerprint, variant, environment, exposure count, and first/last exposure times. Raw user IDs are never persisted or recoverable from that fingerprint.
Decision timeline
What should change?
Verify both groups.
Track stable users.
Read lift and guardrails.
Ship, keep, or iterate.
Calling experiment
records an exposure automatically after the server resolves a variant. Do not send a second exposure event. Record business outcomes explicitly; the SDK queues them in a bounded buffer and sends batches of at most 100. Flush before a short-lived process exits.
import { FlagDash } from "@flagdashio/sdk";
const flagdash = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const context = { user: { id: user.id } };
const assignment = await flagdash.experiment("checkout-flow-v2", context);
flagdash.trackExperimentMetric({
experimentKey: "checkout-flow-v2",
eventName: "checkout_completed",
context,
properties: { checkoutVersion: 2 }
});
renderCheckout(assignment.parameters);
// Short-lived scripts only; browser clients flush automatically.
await flagdash.flushExperimentEvents();
import (
"context"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { return err }
ctx := context.Background()
context := flagdash.EvaluationContext{"user_id": user.ID}
assignment, err := client.Experiment(ctx, "checkout-flow-v2", context)
if err != nil { return err }
client.TrackExperimentMetric(
"checkout-flow-v2", "checkout_completed", context, nil,
map[string]any{"checkout_version": 2},
)
defer client.FlushExperimentEvents(ctx)
renderCheckout(assignment.Parameters)
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
context = EvaluationContext(user_id=user.id)
assignment = client.experiment("checkout-flow-v2", context)
client.track_experiment_metric(
"checkout-flow-v2",
"checkout_completed",
context,
properties={"checkout_version": 2},
)
render_checkout(assignment.parameters)
client.flush_experiment_events() # also called by client.close()
The server ignores any client-supplied variant and recomputes assignment from the stable identity. It stores a SHA-256 identity hash, never the raw user ID. Event IDs make retries idempotent, monthly partitions bound raw-event scans, and the dashboard reads indexed lifetime totals rather than millions of event rows. Daily rollups remain available for charts.
Use a lower environment, resolve the same user twice, record one outcome with a fixed eventId, retry it, then flush. The Results tab should show one unique conversion—not two—and the same variant after reload. The ingestion API rejects events older than 90 days, more than five minutes in the future, malformed names, batches above 100, missing scopes, and foreign experiments.
Session Replay combines browser playback with an interactive history of navigation, interactions, errors, feature decisions, experiment variants, mobile events, and backend traces.
Opt in
10%
30 days
Start in Development with conservative sampling and privacy controls.
Session Replay → environment••••••••BlockedInputs are masked. Block credentials and sensitive regions explicitly.
Application privacy selectorsConnect navigation, errors, and feature context to understand the experience.
Session Replay → recordingPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDashSessionReplay } from "@flagdashio/sdk/replay";
const replay = new FlagDashSessionReplay({
sdkKey: import.meta.env.VITE_FLAGDASH_REPLAY_KEY,
release: "checkout-2026-09",
sampleRate: 10,
});
if (await replay.start()) replay.addEvent("checkout_started");
import { useSessionReplay } from "@flagdashio/react/replay";
export function Checkout() {
const replay = useSessionReplay({
sdkKey: import.meta.env.VITE_FLAGDASH_REPLAY_KEY,
release: "checkout-2026-09",
enabled: true,
});
return <button onClick={() => replay?.addEvent("checkout_started")}>Pay</button>;
}
const { FlagDashBackendReplay } = require("@flagdashio/node");
const replay = new FlagDashBackendReplay({
sdkKey: process.env.FLAGDASH_REPLAY_KEY,
release: "checkout-2026-09",
});
if (await replay.start()) replay.event({ name: "checkout_started" });
await replay.stop();
package main
import (
"context"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func record(ctx context.Context) {
replay := flagdash.NewBackendReplay(os.Getenv("FLAGDASH_REPLAY_KEY")).WithRelease("checkout-2026-09")
if started, _ := replay.Start(ctx); started { replay.Event("checkout_started", "action", nil) }
defer replay.Stop(ctx)
}
import os
from flagdash import FlagDashBackendReplay
replay = FlagDashBackendReplay(
os.environ["FLAGDASH_REPLAY_KEY"],
release="checkout-2026-09",
)
if replay.start():
replay.event("checkout_started")
replay.stop()
curl -X POST "$FLAGDASH_URL/api/v1/replay-sessions/start" \
-H "Authorization: Bearer $FLAGDASH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"release\":\"checkout-2026-09\"}"
Use it when metrics tell you that something changed but you need to understand how a user reached the outcome. It is a standalone product: experiments add useful variant context, but a replay does not require an experiment.
Open Dashboard → Session Replay, choose an environment pill, and review the privacy settings before enabling capture. Capture starts disabled and initially suggests a 10% sample. Retention is fixed at 30 days.
Every typed value is masked, always. Page text is recorded so a replay is recognisable, with sensitive values redacted by shape.
A replay you cannot attribute is hard to act on, so the SDK accepts optional session context. Everything here is your choice to send — FlagDash infers none of it — and each value is sanitized before storage, with keys matching password, secret, token, card and friends redacted.
| Field | Shown as | Notes |
|---|---|---|
user.userId |
Identity | HMAC-hashed with a deployment secret on arrival and the raw id discarded. Two sessions from the same person correlate; the id itself is never stored. |
user.userLabel |
User | Stored and displayed exactly as sent, so keep it readable and non-sensitive. |
user.accountId |
Account | Tenant or workspace — how you tell a single-customer issue from a general one. |
user.plan |
Plan | Billing tier, for spotting behaviour specific to one plan. |
user.attributes |
Additional metadata | Any other keys worth reading later — role, cohort, enabled feature set. |
release |
Release | Application version, so a replay can be tied to a deploy. |
locale, timezone, viewport |
Locale / Timezone / Viewport |
Collected by the browser SDK. Pass your own values in metadata
to override them.
|
Create a project/environment API key with replays:write. That key can start sessions and upload chunks; it cannot list or read recordings. Playback uses the signed-in dashboard and short-lived private object URLs.
import { FlagDashSessionReplay } from "@flagdashio/sdk/replay";
const replay = new FlagDashSessionReplay({
sdkKey: "sk_...", // key includes replays:write
release: "checkout-2026-08",
sampleRate: 10,
blockedSelectors: [".payment-form", "[data-private]"],
// Every field below is optional. FlagDash never infers any of them, and
// anything you leave out shows as "n/a" on the replay rather than being
// hidden — so it is always clear whether a value was absent or unsent.
user: {
userId: user.id, // hashed on arrival; the raw value is discarded
userLabel: user.handle, // shown as-is, so keep it non-sensitive
accountId: user.tenantId,
plan: user.plan,
attributes: { role: user.role, signupCohort: "2026-Q3" }
}
});
await replay.start();
replay.addEvent("checkout_step", { step: "shipping" });
// Call await replay.stop() before a controlled shutdown.
React applications may use useSessionReplay
from @flagdashio/react/replay
with the same options. Vue applications use useSessionReplay
from @flagdashio/vue/replay. Both wrappers own the recorder for the component lifecycle.
React Native, Android, and Swift/iOS record explicit interaction timelines: screens, navigation, taps, breadcrumbs, exceptions, and custom events. They never capture pixels, input values, accessibility trees, or native view hierarchies. Ruby, PHP, Elixir, Lua, Rust, Go, Node, and Python provide backend recorders for explicit actions, request milestones, flag decisions, breadcrumbs, and exceptions.
Every SDK above is a wrapper over three endpoints. Reach for them directly only when
recording from a runtime FlagDash has no SDK for. They exist on both tiers with identical
shapes — /api/v1/…
for browser and mobile recorders, /api/v1/server/…
for trusted backends — and all three need an sk_
key carrying replays:write.
| Endpoint | Purpose |
|---|---|
POST /replay-sessions/start |
Opens a session. Returns its id and the environment's capture policy, which you
must honour on top of any local configuration. A 204
means the session was not sampled — record nothing, and do not treat it as an error.
|
POST /replay-sessions/:id/chunks/presign |
Reserves the next chunk and returns a presigned URL. PUT
the raw body there with the returned headers verbatim.
|
POST /replay-sessions/:id/complete |
Closes the session. One that is never completed is marked abandoned
by the daily cleanup, because a hard tab close never fires pagehide.
|
Full request and response bodies are in docs/api-reference.md
under Session Replay Ingestion.
A replay you cannot recognise is not worth watching, so ordinary page text — headings, labels, copy, button names — is recorded as written. Three separate rules keep that safe, and they apply in this order.
Hard blocks are never captured at all.
Password, payment and one-time-code inputs, canvas, audio, video, iframe, .ph-no-capture,
.rr-block
and [data-flagdash-replay-block]
regions produce no recorded content. No configuration re-enables them.
Every typed value is masked. Input values are replaced before they leave the page, with no option to turn that off — an input is where secrets are entered.
Sensitive values are redacted wherever they are rendered.
Text that has the shape of a credential is replaced even when it is page copy rather than a form field: sk_/pat_-style keys, JWTs, AWS and GitHub tokens, Slack tokens, email addresses, any 32-character-or-longer high-entropy run, and card numbers that pass a Luhn check. Surrounding words survive, so
Support reference owner@example.test was created
records as Support reference •••••••••••••••••••••• was created. Card-shaped digits are Luhn-checked so order numbers and quantities are not redacted.
This last rule matters because a dashboard commonly prints a live API key as ordinary text. Masking only form fields would upload it.
Enable capture only in Development, exercise a page containing fake credentials and form data, then sign in as an owner or manager and open the replay. Confirm blocked regions are blank, text and inputs are masked, URLs have no query or fragment, and the event history contains the expected navigation and FlagDash context.
Open an experiment's Replays tab and use the Baseline vs candidate workspace. Baseline defaults to the control variant and candidate defaults to the first non-control variant. Choose an environment pill, then compare recorded sessions, error rates, average duration, and the paths where the evidence is concentrated. Click a replay to inspect the exact event timeline and return to the comparison with the same experiment context.
Replay evidence explains a metric difference; experiment conversion remains the quantitative source of truth.
A lower conversion rate does not by itself prove that a candidate is harder to use. Compare retries, rage or dead clicks, failed requests, backtracking, time to completion, and abandonment between baseline and candidate. FlagDash should show the component signals and the supporting replay set so a statement such as “candidate users needed more retries on checkout” can be verified directly.
Replay objects and searchable metadata expire after 30 days. Owners and managers can export a browser replay as a video file, download a portable JSON replay archive, or permanently delete a replay sooner. Video export records the mounted player in real time and needs the viewer's screen-capture permission; the JSON archive is instant and needs none. Downloading is not a retention bypass. Configure the private Tigris bucket with snapshots, public access, and soft delete disabled, plus a matching 30-day object lifecycle.
Session Replay and Experiments are available on every plan. Replay capture is limited per calendar month: Free includes 10 sessions and 100 MiB, Premium includes 250 sessions and 2.5 GiB, and Premium Plus includes 1,000 sessions and 10 GiB. When either replay quota is exhausted, new sessions are refused without affecting flag or config reads; existing recordings remain available until their normal expiry.
Queue a one-time enable, disable, or rollout change for a specific environment and timezone.
This is a control-plane workflow. SDK examples read the resulting flag state; use the dashboard or REST to manage it.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("checkout-v2", { user: { id: "usr_123" } }, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("checkout-v2", false, { user: { id: "usr_123" } });
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("checkout-v2", { user: { id: "usr_123" } }, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "checkout-v2", flagdash.EvaluationContext{"user_id": "usr_123"}, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("checkout-v2", default=False, context=EvaluationContext(user_id="usr_123"))
curl -X GET "$FLAGDASH_URL/api/v1/manage/flags/checkout-v2/schedules?project_id=$PROJECT_ID&environment_id=$ENVIRONMENT_ID" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
The scheduled timestamp is stored unambiguously, while the selected timezone keeps the review experience clear.
Open the flag, select Schedule, create the change, then confirm it appears under Scheduled Changes. Schedules are environment-specific and execute once.
Increase traffic in observed steps and let external monitors pause or roll back the release when health degrades.
This is a control-plane workflow. SDK examples read the resulting flag state; use the dashboard or REST to manage it.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("checkout-v2", { user: { id: "usr_123" } }, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("checkout-v2", false, { user: { id: "usr_123" } });
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("checkout-v2", { user: { id: "usr_123" } }, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "checkout-v2", flagdash.EvaluationContext{"user_id": "usr_123"}, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("checkout-v2", default=False, context=EvaluationContext(user_id="usr_123"))
curl -X GET "$FLAGDASH_URL/api/v1/manage/flags/checkout-v2/releases?project_id=$PROJECT_ID&environment_id=$ENVIRONMENT_ID" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
HEALTH SIGNALS
error_rate, p95_latency
ON FAILURE
Roll back to starting traffic
A draft changes nothing until you start it. Missing required signal data pauses progression safely.
curl -X POST \
"$FLAGDASH_URL/api/v1/manage/flags/checkout-v2/releases/$RELEASE_ID/signals?project_id=$PROJECT_ID" \
-H "Authorization: Bearer $FLAGDASH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"metric":"error_rate","status":"passing","value":0.004,"source":"datadog"}'
Record why a flag exists, assign cleanup ownership, and report repository references before archiving temporary flags.
This is a control-plane workflow. SDK examples read the resulting flag state; use the dashboard or REST to manage it.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("checkout-v2", { user: { id: "usr_123" } }, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("checkout-v2", false, { user: { id: "usr_123" } });
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("checkout-v2", { user: { id: "usr_123" } }, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "checkout-v2", flagdash.EvaluationContext{"user_id": "usr_123"}, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("checkout-v2", default=False, context=EvaluationContext(user_id="usr_123"))
curl -X GET "$FLAGDASH_URL/api/v1/manage/flags/checkout-v2?project_id=$PROJECT_ID" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Release and experiment flags are temporary. Operational, permission, and permanent flags are long-lived.
flagdash code-refs scan \
--flag checkout-v2 \
--repository your-org/storefront
A scan reports locations; it never edits or deletes source code. Remove the flag branch from your application, deploy that removal, run a zero-reference scan for every repository, and only then archive the flag.
Change typed application settings independently in each environment without rebuilding or redeploying the application.
JSON
3000
20
Choose a stable key and a type that matches your application.
Remote Config → New Config1500 ms
2500 ms
3000 ms
Save and enable values independently for each deployment.
Select an environmentkey: checkout-settings
fallback: local settings
result: typed value
Use the environment's key and validate the returned shape.
Application · configs:readPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({ sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY });
const result = await client.config("checkout-settings", { timeout_ms: 2500 });
import { FlagDashProvider, useConfig } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const result = useConfig("checkout-settings", { timeout_ms: 2500 });
return <pre>{JSON.stringify(result)}</pre>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
async function main() {
const result = await client.config("checkout-settings", { timeout_ms: 2500 });
console.log(result);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
result, err := client.Config(context.Background(), "checkout-settings", map[string]any{"timeout_ms": 2500})
if err != nil { log.Fatal(err) }
log.Printf("%+v", result)
}
from flagdash import FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
result = client.config("checkout-settings", default={"timeout_ms": 2500})
curl -X GET "$FLAGDASH_URL/api/v1/configs/checkout-settings" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Use feature flags for decisions and remote config for values. Keep the JSON shape backward compatible while old clients are active.
To inspect a value created through MCP or the management API, open Remote Config, select the same environment used by your application, and click the config row. The JSON editor shows the inner application value. Storage and metadata responses use exactly one outer value envelope; SDK config() calls return the inner value. MCP and management writes automatically wrap bare objects. An object with only a value key is already an envelope: to store an application value key, nest it inside the outer value. Existing bare objects are migrated without dropping sibling fields. Verify the value in a development environment with your application's SDK before changing Production.
import { FlagDashProvider, useConfig } from "@flagdashio/react";
export function App() {
return (
<FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Checkout />
</FlagDashProvider>
);
}
const fallback = { timeout_ms: 2500, max_items: 10 };
function Checkout() {
const checkoutSettings = useConfig("checkout-settings", fallback);
return <CheckoutFlow settings={{
timeoutMs: checkoutSettings.timeout_ms ?? fallback.timeout_ms,
maxItems: checkoutSettings.max_items ?? fallback.max_items,
}} />;
}
Reuse a credential without copying its value. Approved rotations take effect on the next backend fetch or delivery.
/api_key••••••••Reuse an approved secret from the same environment.
Secrets · approved versionapi_key
service-credentials
/api_key
Use Insert secret in a supported string or JSON field.
Config, release, or connectionExplicit reads resolve the current approved version without caching it.
Backend only · no-storePick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDashServer } from "@flagdashio/node";
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
// Ordinary read: the reference comes back as written.
await client.config("payments"); // { apiKey: "{{secret:stripe-key}}" }
// Resolved read: the live approved value, fetched explicitly.
await client.resolveConfig("payments"); // { apiKey: "sk_live_51H..." }
client, _ := flagdash.New(flagdash.Config{SDKKey: os.Getenv("FLAGDASH_SDK_KEY")})
// Needs both configs:read and secrets:read on the key.
resolved, err := client.ResolveConfig(context.Background(), "payments")
if err != nil {
return fmt.Errorf("could not resolve payment credentials: %w", err)
}
from flagdash import FlagDashServerClient
client = FlagDashServerClient(sdk_key=os.environ["FLAGDASH_SDK_KEY"])
config = client.resolve_config("payments")
release = client.resolve_ai_release("assistant", user_id="usr_123")
Not available in the Browser & mobile SDK
Client SDKs never resolve a reference. An ordinary read returns the `{{secret:...}}` text exactly as stored, which is inert — resolving it would mean shipping the credential to every one of your users. Call your own backend instead.
curl -X POST "$FLAGDASH_URL/api/v1/server/configs/payments/resolve" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Open Dashboard → Secrets and select the project and environment. Create a string or JSON secret; if approval is required, a different authorized teammate must approve it first.
Example · Production · Datadog API key
The field stores a reference. Its badge opens details; Reveal displays the approved value for 30 seconds.
Typing a name suggests matching secrets in eligible text fields. Inside JSON, suggestions appear within a string, since that is the only place a reference is valid — Insert secret works with the caret anywhere and adds the quotes itself. JSON fields keep their line numbers, highlighting and validity readout. Select a JSON property by its path and type, or enter a JSON Pointer manually. A field resolves references as soon as it contains one — there is nothing to enable. A value saved before Secrets existed that already contained this text keeps its literal meaning and offers the change explicitly.
{"api_key": "{{secret:service-credentials#/api_key}}", "authorization": "Bearer {{secret:service-token}}"}
A token occupying the whole JSON string preserves the selected value's JSON type. Embedded tokens must select strings. JSON Pointer escapes a slash as ~1 and a tilde as ~0; array indexes start at zero. Tokens in object keys are rejected.
Create an environment-bound sk_ key with secrets:read and configs:read, or ai_configs:read for releases. The explicit methods below bypass config caches and never substitute defaults. Their result is sensitive: keep it on the backend and do not log it.
// client is your initialized FlagDashServer with an environment-bound sk_ key.
const config = await client.resolveConfig("backend-settings");
const settings = config.value as { value: { api_key: string } };
if (typeof settings.value.api_key !== "string") throw new Error("Credential unavailable");
const release = await client.resolveAiRelease("assistant", "user-123");
// Pass release.config.credentials to your backend provider client, never to the browser.
config = client.resolve_config("backend-settings")
assert isinstance(config["value"]["value"]["api_key"], str)
release = client.resolve_ai_release("assistant", user_id="user-123")
# The async server client exposes the same methods: await client.resolve_config(...).
config, err := client.ResolveConfig(ctx, "backend-settings")
if err != nil { return err }
_ = config // Use the returned Value on the backend; never log it.
release, err := client.ResolveAiRelease(ctx, "assistant", "user-123")
if err != nil { return err }
_ = release.Config
Ruby: client.resolve_config("backend-settings")
client.resolve_ai_release("assistant", user_id: "user-123")
PHP: $client->resolveConfig('backend-settings');
$client->resolveAiRelease('assistant', 'user-123');
Rust: client.resolve_config("backend-settings")?;
client.resolve_ai_release("assistant", "user-123")?;
Lua: client:resolve_config("backend-settings")
client:resolve_ai_release("assistant", "user-123")
Elixir: FlagDash.Client.resolve_config(client, "backend-settings")
FlagDash.Client.resolve_ai_release(client, "assistant", "user-123")
curl --fail-with-body -X POST \
https://flagdash.io/api/v1/server/configs/backend-settings/resolve \
-H "Authorization: Bearer $FLAGDASH_SDK_KEY" \
-H 'Content-Type: application/json' -d '{}'
# AI releases accept {"user_id":"user-123"} in the JSON body:
# POST /api/v1/server/ai-config-releases/assistant/resolve
Encrypted API tokens, database passwords and private JSON credentials, scoped per environment. Your backend reads them with one call. Browsers and mobile apps never can.
••••••••••••EncryptedCreate a string or JSON secret in the intended environment.
Secrets → project → environmentGive only your server the key and scope needed to read it.
API Keys · backend onlyApprove when required, verify the new credential, then retire the old one.
Secret version historyPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDashServer } from "@flagdashio/node";
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
// Never cached, never defaulted — a failure throws rather than
// handing you the wrong credential.
const { value } = await client.getSecret("stripe-secret-key");
client, _ := flagdash.New(flagdash.Config{SDKKey: os.Getenv("FLAGDASH_SDK_KEY")})
token, err := client.SecretString(context.Background(), "stripe-secret-key")
if err != nil {
return fmt.Errorf("could not load the Stripe key: %w", err)
}
from flagdash import FlagDashServerClient
client = FlagDashServerClient(sdk_key=os.environ["FLAGDASH_SDK_KEY"])
token = client.secret("stripe-secret-key")
# A project-scoped sk_ key with secrets:read — not your login token.
export FLAGDASH_SDK_KEY=sk_...
flagdash secrets fetch db-password api-token --format env >> "$GITHUB_ENV"
Not available in the Browser & mobile SDK
There is no secret method in the browser, React, React Native, Vue, Android, iOS or Flutter SDKs, and no client-tier endpoint for one to call. Anything a shipped client can fetch is a value you have given to every one of your users — call your own backend for secret-dependent operations and return a result, not the credential.
curl -X GET "$FLAGDASH_URL/api/v1/server/secrets/stripe-secret-key" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Reuse credentials with secret references in Remote Config, AI Releases and outbound connections.
Remote Config is designed to be read by browsers, cached at the edge, exported to a file, and streamed over SSE. Every one of those properties is exactly wrong for a credential, so Secrets is a separate resource with its own tables, scopes, permissions and read path — not a fifth config type.
| Remote Config | Secrets | |
|---|---|---|
| Client SDKs | Yes | Never — no client endpoint exists |
| Caching | ETS cache, SDK cache | Cache-Control: no-store, no SDK cache |
| SSE, webhooks, GitOps export | Yes | Never |
| Scopes | configs:read / configs:write |
secrets:read / secrets:write |
| At rest | Plaintext JSON | AES-256-GCM, fresh data key per version, wrapped by a master key |
| MCP | Full read and write | Refused on every path |
Go to Dashboard → Secrets, pick the environment, and choose a format:
A secret belongs to one project and one environment. Staging and production hold separate values, and ciphertext from one cannot be decrypted in the other — the account, project, environment, secret and version ids are all authenticated in the cipher itself.
Create an API key at Dashboard → API
Keys
with the secrets:read
scope. It is an sk_
key bound to one project and environment; a personal access token is account-wide and is
refused here on purpose.
import { FlagDashServer } from "@flagdashio/node"
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY })
// Returns the value plus its version. Never cached, never defaulted:
// a failure throws rather than handing you the wrong credential.
const { value, version_id } = await client.getSecret("stripe-secret-key")
const stripe = new Stripe(value)
client, _ := flagdash.New(flagdash.Config{SDKKey: os.Getenv("FLAGDASH_SDK_KEY")})
token, err := client.SecretString(ctx, "stripe-secret-key")
if err != nil {
return fmt.Errorf("could not load the Stripe key: %w", err)
}
// A JSON secret lands straight in your own struct.
var creds ServiceAccount
err = client.SecretJSON(ctx, "gcp-credentials", &creds)
from flagdash import FlagDashServerClient
client = FlagDashServerClient(sdk_key=os.environ["FLAGDASH_SDK_KEY"])
token = client.secret("stripe-secret-key")
credentials = client.secret("gcp-credentials") # a dict, for a json secret
# The async client has the same two methods:
# await client.get_secret(key) -> Secret
# await client.secret(key) -> the value
# Ruby
token = client.secret("stripe-secret-key")
# PHP
$token = $client->secret('stripe-secret-key');
# Rust
let token = client.secret_string("stripe-secret-key")?;
let creds: ServiceAccount = client.secret_json("gcp-credentials")?;
# Lua
local token = client:secret("stripe-secret-key")
# Elixir
{:ok, token} = FlagDash.Client.secret(client, "stripe-secret-key")
One call, on the server tier, with an sk_
key holding secrets:read:
curl https://flagdash.io/api/v1/server/secrets/stripe-secret-key \\
-H "Authorization: Bearer sk_your_key"
# 200 — Cache-Control: no-store
{
"secret": {
"key": "stripe-secret-key",
"format": "string",
"value": "sk_live_51H...",
"version_id": "secv_7Kd2...",
"created_at": "2026-09-05T11:04:22Z",
"created_by_id": "usr_9Fa1..."
}
}
# 403 secret_access_denied — the key lacks secrets:read
# 404 secret_not_found — no such secret in this environment
# 409 secret_awaiting_approval
# 503 secret_unavailable — encryption unreachable; nothing was substituted
Metadata and lifecycle live under /api/v1/manage/secrets
and take a PAT or session token with the Secrets permission. There is deliberately no
management action that returns a value — listing, history and lifecycle are metadata only,
and retrieval is always the separate, separately authorized call above.
Every endpoint, with its parameters, request bodies and error codes, is in the API reference under Secrets. It is generated from the router and the controllers, so it cannot drift from what the server actually accepts.
Secrets are not part of a GitOps document.
flagdash.yaml
is committed to your repository, so a credential must never appear in it. GitOps stays
flags and segments; CI reads secrets at run time instead, with a key scoped to exactly
that.
flagdash secrets list # metadata only, never values
flagdash secrets show stripe-secret-key
flagdash secrets create db-password --stdin
flagdash secrets set db-password --from-file ./new-password.txt
flagdash secrets versions stripe-secret-key
flagdash secrets restore stripe-secret-key secv_7Kd2...
flagdash secrets delete stripe-secret-key # recoverable for 7 days
flagdash secrets recover stripe-secret-key
# Values go in from a file or stdin, never as an argument — an argv string
# is visible in shell history and in every process listing on the machine.
# One value, straight out
flagdash secrets fetch stripe-secret-key --format raw
# Several, as shell-safe environment assignments
flagdash secrets fetch db-password api-token --format env >> "$GITHUB_ENV"
# A credential document, written to a private (0600) file
flagdash secrets fetch gcp-credentials --format json --out /tmp/gcp.json
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
env:
# A project- and environment-scoped key holding only `secrets:read`.
# It cannot change flags, read configs, or reach another environment —
# which is what makes it safe to hand to a deploy job.
FLAGDASH_SDK_KEY: ${{ secrets.FLAGDASH_SDK_KEY }}
FLAGDASH_PROJECT_ID: prj_a1b2c3
FLAGDASH_ENVIRONMENT_ID: env_d4e5f6
steps:
- uses: actions/checkout@v5
- name: Install the FlagDash CLI
run: npm install -g @flagdashio/cli
# Values are registered with ::add-mask:: before they are printed, so
# the runner redacts them from the log even if a later step echoes one.
- name: Load credentials into the job environment
run: flagdash secrets fetch db-password api-token --format env >> "$GITHUB_ENV"
# A credential document goes to a private (0600) file instead.
- name: Write the service-account credentials
run: flagdash secrets fetch gcp-credentials --format json --out "$RUNNER_TEMP/gcp.json"
# Piped, not interpolated: an argument is visible in the process
# listing for as long as the command runs.
- name: Log in to the registry
run: flagdash secrets fetch registry-password --format raw | docker login --password-stdin -u ci ghcr.io
# $DB_PASSWORD and $API_TOKEN came from $GITHUB_ENV, so they pass into
# the container like any other variable.
- name: Build and run
run: |
docker build -t app:${{ github.sha }} .
docker run --rm \
-e DB_PASSWORD -e API_TOKEN \
-v "$RUNNER_TEMP/gcp.json:/run/secrets/gcp.json:ro" \
-e GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/gcp.json \
app:${{ github.sha }} ./deploy.sh
deploy:
image: docker:27
stage: deploy
only: [main]
variables:
# Set FLAGDASH_SDK_KEY as a masked, protected CI/CD variable.
FLAGDASH_PROJECT_ID: "prj_a1b2c3"
FLAGDASH_ENVIRONMENT_ID: "env_d4e5f6"
before_script:
- apk add --no-cache nodejs npm
- npm install -g @flagdashio/cli
script:
# GitLab has no ::add-mask:: equivalent, so nothing is printed here: the
# values are sourced into the shell and the file is deleted at once.
- umask 077
- flagdash secrets fetch db-password api-token --format env > /tmp/flagdash.env
- set -a && . /tmp/flagdash.env && set +a && rm -f /tmp/flagdash.env
- flagdash secrets fetch gcp-credentials --format json --out /tmp/gcp.json
- flagdash secrets fetch registry-password --format raw | docker login --password-stdin -u ci "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- |
docker run --rm \
-e DB_PASSWORD -e API_TOKEN \
-v /tmp/gcp.json:/run/secrets/gcp.json:ro \
-e GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/gcp.json \
"$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" ./deploy.sh
after_script:
- rm -f /tmp/gcp.json /tmp/flagdash.env
secrets:read
or secrets:write. A key with every config scope still
gets a 403.
AI agents cannot read, create, replace, restore, approve, recover or delete a secret. Not with a permissive token, not through another tool, not on any path — the MCP server returns:
For security, secrets must be created and changed in the FlagDash portal.
Open Secrets → Create secret.
It will also refuse to store a credential as a plaintext Remote Config: a config request carrying encryption or protection intent is rejected with the same message rather than quietly downgraded. Remote Config itself is untouched — the standard JSON, String, Number and Boolean types work over MCP exactly as before.
The browser, React, React Native, Vue, Android, iOS and Flutter SDKs have no secret method, and there is no client-tier endpoint for one to call. Anything a shipped client can fetch is a value you have given to everyone who runs your app. Keep secret-dependent operations on your own backend and expose a result, not the credential.
Change product language without redeploying while keeping drafts, approvals, environments, and rollback explicit.
Pay now
Pagar agora
Draft
Group keys in a namespace and edit each supported locale.
Translations → namespaceReview the draft, publish an atomic release, and keep rollback history.
Environment release controlsSend the locale and variables, with safe local copy as a fallback.
Application · translations:readPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const label = await client.translation("checkout.items", {
locale: "pt-BR",
defaultValue: "{count} items",
variables: { count: 2 }
});
// "2 itens"
import { FlagDashProvider, useTranslation } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<CartCount />
</FlagDashProvider>;
}
function CartCount() {
const { t, isLoading } = useTranslation("pt-BR", "checkout");
return <span>{isLoading ? "Loading" : t("items", {
defaultValue: "{count} items",
variables: { count: 2 }
})}</span>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
async function main() {
const label = await client.translation("checkout.items", {
locale: "pt-BR",
defaultValue: "{count} items",
variables: { count: 2 }
});
console.log(label);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
label, err := client.Translation(context.Background(), "checkout.items", flagdash.TranslationOptions{
Locale: "pt-BR", DefaultValue: "{count} items",
Variables: map[string]any{"count": 2},
})
if err != nil { log.Fatal(err) }
log.Print(label)
}
from flagdash import FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
label = client.translation(
"checkout.items",
locale="pt-BR",
default="{count} items",
variables={"count": 2},
)
curl -X GET "$FLAGDASH_URL/api/v1/translations/pt-BR/checkout" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Open Translations, add the locales your application supports, and choose the project default fallback. Create a namespace such as checkout, choose an environment pill, then add or select a message. Search checks keys and translated values across every locale; large catalogs use the page's normal scroll so you can move naturally between messages and environment controls. After the full environment panel scrolls away, a compact rail keeps environment switching, editor mode, locales, pending state, and release controls available. The table header remains visible while reviewing long lists. The side panel keeps every locale together as vertical fields and highlights ICU variables such as {name}; the JSON editor remains available for bulk import.
pay_nowPay nowPagar agora
items{count, plural, one {# item} other {# items}}{count, plural, one {# item} other {# itens}}
Drafts remain private until the complete environment is published.
Use {name}
for a simple runtime value. For quantities, use the ICU plural form below. count
is the variable your application sends, one
handles exactly one item, other
is required for every other value, and #
prints the formatted count.
{count, plural, one {# item} other {# items}}
The FlagDash MCP server exposes list_translation_catalogs, create_translation_namespace, add_translation_locale, put_translation_draft, and publish_translation_release. An agent can inspect the existing keys, configure a locale, write a complete draft for one environment, and publish it through the same permission, validation, and audit boundaries as the dashboard.
For pt-BR, FlagDash checks pt-BR, then pt, then the project default locale. If the key is still missing or the catalog cannot be loaded, the SDK returns the fallback supplied by your application.
Release history keeps each atomic catalog snapshot. Expand a version before restoring it to compare it with the previous published version and review every added, changed, or removed namespace, key, and locale value.
The message table is the default editor. Switch to JSON for bulk paste or export; JSON may be flat or nested and nested keys are flattened with dots when stored. Removing a locale does not delete its release history; delete obsolete keys deliberately after application references and fallbacks have been cleaned up.
Version instructions, rules, agents, and progressively loaded skill bundles alongside your application environments.
SKILL.md
rollback.md
Development
Keep prompts, agents, and skill bundles scoped to an environment.
AI Configs → New Filename: safe-rollout
description: Plan a safe rollout
body: Release instructions
Explain when to use it so an agent can discover the right capability.
SKILL.md frontmatterDiscover descriptions first, then fetch one skill and its supporting files.
MCP discovery or explicit SDK readPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({ sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY });
const result = await client.aiConfig("agent.md", "# Safe fallback");
import { FlagDashProvider, useAiConfig } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const result = useAiConfig("agent.md", "# Safe fallback");
return <pre>{JSON.stringify(result)}</pre>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({ sdkKey: process.env.FLAGDASH_SDK_KEY });
async function main() {
const result = await client.aiConfig("agent.md");
console.log(result);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
result, err := client.AiConfig(context.Background(), "agent.md")
if err != nil { log.Fatal(err) }
log.Printf("%+v", result)
}
from flagdash import FlagDashClient
with FlagDashClient(sdk_key="sk_...") as client:
result = client.ai_config("agent.md")
curl -X GET "$FLAGDASH_URL/api/v1/ai-configs/agent.md" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Use an agent or rule file when one document should be fetched directly.
Use SKILL.md plus supporting text files when discovery and detailed loading should be separate.
---
name: safe-rollout
description: Use when planning or reviewing a feature-flag rollout.
---
# Safe rollout
Start from the old behavior, target internal users, then increase traffic.
Load `references/rollback.md` before changing production traffic.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const agent = await client.aiConfig("agent.md", "# Safe local fallback");
const instructions = agent?.content ?? "# Safe local fallback";
The description
is required because it is the only content an agent sees during discovery. Supporting file names become paths when materialized, so FlagDash rejects traversal paths and unsafe extensions.
Select, target, and gradually release model and prompt configurations while your application keeps calling its provider directly.
Your current model
support-v2
0.2
Select the model, prompt, and parameters for the known-good behavior.
AI Releases → New AI Releasesupport-v3
10%
90%
Start with small candidate traffic and a stable user identity.
Candidate rolloutFetch the evaluated configuration, then make the model request on your backend.
Evaluated HTTP endpointPick your runtime. Each example includes setup, credentials, and the feature call.
const release = await client.aiConfigRelease("support-agent", "usr_123");
const { client } = useFlagDash();
// In an effect or event handler:
const release = await client?.aiConfigRelease("support-agent", "usr_123");
const { client } = useFlagDash();
const release = await client.aiConfigRelease("support-agent", "usr_123");
const { client } = useFlagDash();
const release = await client?.aiConfigRelease("support-agent", "usr_123");
const release = await client.aiConfigRelease("support-agent", "usr_123");
release, err := client.AiConfigRelease(context.Background(), "support-agent", "usr_123")
if err != nil { return err }
release = client.ai_config_release("support-agent", user_id="usr_123")
# Async clients: release = await client.ai_config_release(...)
let release = client.ai_config_release("support-agent", "usr_123")?;
local release = client:ai_config_release("support-agent", "usr_123")
$release = $client->aiConfigRelease("support-agent", "usr_123");
release = client.ai_config_release("support-agent", user_id: "usr_123")
{:ok, release} = FlagDash.Client.ai_config_release(client, "support-agent", "usr_123")
val release = client.aiConfigRelease("support-agent", "usr_123")
final release = await client.aiConfigRelease("support-agent", userId: "usr_123");
let release = await client.aiConfigRelease("support-agent", userID: "usr_123")
curl -X GET "$FLAGDASH_URL/api/v1/ai-config-releases/support-agent?user_id=usr_123" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
gpt-4o-mini · support-v2
claude-sonnet · support-v3
The release chooses a configuration; your application still owns the model-provider request.
const configResponse = await fetch(
`${FLAGDASH_URL}/api/v1/ai-config-releases/support-agent?user_id=${session.user.id}`,
{ headers: { Authorization: `Bearer ${FLAGDASH_CLIENT_KEY}` } }
);
const release = (await configResponse.json()).ai_config;
const response = await openai.chat.completions.create({
model: release.config.model,
messages: [{ role: "system", content: release.config.system_prompt }],
temperature: release.config.temperature,
});
// release.variation_key === "candidate"
// release.reason === "rollout"
The response includes the selected configuration, release version, reason, and variation key. Use
variation_key
in your own request logs so a support conversation can always be traced to the configuration it received.
POST /api/v1/manage/ai-config-releases
?project_id=prj_storefront&environment_id=env_production
{
"key": "support-agent",
"baseline": {
"model": "gpt-4o-mini",
"system_prompt": "support-v2",
"temperature": 0.2
},
"variants": {
"variants": [{
"key": "candidate",
"weight": 100,
"config": {
"model": "claude-sonnet",
"system_prompt": "support-v3",
"temperature": 0.2
}
}]
},
"rollout_percentage": 10
}
The baseline
and candidate variant config
objects are intentionally provider-neutral. They can contain a model name, system prompt, parameters, tool policy, and skill references that your application understands.
The MCP server
exposes six release tools, so an assistant connected to your project can create a candidate, check who would receive it, and move the rollout without leaving the editor. They are named for the runtime decision rather than the files:
list_ai_configs
reads prompts, list_ai_releases
reads what is being served.
list_ai_releases project_id, environment_id
get_ai_release project_id, environment_id, key
create_ai_release project_id, environment_id, key, baseline,
[variants, rollout_percentage, status]
update_ai_release project_id, environment_id, key,
[baseline, variants, rollout_percentage, status]
set_ai_release_rollout project_id, environment_id, key, rollout_percentage,
[status]
evaluate_ai_release project_id, environment_id, key, [context]
evaluate_ai_release
is the one to reach for first. It returns the same decision your application would receive for a given
user_id
without writing anything, which turns "would this change affect me?" into a question an assistant can answer before the rollout moves.
You: what would user usr_8812 get from the support agent in production?
evaluate_ai_release
key: "support-agent"
context: { "user_id": "usr_8812" }
→ reason: "baseline", variation_key: null
config: { "model": "gpt-4o-mini", "system_prompt": "support-v2" }
You: take the candidate to 25%
set_ai_release_rollout
key: "support-agent"
rollout_percentage: 25
→ rollout_percentage: 25, version: 4
You: roll it back
set_ai_release_rollout
key: "support-agent"
rollout_percentage: 0
status: "paused"
Separate products with projects, release stages with environments, and application access with least-privilege credentials.
Choose the organization, project, and environment before configuring access.
Dashboard project selectorChoose read scopes for a browser; keep privileged credentials on the backend.
API Keys → createVerify the replacement in your application before revoking the old key.
Application deploymentThis is a control-plane workflow. Use the dashboard or REST; runtime SDKs observe the resulting flag or config state.
curl -X GET "$FLAGDASH_URL/api/v1/manage/environments?project_id=$PROJECT_ID" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
Project
One product or independently owned flag namespace.
Environment
Development, staging, production, or another release stage.
Credential
An sk_ key for one project and environment, or a user-scoped pat_ for management.
Browser SDKs call evaluated client-tier endpoints, so they never receive targeting rules. Keep management credentials server-side.
Understand evaluation activity, inspect configuration changes, and diagnose webhook or integration delivery without putting observability on the flag read path.
Storefront
Production
Around the incident
Match the environment and time window to the application's key.
AnalyticsFind who changed the affected resource and when it happened.
Audit LogsRead destination responses alongside the release's health decisions.
Webhooks / Integrations / ReleaseThis is a control-plane workflow. Use the dashboard or REST; runtime SDKs observe the resulting flag or config state.
No public REST endpoint
Audit logs are intentionally available in the authenticated dashboard, not through a public REST endpoint.
Open Dashboard → Analytics. Select 1 day, 3 days, 1 week, or 1 month (30 days). These are UTC calendar days including today. For an incident, choose Custom, enter From and Through dates, and click Apply range. Both dates are included; custom ranges must fall within the last 90 days.
Analytics · date range
Project: All accessible projects · UTC · All environments
Use the Project filter to narrow the view. All accessible projects includes only projects your membership can access. The sidebar project selector also narrows Analytics. Copy the page URL to share the filters; the recipient still needs their own access. Use Refresh data to fetch newly flushed activity.
Flag evaluation volume and How flags were evaluated show traffic and its recorded evaluation paths. Short recent ranges use hourly counters; longer or older ranges use daily totals. Other paths includes evaluations such as variations that do not have a dedicated counter. Default and disabled results do not mean a request failed.
MCP activity & errors shows recorded project-associated tool calls. The p95 latency means 95% of calls with a measured latency completed at or below that duration. Calls without a project are excluded, including account-wide operations and unassigned authentication failures.
Webhook delivery health counts attempts, including retries, rather than unique events. Success is successful attempts divided by all recorded attempts in the window. No attempts displays a dash, not a claim of perfect delivery.
Configuration activity groups successful customer audit entries by resource, including AI releases, translations, secrets, segments, and experiments. It requires audit-log access and a plan that includes audit logs. Only counts are shown; secret values and audit payloads are never charted. Open Audit Logs for the actor and change details.
Your configuration footprint shows current inventory, including archived resources where they remain stored. It follows the project filter, but is independent of the date filter. AI config files include supporting skill files. Use Experiments for exposure/conversion results and Session Replay for recorded sessions; these charts do not measure provider token spend or remote-config read volume.
Who received a value?
Use Analytics for evaluation volume, reasons, and trends across environments.
Who changed configuration?
Use Audit Logs for actor, action, resource, timestamp, and change context.
Did automation receive it?
Use Webhooks or Integrations delivery history for attempts and response failures.
Is a rollout healthy?
Use the Release timeline together with signals from your external monitor.
Understand when FlagDash reads Postgres, how changes propagate, and how to verify a rollout safely.
key: checkout-v2
fallback: false
on failure: old checkout
Choose a local value that preserves application behavior when reads fail.
Application SDKSaved changes invalidate server caches; SDK refresh behavior depends on runtime.
Cache and update pathVerify updates and offline behavior in a lower environment.
Before productionPick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
const value = await client.flag("checkout-v2", {}, false);
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY}>
<Feature />
</FlagDashProvider>;
}
function Feature() {
const value = useFlag("checkout-v2", false, {});
return <p>{value ? "Feature enabled" : "Original experience"}</p>;
}
const { FlagDashServer } = require("@flagdashio/node");
const client = FlagDashServer.init({
sdkKey: process.env.FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
});
async function main() {
const value = await client.flag("checkout-v2", {}, false);
console.log(value);
}
main().catch(console.error);
package main
import (
"context"
"log"
"os"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(os.Getenv("FLAGDASH_SDK_KEY"))
if err != nil { log.Fatal(err) }
value, err := client.Flag(context.Background(), "checkout-v2", nil, false)
if err != nil { log.Print(err) }
log.Print(value)
}
from flagdash import EvaluationContext, FlagDashClient
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-flagdash-instance.com",
) as client:
value = client.flag("checkout-v2", default=False, context=None)
curl -X GET "$FLAGDASH_URL/api/v1/flags/checkout-v2" \
-H "Authorization: Bearer $FLAGDASH_TOKEN"
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
baseUrl: "https://your-flagdash-instance.com",
realtime: true,
});
// Keep the pre-release behavior as the final argument.
const checkoutV2 = await client.flag("checkout-v2", {}, false);
client.on("error", (error) => console.error("FlagDash refresh failed", error));
window.addEventListener("pagehide", () => client.destroy());
SDK reads for flags, remote configs, segments, AI configs, AI releases, and experiment definitions are served from an in-memory cache after their first load. The database remains authoritative for mutations, permissions, audit history, analytics, and other transactional records.
Change a non-production environment first, read the affected key through the same SDK and API key your application uses, and confirm the new value arrives within one second. Keep an application default configured: during a cold cache plus database outage, the SDK fallback remains the final safety boundary.
curl "$FLAGDASH_URL/api/v1/flags/checkout-v2?user_id=cache-check" \
-H "Authorization: Bearer $FLAGDASH_SERVER_KEY"
Invite collaborators, constrain what they can change, review active sessions, and preserve a searchable history of sensitive operations.
Your team
Developer
Selected projects
Choose the organization and a role matching each person's responsibility.
Settings → TeamGrant the smallest set of capabilities needed for the work.
Member permissionsInspect audit history and revoke obsolete sessions, tokens, and memberships.
Settings and Audit LogsThis is a control-plane workflow. Use the dashboard or REST; runtime SDKs observe the resulting flag or config state.
No public REST endpoint
Team membership and account-role administration currently use authenticated dashboard workflows; no public management REST endpoint is shipped.
Everything in FlagDash belongs to an organization: your projects, flags, teammates and subscription. One is created for you when you sign up, and the first step of setup invites you to name it. Skipping that step is safe — the organization already exists, keeps the name we gave it, and can be renamed any time under Settings → Organization.
Use Settings → Profile for your own name, email address and password; Settings → Organization for the organization's name, billing contact and company details; and Settings → Team for membership, roles and feature permissions. Use Audit Logs to review the resulting administrative and product changes.
You have one FlagDash login, and it reaches every organization you belong to. Being invited somewhere does not create a second account or a second password: you accept the invitation and the organization appears in the switcher at the top of the sidebar, showing the role you hold in each.
Your plan follows the organization, not you. Each organization has its own subscription, its own limits and its own feature set, and the one you are currently in is the one that applies. If you own an organization on Free and are a developer in one on Premium Plus, you get Free's limits in the first and Premium Plus's in the second — switching between them switches both the plan and your role.
Grant the smallest set of capabilities needed for the person's current responsibility. Review access again when ownership changes.
Add email-and-password sign-in to an OAuth account, or replace an existing password without disconnecting Google or GitHub.
Set a password
Change password
Your verified email
Set a password for an OAuth account or change an existing one.
Settings → ProfileOnce••••••••Use the single-use link before it expires to choose your new password.
Verified email · one hourTest the new credential before unlinking an existing sign-in method.
Sign out → sign inThis is a control-plane workflow. Use the dashboard or REST; runtime SDKs observe the resulting flag or config state.
No public REST endpoint
Password, OAuth identity, sessions, billing, and cancellation are browser-authenticated account workflows and are not exposed through API keys.
Open Settings → Profile and find Password. Choose Change password, or Set a password if you originally signed up with Google or GitHub. FlagDash emails the signed-in person's verified address.
Password sign-in
Confirmation goes to the verified account email.
OAuth-only accounts see “Set a password”; existing password users see “Change password”.
Open the email within one hour, choose an 8–72 character password, and submit it once.
Sign out, then verify the new password on /admin. Google, GitHub, and magic-link
sign-in remain available, so test the new method before unlinking another one.
Enterprise billing changes follow the agreement negotiated for the account. Open Settings → Plan & Billing and choose Contact your account team. Describe the cancellation, renewal, invoicing, or term change you need; the form includes your account and current plan automatically. Enterprise never exposes the payment provider's generic self-service cancellation action.
Fourteen libraries over one HTTP API. Pick your language — every tile opens its
full reference. The API key decides which project and environment you read, so no
SDK takes an environment argument.
Ships with your app. The client tier never returns targeting rules, so the browser or device cannot see who else you are targeting — which is what makes an sk_ key with only read scopes safe to ship.
Runs on your own machines. The server tier adds targeting rules, variations and full metadata to every response, so its key must never be shipped to a client.
Flags, remote config and AI configs are in every SDK. These three are not — check here before designing around one.
| Capability | Available in | Not yet |
|---|---|---|
| Browser DOM replay | JavaScript, React, Vue | — |
| Explicit interaction replay | React Native, Android, Swift/iOS | — |
| Explicit backend replay | Node, Go, Python, Ruby, PHP, Elixir, Lua, Rust | — |
| Translations | JavaScript, React, Vue, React Native, Android, Node, Go, Python, Elixir, Lua, PHP, Ruby, Rust | Swift/iOS |
| Management CRUD | Node, Go, Python | everything else — use the HTTP API |
Mobile and backend recorders capture an explicit timeline only: they never intercept pixels, native view hierarchies, logs, requests or input values.
user_id, so
a context without one re-rolls on every call by design.
FLY_REGION,
AWS_REGION
and friends, so region-scoped targeting
works with no wiring.
Client-side SDK for browser environments. Provides flag evaluation, remote config, and AI config access with real-time SSE updates and polling fallback.
npm install @flagdashio/sdk
import { FlagDash } from '@flagdashio/sdk';
const client = FlagDash.init({
sdkKey: 'sk_...', // Your API key
baseUrl: 'https://your-app.com', // Your FlagDash instance URL
realtime: true, // SSE for instant updates (optional)
// refreshInterval: 30000, // Or use polling instead (optional)
});
// Boolean flag (uses cached value from initial GET /flags)
const showBanner = await client.flag('show-banner');
// Flag with user context — server evaluates targeting rules
// and percentage rollout via GET /flags/:key?user_id=...
const newUI = await client.flag('new-ui', {
user: { id: 'usr_123', email: 'john@acme.com', plan: 'premium' }
});
// All flags with context (single request)
const flags = await client.allFlags({
user: { id: 'usr_123' }, country: 'US'
});
// String flag with default value
const theme = await client.flag('app-theme', {}, 'light');
// Get a remote config value
const pricing = await client.config('pricing-tiers');
console.log(pricing); // { basic: 9.99, pro: 29.99 }
// Config with default value
const limits = await client.config('rate-limits', { default: 100 });
// Get a single AI config file
const agent = await client.aiConfig('agent.md');
console.log(agent.content); // "# Agent Instructions..."
console.log(agent.file_type); // "agent"
console.log(agent.folder); // null or "prompts"
// List all AI configs
const configs = await client.listAiConfigs();
// Filter by file type
const skills = await client.listAiConfigs({ fileType: 'skill' });
// Filter by folder
const prompts = await client.listAiConfigs({ folder: 'prompts' });
Get detailed evaluation information for a flag, including the reason it resolved to its value and the variation key (if applicable). This always calls the server for accurate evaluation data.
// Get detailed evaluation info (always calls the server)
const detail = await client.flagDetail('new-checkout', {
user: { id: 'usr_123', plan: 'premium' }
});
console.log(detail.key); // "new-checkout"
console.log(detail.value); // true
console.log(detail.reason); // "rule_match" | "variation" | "rollout" | "disabled" | "default"
console.log(detail.variationKey); // "variant-a" (null if not a variation)
// With a default value
const theme = await client.flagDetail('app-theme', {}, 'light');
// theme = { key: "app-theme", value: "dark", reason: "rollout", variationKey: null }
Retrieve all remote config values from cache in a single call, similar to allFlags().
// Get all config values from cache (single call)
const configs = await client.allConfigs();
console.log(configs);
// { "pricing-tiers": { basic: 9.99, pro: 29.99 }, "rate-limits": { default: 100 } }
Enable or disable SSE real-time updates at runtime without re-initializing the client.
// Check current SSE state
console.log(client.isRealtimeEnabled); // true or false
// Disable SSE at runtime (closes the connection)
client.disableRealtime();
// Re-enable SSE at runtime (opens a new connection)
client.enableRealtime();
// Listen for realtime state changes
client.on('realtime_changed', (enabled) => {
console.log('Realtime is now:', enabled ? 'on' : 'off');
});
// Listen for events
client.on('ready', () => console.log('FlagDash ready'));
client.on('flags_updated', (flags) => console.log('Flags changed:', flags));
client.on('config_updated', () => console.log('Config changed'));
client.on('configs_updated', (configs) => console.log('Configs changed:', configs));
client.on('ai_config_updated', () => console.log('AI config changed'));
client.on('realtime_changed', (enabled) => console.log('Realtime:', enabled));
client.on('error', (err) => console.error('Error:', err));
// Clean up when done (closes SSE connection + stops polling)
client.destroy();
const greeting = await client.translation("common.welcome", {
locale: "de",
defaultValue: "Welcome",
variables: { name: "Ada" },
});
// Which locale answered, and why
const detail = await client.translationDetail("common.welcome", { locale: "de" });
detail.locale; detail.sourceLocale; detail.reason;
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
import { FlagDashSessionReplay } from "@flagdashio/sdk/replay";
const replay = new FlagDashSessionReplay({
sdkKey: "sk_...",
release: "checkout-2026-08",
sampleRate: 10,
blockedSelectors: [".payment-form", "[data-private]"],
user: { userId: user.id, plan: user.plan },
});
await replay.start();
replay.addEvent("checkout_step", { step: "shipping" });
await replay.stop();
The key needs the replays:write
scope. The recorder ships behind the @flagdashio/sdk/replay
subpath so pages that never record do not pay for it in their bundle.
Attribute keys that look sensitive (password, token, authorization, api_key, card) are redacted before
the event leaves the process.
React hooks and components for feature flags, remote config, and AI configs.
Built on top of @flagdashio/sdk with SSR-safe defaults.
npm install @flagdashio/react @flagdashio/sdk
import { FlagDashProvider } from '@flagdashio/react';
function App() {
return (
<FlagDashProvider
sdkKey="sk_..."
baseUrl="https://your-app.com"
user={{ id: 'usr_123', plan: 'premium' }}
realtime // SSE for instant updates (optional)
>
<MyApp />
</FlagDashProvider>
);
}
import { useFlag } from '@flagdashio/react';
function CheckoutButton() {
const showNewCheckout = useFlag('new-checkout', false);
if (!showNewCheckout) return <OldCheckout />;
return <NewCheckout />;
}
Get detailed evaluation information for a flag, including the reason it resolved to its value and the variation key. Always calls the server for accurate data.
import { useFlagDetail } from '@flagdashio/react';
function FeatureBanner() {
const {
value,
reason,
variationKey,
isLoading,
} = useFlagDetail('new-checkout', false, { user_id: 'usr_123' });
if (isLoading) return <Skeleton />;
// reason: "disabled" | "rule_match" | "variation" | "rollout" | "default"
return (
<div>
<p>Value: {String(value)}</p>
<p>Reason: {reason}</p>
{variationKey && <p>Variation: {variationKey}</p>}
</div>
);
}
The useConfig
and useConfigWithLoading
hooks are reactive to SSE updates. When realtime is enabled,
config values automatically refresh when changes are pushed from the server.
import { useConfig, useFlagDash } from '@flagdashio/react';
function PricingDisplay() {
const pricing = useConfig('pricing-tiers', { basic: 9.99 });
const { isReady } = useFlagDash();
if (!isReady) return <Skeleton />;
return <span>${pricing.basic}/mo</span>;
}
import { useAiConfig, useAiConfigs } from '@flagdashio/react';
function AgentInstructions() {
const { content, fileType, isLoading } = useAiConfig('agent.md');
if (isLoading) return <Spinner />;
return <pre>{content}</pre>;
}
function SkillList() {
const { configs, isLoading } = useAiConfigs({ fileType: 'skill' });
if (isLoading) return <Spinner />;
return (
<ul>
{configs.map(c => <li key={c.file_name}>{c.file_name}</li>)}
</ul>
);
}
| Hook | Returns | Description |
|---|---|---|
| useFlag(key, default) | T | Evaluate a feature flag reactively |
| useFlagWithLoading(key, default) | { value, isLoading } | Flag evaluation with loading state |
| useFlagDetail(key, default, ctx?) | { value, reason, variationKey, isLoading } | Flag evaluation with reason and variation details |
| useConfig(key, default) | T | Get a remote config value reactively (auto-refreshes via SSE) |
| useConfigWithLoading(key, default) | { value, isLoading } | Config value with loading state (auto-refreshes via SSE) |
| useAiConfig(fileName) | { content, fileName, fileType, folder, isLoading } | Get an AI config file reactively |
| useAiConfigs(options?) | { configs, isLoading } | List AI config files with optional filters |
| useFlagDash() | { client, isReady } | Access the raw client and readiness state |
Catch errors from FlagDash hooks with FlagDashErrorBoundary:
import { FlagDashErrorBoundary } from '@flagdashio/react';
// With static fallback
<FlagDashErrorBoundary fallback={<div>Failed to load flags</div>}>
<MyComponent />
</FlagDashErrorBoundary>
// With render function for retry
<FlagDashErrorBoundary
fallback={(error, reset) => (
<div>
<p>Error: {error.message}</p>
<button onClick={reset}>Retry</button>
</div>
)}
onError={(error) => console.error('FlagDash error:', error)}
>
<MyComponent />
</FlagDashErrorBoundary>
import { useTranslation } from "@flagdashio/react";
const { t, isLoading } = useTranslation("de", "common");
// `t` takes the key within the namespace, so `common.welcome` is t("welcome")
t("welcome", { defaultValue: "Welcome", variables: { name: "Ada" } });
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
import { useSessionReplay } from "@flagdashio/react/replay";
const replay = useSessionReplay({
sdkKey: "sk_...",
release: "checkout-2026-08",
sampleRate: 10,
blockedSelectors: [".payment-form", "[data-private]"],
enabled: user.hasConsented,
});
replay?.addEvent("checkout_step", { step: "shipping" });
The key needs the replays:write
scope. useSessionReplay
lives behind the @flagdashio/react/replay
subpath. It starts on mount and stops on unmount.
Attribute keys that look sensitive (password, token, authorization, api_key, card) are redacted before
the event leaves the process.
Vue 3 plugin and reactive composables for client-side feature flags, remote config, AI configs, translations, experiments, and realtime updates.
Use an environment-scoped sk_ key with only the read
scopes the application needs. Never place a pat_ token
or write-capable key in a browser bundle.
npm install @flagdashio/vue
Source is published in flagdash/flagdash-js
under packages/vue. Use npm for installation because Git dependencies do not
install an individual workspace subdirectory as a standalone package.
VITE_FLAGDASH_SDK_KEY; do not commit the local environment file.
import { createApp } from "vue";
import { createFlagDash } from "@flagdashio/vue";
import App from "./App.vue";
const flagdash = createFlagDash({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
realtime: false,
});
createApp(App).use(flagdash).mount("#app");
<script setup lang="ts">
import { useFlag } from "@flagdashio/vue";
const checkout = useFlag("checkout-v2", false, {
user: { id: "user_123", plan: "pro" },
});
</script>
<template>
<p v-if="checkout.isLoading.value">Loading flags...</p>
<CheckoutV2 v-else-if="checkout.value.value" />
<CheckoutV1 v-else />
</template>
Start with realtime disabled and verify the displayed value against Test evaluation in the dashboard. Enable realtime after the initial values and targeting context are correct. The composable returns the supplied default while loading or when no value can be fetched.
<script setup lang="ts">
import { useTranslation } from "@flagdashio/vue";
const greeting = useTranslation("welcome", {
locale: "de",
defaultValue: "Welcome",
variables: { name: "Ada" },
});
</script>
<template>
<p>{{ greeting.value.value }}</p>
</template>
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
<script setup lang="ts">
import { useSessionReplay } from "@flagdashio/vue/replay";
const replay = useSessionReplay({
sdkKey: import.meta.env.VITE_FLAGDASH_REPLAY_KEY,
release: "checkout-2026-08",
sampleRate: 10,
blockedSelectors: [".payment-form", "[data-private]"],
enabled: user.hasConsented,
});
function onPay() {
replay.value?.addEvent("checkout_step", { step: "payment" });
}
</script>
useSessionReplay
lives behind the @flagdashio/vue/replay
subpath so applications that never record do not pull the recorder into their bundle. It
starts on mount and stops on unmount. The key needs the replays:write
scope, and everything under a blockedSelectors
match is excluded before it is serialised.
Keep one plugin instance for the application. Call flagdash.destroy() when
unmounting an embedded Vue application so polling, SSE, and experiment flush resources are
released. Realtime updates refresh subscribed composables but never grant mutation access.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
npm install @flagdashio/react-native
Immutable GitHub install: flagdash/flagdash-js/tree/main/packages/react-native
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
const client = new FlagDashClient({ sdkKey: process.env.EXPO_PUBLIC_FLAGDASH_SDK_KEY! })
const enabled = await client.flag('checkout-v2', false, { user: { id: 'alice' } })
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
const greeting = await client.translation("common.welcome", { locale: "de", defaultValue: "Welcome", variables: { name: "Ada" } });
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
import { ReactNativeInteractionReplay } from "@flagdashio/react-native";
const replay = new ReactNativeInteractionReplay({ sdkKey: REPLAY_KEY, release: "2026.08" });
if (await replay.start()) {
replay.screen("Checkout");
replay.interaction({ name: "pay_pressed", category: "action", properties: { items: 2 } });
}
await replay.stop();
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: npm / NPM_TOKEN. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
implementation("io.flagdash:flagdash-android:0.1.0")
Immutable GitHub install: github.com/flagdash/flagdash-android (JitPack supported)
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
val client = FlagDashClient(BuildConfig.FLAGDASH_SDK_KEY)
val enabled = client.flag("checkout-v2", false, EvaluationContext(userId = "alice"))
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
val greeting = client.translation("common.welcome", locale = "de", defaultValue = "Welcome", variables = mapOf("name" to "Ada"))
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
val replay = InteractionReplay(BuildConfig.FLAGDASH_REPLAY_KEY, release = "2026.08")
.observeLifecycle(this)
if (replay.start()) {
replay.screen("Checkout")
replay.interaction("pay_pressed", properties = mapOf("items" to 2))
}
replay.close()
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: Maven Central Portal token and GPG signing key. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
https://github.com/flagdash/flagdash-swift (Swift Package Manager)
Immutable GitHub install: github.com/flagdash/flagdash-swift tag v0.1.0
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
let client = FlagDashClient(sdkKey: sdkKey)
let enabled = await client.flag("checkout-v2", default: .bool(false), context: .init(userID: "alice"))
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
let replay = InteractionReplay(sdkKey: replayKey, release: "2026.08")
if await replay.start() {
await replay.screen("Checkout")
await replay.interaction("pay_pressed", properties: ["items": .int(2)])
}
await replay.stop()
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: No registry credential; signed GitHub tag only. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Server-side SDK with TTL caching, full flag details, and a management client for CRUD operations.
Works with sk_
API Keys.
A key with read scopes is sufficient for reading flag values, configs, and AI configs.
Use a key with write scopes for CRUD operations, or a pat_
Personal Access Token for account-wide management.
npm install @flagdashio/node
const { FlagDashServer } = require('@flagdashio/node');
const client = FlagDashServer.init({
sdkKey: 'sk_...', // Your API key
baseUrl: 'https://your-app.com', // Your FlagDash instance URL
cacheTTL: 60000, // Cache for 60s (default)
});
// Evaluate a flag with user context
app.get('/checkout', async (req, res) => {
const newCheckout = await client.flag('new-checkout', {
user: { id: req.userId, plan: req.userPlan }
});
if (newCheckout) {
return res.render('checkout-v2');
}
return res.render('checkout');
});
// Get all evaluated flags for a user
const flags = await client.allFlags({
user: { id: req.userId }
});
// flags = { "new-checkout": true, "dark-mode": false }
// Get a flag with full rule details
const flagDetails = await client.getFlag('new-checkout');
console.log(flagDetails.rollout_percentage);
Get detailed evaluation information including the reason a flag resolved to its value and the variation key. Always calls the server for accurate evaluation data.
// Get detailed evaluation info (always calls the server)
const detail = await client.flagDetail('new-checkout', {
user: { id: req.userId, plan: req.userPlan }
});
console.log(detail.key); // "new-checkout"
console.log(detail.value); // true
console.log(detail.reason); // "rule_match" | "variation" | "rollout" | "disabled" | "default"
console.log(detail.variationKey); // "variant-a" (null if not a variation)
// Use for analytics or debugging
analytics.track('flag_evaluated', {
flag: detail.key,
value: detail.value,
reason: detail.reason,
});
// Get a single AI config file (with caching)
const agent = await client.aiConfig('agent.md');
console.log(agent.content); // Markdown content
console.log(agent.file_type); // "agent"
// List all AI config files
const configs = await client.listAiConfigs();
const skills = configs.filter(c => c.file_type === 'skill');
The server SDKs send a region
with every evaluation, so targeting rules and segments can roll a feature out
region by region without threading it through each call. The region is read
from the environment automatically.
import { FlagDashServerClient } from '@flagdashio/node';
// region is auto-detected from the environment, checked in this order:
// FLAGDASH_REGION, FLY_REGION, AWS_REGION, AWS_DEFAULT_REGION,
// VERCEL_REGION, GOOGLE_CLOUD_REGION, RAILWAY_REPLICA_REGION, RENDER_REGION
const client = new FlagDashServerClient({ sdkKey: 'sk_...' });
console.log(client.region); // 'eu-west-1'
// Every evaluation now carries region=eu-west-1, so a rule like
// "region eq eu-west-1, serve to 10%" applies without any extra wiring.
const enabled = await client.flag('new-checkout', { user: { id: 'alice' } }, false);
// Set it explicitly (overrides detection)
const explicit = new FlagDashServerClient({ sdkKey: 'sk_...', region: 'us-east-1' });
// Or turn detection off
const none = new FlagDashServerClient({ sdkKey: 'sk_...', autoDetectRegion: false });
The management client provides full CRUD operations for flags, configs, AI configs, and webhooks.
Requires a pat_
Personal Access Token or an API key with write scopes.
const { FlagDashManagement } = require('@flagdashio/node');
const mgmt = FlagDashManagement.init({
apiKey: 'pat_...',
baseUrl: 'https://your-app.com',
});
// Create a flag
const flag = await mgmt.createFlag({
project_id: 'prj_xxx',
key: 'new-feature',
name: 'New Feature',
flag_type: 'boolean',
});
// Toggle a flag in an environment
await mgmt.toggleFlag('new-feature', 'prj_xxx', 'env_xxx');
// Set rollout percentage
await mgmt.updateFlagRollout('new-feature', 'prj_xxx', 'env_xxx', 25);
// Create an AI config file
await mgmt.createAiConfig({
project_id: 'prj_xxx',
environment_id: 'env_xxx',
file_name: 'agent.md',
file_type: 'agent',
content: '# Agent Instructions\n\n...',
});
const greeting = await client.translation("common.welcome", {
locale: "de",
defaultValue: "Welcome",
variables: { name: "Ada" },
});
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
import { FlagDashBackendReplay } from "@flagdashio/node";
const replay = new FlagDashBackendReplay({
sdkKey: process.env.FLAGDASH_REPLAY_KEY,
release: "2026.08",
});
if (await replay.start()) {
replay.event({ name: "checkout_started", attributes: { items: 2 } });
replay.breadcrumb("coupon applied");
}
// Correlate a browser recording with this backend timeline
replay.contextHeaders();
await replay.stop();
The key needs the replays:write
scope. Backend replay records an explicit
timeline — only the actions you name. Nothing captures request bodies, logs, or input values on its own.
Attribute keys that look sensitive (password, token, authorization, api_key, card) are redacted before
the event leaves the process.
Go client for FlagDash with thread-safe TTL caching and zero external dependencies.
Use an sk_
API Key for reading flag values, configs, and AI configs.
The SDK routes to the correct endpoints based on the key's configured scopes.
Use a pat_
Personal Access Token for management operations.
go get github.com/flagdash/flagdash-go
package main
import (
"context"
"fmt"
"log"
"time"
flagdash "github.com/flagdash/flagdash-go"
)
func main() {
client, err := flagdash.NewClient(
"sk_...",
flagdash.WithBaseURL("https://your-app.com"),
flagdash.WithCacheTTL(60 * time.Second),
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
enabled, err := client.Flag(ctx, "new-checkout", nil, false)
if err != nil {
log.Printf("flag error: %v", err)
}
fmt.Println("new-checkout:", enabled)
}
ctx := context.Background()
// Simple flag evaluation (cached)
enabled, _ := client.Flag(ctx, "dark-mode", nil, false)
// Flag with user context (not cached)
value, _ := client.Flag(ctx, "new-checkout",
flagdash.EvaluationContext{
"user_id": "usr_123",
"plan": "premium",
},
false,
)
// Get all evaluated flags
flags, _ := client.AllFlags(ctx, nil)
fmt.Println(flags["dark-mode"]) // true
// Get flag with full details (requires read scopes)
flag, _ := client.GetFlag(ctx, "new-checkout")
fmt.Println(flag.RolloutPercentage) // 50
Get detailed evaluation information including the reason a flag resolved to its value
and the variation key. Returns a *FlagDetailResult
struct. Always calls the server for accurate evaluation data.
ctx := context.Background()
// Get detailed evaluation info (always calls the server)
detail, err := client.FlagDetail(ctx, "new-checkout",
flagdash.EvaluationContext{
"user_id": "usr_123",
"plan": "premium",
},
)
if err != nil {
log.Printf("flag detail error: %v", err)
}
fmt.Println(detail.Key) // "new-checkout"
fmt.Println(detail.Value) // true
fmt.Println(detail.Reason) // "rule_match" | "variation" | "rollout" | "disabled" | "default"
fmt.Println(detail.VariationKey) // "variant-a" (empty string if not a variation)
// Get a config value (cached)
pricing, _ := client.Config(ctx, "pricing-tiers", nil)
// Get all configs
configs, _ := client.AllConfigs(ctx)
for _, c := range configs {
fmt.Printf("%s = %v\n", c.Key, c.Value)
}
// Get a single AI config file
agent, err := client.AiConfig(ctx, "agent.md")
if err != nil {
log.Fatal(err)
}
fmt.Println(agent.Content) // "# Agent Instructions..."
fmt.Println(agent.FileType) // "agent"
// List all AI configs
configs, _ := client.ListAiConfigs(ctx)
for _, c := range configs {
fmt.Printf("[%s] %s (%s)\n", c.FileType, c.FileName, c.Folder)
}
The client sends a region
with every evaluation, read from the environment automatically, so targeting
rules and segments can roll out region by region.
// region is auto-detected from the environment, checked in this order:
// FLAGDASH_REGION, FLY_REGION, AWS_REGION, AWS_DEFAULT_REGION,
// GOOGLE_CLOUD_REGION, RAILWAY_REPLICA_REGION, RENDER_REGION
client, _ := flagdash.NewClient("sk_...")
fmt.Println(client.Region()) // "eu-west-1"
// Every evaluation now carries region=eu-west-1
value, _ := client.Flag(ctx, "new-checkout", flagdash.EvaluationContext{
"user_id": "alice",
}, false)
// Set it explicitly (overrides detection)
client, _ = flagdash.NewClient("sk_...", flagdash.WithRegion("us-east-1"))
// Or turn detection off
client, _ = flagdash.NewClient("sk_...", flagdash.WithoutRegionDetection())
| Option | Default | Description |
|---|---|---|
| WithBaseURL(url) | https://flagdash.io | Set the FlagDash instance URL |
| WithCacheTTL(d) | 60s | Cache TTL (0 to disable) |
| WithTimeout(d) | 5s | HTTP request timeout |
greeting, err := client.Translation(ctx, "common.welcome", flagdash.TranslationOptions{
Locale: "de",
DefaultValue: "Welcome",
Variables: map[string]any{"name": "Ada"},
})
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
replay := flagdash.NewBackendReplay(os.Getenv("FLAGDASH_REPLAY_KEY")).
WithRelease("2026.08")
started, err := replay.Start(ctx)
if started {
replay.Event("checkout_started", "action", map[string]any{"items": 2})
replay.Breadcrumb("coupon applied", nil)
}
for k, v := range replay.ContextHeaders() {
req.Header.Set(k, v)
}
defer replay.Stop(ctx)
The key needs the replays:write
scope. Backend replay records an explicit
timeline — only the actions you name. Nothing captures request bodies, logs, or input values on its own.
Attribute keys that look sensitive (password, token, authorization, api_key, card) are redacted before
the event leaves the process.
Python client for FlagDash with both sync and async support.
Uses httpx for HTTP requests and supports context managers.
Use FlagDashClient
with an sk_
API Key
for reading flag values, configs, and AI configs.
Use ServerClient
with an API key that has full read scopes
for flag details (rules, variations, rollout metadata).
pip install flagdash
from flagdash import FlagDashClient, EvaluationContext
# As a context manager (recommended)
with FlagDashClient(
sdk_key="sk_...",
base_url="https://your-app.com",
) as client:
# Evaluate a flag
enabled = client.flag("new-checkout", default=False)
# With user context for targeting
value = client.flag(
"new-checkout",
context=EvaluationContext(
user_id="usr_123",
attributes={"plan": "premium"},
),
)
# Get all flags
flags = client.all_flags()
print(flags) # {"new-checkout": True, "dark-mode": False}
# Get a remote config
pricing = client.config("pricing-tiers", default={})
from flagdash import FlagDashServerClient, EvaluationContext
with FlagDashServerClient(
sdk_key="sk_...",
base_url="https://your-app.com",
cache_ttl=60.0, # Cache for 60 seconds (default)
) as client:
# Evaluate a flag (cached)
enabled = client.flag("new-checkout", default=False)
# Get flag with full metadata
flag = client.get_flag("new-checkout")
print(flag.rollout_percentage) # 50
print(flag.variations) # [FlagVariation(...), ...]
# List all flags with details
flags = client.list_flags()
Get detailed evaluation information including the reason a flag resolved to its value
and the variation key. Returns a FlagDetailResult
with key, value, reason, and
variation_key
fields. Both sync and async clients support this method.
from flagdash import FlagDashClient, EvaluationContext
# Sync client
with FlagDashClient(sdk_key="sk_...", base_url="https://your-app.com") as client:
detail = client.flag_detail(
"new-checkout",
context=EvaluationContext(
user_id="usr_123",
attributes={"plan": "premium"},
),
)
print(detail.key) # "new-checkout"
print(detail.value) # True
print(detail.reason) # "rule_match" | "variation" | "rollout" | "disabled" | "default"
print(detail.variation_key) # "variant-a" (None if not a variation)
# With a default value
theme = client.flag_detail("app-theme", default="light")
# Async client
from flagdash import FlagDashAsyncClient
async with FlagDashAsyncClient(sdk_key="sk_...") as client:
detail = await client.flag_detail("new-checkout")
print(detail.reason) # "default"
# Read-only access (content only)
agent = client.ai_config("agent.md")
print(agent.content) # "# Agent Instructions..."
print(agent.file_type) # "agent"
configs = client.list_ai_configs()
skills = [c for c in configs if c.file_type == "skill"]
# Full metadata with caching
from flagdash import FlagDashServerClient
with FlagDashServerClient(sdk_key="sk_...") as server:
agent = server.ai_config("agent.md")
print(agent.id) # "aic_xxxx"
print(agent.is_active) # True
print(agent.folder) # None or "prompts"
The client sends a region
with every evaluation, read from the environment automatically, so targeting
rules and segments can roll out region by region.
from flagdash import FlagDashClient
# region is auto-detected from the environment, checked in this order:
# FLAGDASH_REGION, FLY_REGION, AWS_REGION, AWS_DEFAULT_REGION,
# GOOGLE_CLOUD_REGION, RAILWAY_REPLICA_REGION, RENDER_REGION
client = FlagDashClient(sdk_key="sk_...")
# Every evaluation now carries region=eu-west-1
value = client.flag("new-checkout", default=False,
context=EvaluationContext(user_id="alice"))
# Set it explicitly (overrides detection)
client = FlagDashClient(sdk_key="sk_...", region="us-east-1")
# Or turn detection off
client = FlagDashClient(sdk_key="sk_...", auto_detect_region=False)
import asyncio
from flagdash import FlagDashAsyncClient
async def main():
async with FlagDashAsyncClient(
sdk_key="sk_...",
base_url="https://your-app.com",
) as client:
enabled = await client.flag("new-checkout", default=False)
configs = await client.list_ai_configs()
print(f"Found {len(configs)} AI configs")
asyncio.run(main())
| Class | Key Type | Description |
|---|---|---|
| FlagDashClient | sk_ | Read-only: evaluated flags, configs, AI configs |
| FlagDashServerClient | sk_ | Full metadata with TTL cache |
| FlagDashAsyncClient | sk_ | Async version of FlagDashClient |
| FlagDashAsyncServerClient | sk_ | Async version of FlagDashServerClient |
| FlagDashManagementClient | pat_ | Full CRUD: flags, configs, AI configs, webhooks |
greeting = client.translation(
"common.welcome",
locale="de",
default="Welcome",
variables={"name": "Ada"},
)
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
from flagdash import FlagDashBackendReplay
replay = FlagDashBackendReplay(
os.environ["FLAGDASH_REPLAY_KEY"],
release="2026.08",
)
if replay.start():
replay.event("checkout_started", attributes={"items": 2})
replay.breadcrumb("coupon applied")
headers.update(replay.context_headers())
replay.stop()
The key needs the replays:write
scope. Backend replay records an explicit
timeline — only the actions you name. Nothing captures request bodies, logs, or input values on its own.
Attribute keys that look sensitive (password, token, authorization, api_key, card) are redacted before
the event leaves the process.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
{:flagdash, "~> 0.1"}
Immutable GitHub install: {:flagdash, github: "flagdash/flagdash-beam", tag: "v0.1.0"}
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
{:ok, client} = FlagDash.Client.new(System.fetch_env!("FLAGDASH_SDK_KEY"))
enabled = FlagDash.Client.flag(client, "checkout-v2", %{user: %{id: "alice"}}, false)
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
greeting = FlagDash.Client.translation(client, "common.welcome", "de", default: "Welcome", variables: %{name: "Ada"})
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
{:ok, replay} = FlagDash.BackendReplay.start_link(sdk_key: System.fetch_env!("FLAGDASH_REPLAY_KEY"), release: "2026.08")
if FlagDash.BackendReplay.start(replay) do
FlagDash.BackendReplay.event(replay, "checkout_started", "action", %{items: 2})
end
FlagDash.BackendReplay.stop(replay)
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: Hex.pm scoped HEX_API_KEY. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
luarocks install flagdash
Immutable GitHub install: luarocks make https://github.com/flagdash/flagdash-lua/raw/v0.1.0/flagdash-0.1.0-1.rockspec
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
local client = flagdash.new(os.getenv("FLAGDASH_SDK_KEY"))
local enabled = client:flag("checkout-v2", false, { user_id = "alice" })
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
local greeting = client:translation("common.welcome", "de", "Welcome", { name = "Ada" })
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
local replay = flagdash.BackendReplay.new(os.getenv("FLAGDASH_REPLAY_KEY"), { release = "2026.08" })
if replay:start() then
replay:event("checkout_started", "action", { items = 2 })
end
replay:stop()
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: LuaRocks.org LUAROCKS_API_KEY. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
composer require flagdash/sdk
Immutable GitHub install: Composer VCS repository github.com/flagdash/flagdash-php at v0.1.0
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
$client = new FlagDash\Client($_ENV['FLAGDASH_SDK_KEY']);
$enabled = $client->flag('checkout-v2', false, ['user_id' => 'alice']);
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
$greeting = $client->translation('common.welcome', 'de', 'Welcome', ['name' => 'Ada']);
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
$replay = new FlagDash\\BackendReplay(getenv('FLAGDASH_REPLAY_KEY'), release: '2026.08');
if ($replay->start()) {
$replay->event('checkout_started', attributes: ['items' => 2]);
}
$replay->stop();
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: GitHub-linked Packagist account and webhook. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
gem install flagdash
Immutable GitHub install: gem "flagdash", github: "flagdash/flagdash-ruby", tag: "v0.1.0"
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
client = FlagDash::Client.new(sdk_key: ENV.fetch("FLAGDASH_SDK_KEY"))
enabled = client.flag("checkout-v2", default: false, context: {user_id: "alice"})
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
greeting = client.translation("common.welcome", locale: "de", default: "Welcome", variables: {name: "Ada"})
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
replay = FlagDash::BackendReplay.new(sdk_key: ENV.fetch("FLAGDASH_REPLAY_KEY"), release: "2026.08")
if replay.start
replay.event("checkout_started", attributes: {items: 2})
headers.merge!(replay.context_headers)
end
replay.stop
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: RubyGems Trusted Publisher. The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use this SDK to evaluate FlagDash flags and read remote and AI configuration using native application code.
cargo add flagdash-sdk
Immutable GitHub install: flagdash-sdk = { git = "https://github.com/flagdash/flagdash-rust", tag = "v0.1.0" }
In Dashboard → Project → Environment → API keys, create an sk_
key for that environment. Grant flags:read, configs:read, and
ai_configs:read
only when the application uses each capability. Never ship a pat_
or write-capable credential.
let client = flagdash_sdk::Client::new(flagdash_sdk::Config::new(std::env::var("FLAGDASH_SDK_KEY")?))?;
let enabled = client.flag("checkout-v2", false, Some(&flagdash_sdk::EvaluationContext::new().user_id("alice")));
Use a default matching the old production behavior. In Dashboard, use
Test evaluation
with user alice, then compare it with the example application's visible result before rollout.
let greeting = client.translation("common.welcome", "de", Some("Welcome"), &HashMap::from([("name".into(), "Ada".into())]));
Keys are namespace.key. A locale with no published value falls back to the
catalog's source locale, and then to the default you pass — so a missing translation
degrades to readable copy instead of a blank string.
let mut replay = flagdash_sdk::BackendReplay::new(flagdash_sdk::Config::new(std::env::var("FLAGDASH_REPLAY_KEY")?))?;
if replay.start()? {
replay.event("checkout_started", "action", serde_json::json!({"items": 2}));
}
replay.stop()?;
The key needs the replays:write
scope. This records an explicit
timeline — only what you name. Nothing captures request bodies, logs, input values,
native view hierarchies, or pixels on its own, and attribute keys that look sensitive
(password, token, authorization, api_key, card) are redacted before the event is buffered.
Reuse one client, keep its bounded last-known cache, close it during application shutdown, and flush experiment metrics before exit. Context-specific evaluations bypass shared cache entries. Remove the key from the app and revoke it in the dashboard when decommissioning an environment.
Release automation needs: crates.io CRATES_IO_TOKEN (reuse the CLI/TUI publishing account). The complete least-privilege setup and rotation checklist is in docs/sdk-publishing.md.
Use organization-owned accounts, passkeys or MFA, two maintainers where supported, and a protected GitHub release environment. GitHub installation uses immutable tags.
| Packages | Account | Secret or trust |
|---|---|---|
| Vue, React Native | Existing @flagdashio npm organization |
NPM_TOKEN, or npm trusted publishing when enabled |
| Android | Maven Central Portal; verify io.flagdash |
MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD, MAVEN_SIGNING_KEY,
MAVEN_SIGNING_PASSWORD
|
| iOS / Swift | No registry account | Protected, signed GitHub semantic tags; no Apple Developer credential for SwiftPM |
| Elixir / Erlang | Hex.pm organization with two owners | Scoped HEX_API_KEY |
| Lua | LuaRocks.org organization account | LUAROCKS_API_KEY |
| PHP | GitHub-linked Packagist account | Repository access and GitHub push webhook; no upload token |
| Ruby | RubyGems.org owners |
GitHub OIDC Trusted Publisher; scoped GEM_HOST_API_KEY fallback
|
| Rust | Existing crates.io organization account | Existing scoped CRATES_IO_TOKEN |
| Public mirrors | Existing flagdash GitHub organization |
SDK_SYNC_TOKEN with contents-only access to exact mirror repositories
|
Keep the current NPM_TOKEN
for the JavaScript monorepo and CRATES_IO_TOKEN
for both the Rust CLI/TUI and flagdash-sdk
crate.
Store release credentials in a protected sdk-release
GitHub Environment.
sdk-release environment.
Use FlagDash through the vendor-neutral OpenFeature API when your application already standardizes feature flag calls across providers. FlagDash's native SDKs remain the best choice when you also need remote config, AI config, experimentation, or native realtime APIs.
OpenFeature evaluates the same environment flags, targeting rules, segments, variations, and rollouts as every FlagDash SDK.
OFREP base URL
https://flagdash.io
Authentication: an sk_ key with flags:read.
flags:read scope.targetingKey.npm install @openfeature/server-sdk @openfeature/ofrep-provider
import { OpenFeature } from '@openfeature/server-sdk';
import { OFREPProvider } from '@openfeature/ofrep-provider';
await OpenFeature.setProviderAndWait(new OFREPProvider({
baseUrl: 'https://flagdash.io',
headers: [['Authorization', `Bearer ${process.env.FLAGDASH_SDK_KEY}`]]
}));
const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue(
'checkout-v2',
false,
{ targetingKey: 'user-123', plan: 'pro' }
);
The web provider bulk-loads evaluated values, persists its cache, honors ETags, and reconnects to FlagDash's OFREP change stream. Set context before rendering flag hooks.
npm install @openfeature/web-sdk @openfeature/react-sdk @openfeature/ofrep-web-provider
import { OpenFeature } from '@openfeature/web-sdk';
import { OpenFeatureProvider } from '@openfeature/react-sdk';
import { OFREPWebProvider } from '@openfeature/ofrep-web-provider';
await OpenFeature.setContext({ targetingKey: currentUser.id, plan: currentUser.plan });
await OpenFeature.setProviderAndWait(new OFREPWebProvider({
baseUrl: 'https://flagdash.io',
headers: [['X-API-Key', import.meta.env.VITE_FLAGDASH_SDK_KEY]]
}));
root.render(<OpenFeatureProvider><App /></OpenFeatureProvider>);
go get github.com/open-feature/go-sdk github.com/open-feature/go-sdk-contrib/providers/ofrep
provider := ofrep.NewProvider(
"https://flagdash.io",
ofrep.WithBearerToken(os.Getenv("FLAGDASH_SDK_KEY")),
)
if err := openfeature.SetProviderAndWait(provider); err != nil {
log.Fatal(err)
}
client := openfeature.NewClient("checkout")
enabled, err := client.BooleanValue(
context.Background(), "checkout-v2", false,
openfeature.NewEvaluationContext("user-123", map[string]any{"plan": "pro"}),
)
pip install openfeature-sdk openfeature-provider-ofrep
import os
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from openfeature.contrib.provider.ofrep import OFREPProvider
api.set_provider_and_wait(OFREPProvider(
base_url="https://flagdash.io",
headers_factory=lambda: {
"Authorization": f"Bearer {os.environ['FLAGDASH_SDK_KEY']}"
},
))
client = api.get_client()
enabled = client.get_boolean_value(
"checkout-v2", False,
EvaluationContext("user-123", {"plan": "pro"}),
)
curl -X POST https://flagdash.io/ofrep/v1/evaluate/flags/checkout-v2 \
-H "Authorization: Bearer $FLAGDASH_SDK_KEY" \
-H "Content-Type: application/json" \
-d '{"context":{"targetingKey":"verification-user","plan":"pro"}}'
A successful response includes key, a typed value, and a standard
reason such as STATIC, TARGETING_MATCH, SPLIT, or DISABLED. Test separate keys for development and production environments.
targetingKey uses the application's OpenFeature default.
Declare your flags in a file, review changes in a pull request, apply them on merge. Works with GitHub, GitLab, Bitbucket, Azure DevOps, Jenkins, anything that can run a binary and hold a secret.
There is no state bucket to provision and no provider to install. FlagDash already
holds the state, so ownership is a column on the row, which is why
plan
is a question it can answer directly, and why this is one YAML file and one binary.
flag: checkout-v2
environment: staging
enabled: false
Keep desired flags in a reviewed repository alongside application code.
Repository configurationInspect additions, changes, and removals before applying them.
CLI plan / CI reviewUse a scoped CI credential and confirm the resulting runtime value.
CI → FlagDash → application
You do not have to learn the schema. Export the project as it stands, commit that, and
your first plan
reports no changes, a green baseline before you change anything. The same button is on Dashboard → GitOps.
npm install -g @flagdashio/cli # or brew install flagdash/tap/flagdash
export FLAGDASH_API_KEY=sk_...
export FLAGDASH_PROJECT_ID=prj_...
flagdash gitops export > flagdash.yaml
flagdash plan
# No changes. Your file matches FlagDash.
A management API key with flags:write
and segments:write. Nothing else, a sync performs
ordinary flag and segment writes, so there is no separate GitOps permission. Store it in
your CI secrets as FLAGDASH_API_KEY.
Plan on the pull request, apply on merge. Recipes for other CI systems are below.
name: FlagDash
on:
pull_request:
push:
branches: [main]
jobs:
flagdash:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install -g @flagdashio/cli
# On a pull request: show what would change, and comment it.
- name: Plan
if: github.event_name == 'pull_request'
run: flagdash plan --format markdown | tee plan.md
env:
FLAGDASH_API_KEY: ${{ secrets.FLAGDASH_API_KEY }}
FLAGDASH_PROJECT_ID: ${{ vars.FLAGDASH_PROJECT_ID }}
- name: Comment the plan
if: github.event_name == 'pull_request'
run: gh pr comment ${{ github.event.number }} --body-file plan.md
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# On merge: make it so.
- name: Apply
if: github.ref == 'refs/heads/main'
run: flagdash apply
env:
FLAGDASH_API_KEY: ${{ secrets.FLAGDASH_API_KEY }}
FLAGDASH_PROJECT_ID: ${{ vars.FLAGDASH_PROJECT_ID }}
How the job finds your file:
actions/checkout
puts your repository in the runner's working directory, and
flagdash
reads flagdash.yaml
(or flagdash.d/) from there. Committing the file at the
repository root is the whole connection. There is nothing to configure. If you keep it
elsewhere, pass --file path/to/flagdash.yaml. The three
environment variables are what point the CLI at which FlagDash project
to compare against; the file itself comes from the checkout.
Four pull requests, from creating the flag to deleting it. Nobody touches the dashboard, and every change to who sees the feature is reviewable, attributable and revertible.
Ship the flag with the code that reads it, in the same pull request. It is on in development so you can work with it, and off in production so merging changes nothing for customers.
version: 1
flags:
new-checkout:
name: New Checkout Experience
description: Rewrite of the payment step
type: boolean
default_value: false
tags: [checkout]
owner: payments-team
environments:
development:
enabled: true
production:
enabled: false
What the pull request shows:
+ new-checkout
name: New Checkout Experience
description: Rewrite of the payment step
type: boolean
default_value: false
+ new-checkout development
enabled: true
+ new-checkout production
enabled: false
3 changes: 3 to create, 0 to update, 0 to delete, 0 drifted.
Turn it on in production, but only for a targeting rule. Everyone else still gets the default, because the rule is the only thing that returns true.
production:
- enabled: false
+ enabled: true
+ rules:
+ - description: The payments team, first
+ attribute: email
+ operator: ends_with
+ values: ["@acme.com"]
+ variation: true
~ new-checkout production
enabled: false → true
rules: none → [The payments team, first]
1 change: 0 to create, 1 to update, 0 to delete, 0 drifted.
Raise the percentage over a few merges: 10, then 25, then 50. Bucketing is consistent per user, so raising the number only ever adds people, nobody who already had the new checkout loses it.
production:
enabled: true
+ rollout_percentage: 10
rules:
- description: The payments team, first
~ new-checkout production
rollout_percentage: 100 → 10
1 change: 0 to create, 1 to update, 0 to delete, 0 drifted.
If something is wrong, revert the pull request. The next apply puts the percentage back where it was. The rollback is a git revert, with the same review trail as the change.
Once it is at 100% and the old path is gone from the code, remove the flag from the file
and apply with --prune. A prune only ever deletes flags
this repository owns
a flag somebody created in the dashboard was never in your file, and is left alone.
# Delete the flag block from flagdash.yaml, then:
flagdash plan --prune
# - new-checkout
# 1 change: 0 to create, 0 to update, 1 to delete, 0 drifted.
flagdash apply --prune
--format markdown
renders the plan as a pull-request comment. FlagDash does not post it for you, which is
what keeps this working on every forge, pipe it into whatever your CI already has.
# GitHub
flagdash plan --format markdown > plan.md
gh pr comment "$PR_NUMBER" --body-file plan.md
# GitLab
flagdash plan --format markdown > plan.md
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" --form "body=<plan.md" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
The portable integration point. Any CI can gate on these with no plugin.
| Code | Meaning |
|---|---|
| 0 | No changes |
| 2 |
Changes pending (plan). Not a failure, pass --exit-zero
if a step's success matters more than the signal.
|
| 1 | Error, including a blocked apply |
A managed flag is read-only in the dashboard, but not during an incident. Break glass
and the toggle goes through, because refusing would make GitOps the reason an outage
lasted longer. What it does not do is disappear quietly: the next
plan
reports it, with who and when.
! new-checkout production
enabled: false → true
changed outside the repository at 2026-08-30T02:14Z by ops@acme.com
1 change: 0 to create, 0 to update, 0 to delete, 1 drifted.
Some of these were changed outside the repository, so `flagdash apply` will
stop rather than revert them. Update the document to match, or apply with
--accept-drift to overwrite them deliberately.
And the next apply stops rather than reverting them. This is the part most tooling gets wrong: reconciling against a state file means an operator's fix silently vanishes at the next merge. Because FlagDash holds the state, it can notice and refuse.
These resources were changed outside the repository since the last sync.
Update the document to match, or re-run with accept_drift to overwrite them.
new-checkout: changed outside the repository at 2026-08-30T02:14Z by ops@acme.com
Error: FlagDash rejected the sync # exit 1
Two ways forward, both explicit:
# Keep what the operator did: pull reality into the file, commit, apply.
flagdash gitops export > flagdash.yaml
# Or overwrite it deliberately.
flagdash apply --accept-drift
Only the fields your document mentions
are managed. A flag that declares a rollout but not enabled
has its rollout enforced from the repository while the kill switch stays an operator's
to flip without a pull request.
flags:
search-v2:
environments:
production:
rollout_percentage: 25 # the repository owns this
# `enabled` is not mentioned, so ops can still kill it
# from the dashboard without a pull request
flagdash:plan:
image: node:22
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- npm install -g @flagdashio/cli
- flagdash plan --format markdown > plan.md
- cat plan.md
flagdash:apply:
image: node:22
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- npm install -g @flagdashio/cli
- flagdash apply
pipelines:
pull-requests:
'**':
- step:
image: node:22
script:
- npm install -g @flagdashio/cli
- flagdash plan
branches:
main:
- step:
image: node:22
script:
- npm install -g @flagdashio/cli
- flagdash apply
npm install -g @flagdashio/cli
# `plan` exits 2 when there are changes, which is not a failure.
flagdash plan || [ $? -eq 2 ]
flagdash apply
repository@sha, so "who turned this on?" answers with a
person and something you can go read.
flagdash.yaml, or
flagdash.d/*.yaml
merged in filename order. Prefer the directory on any team with more than a couple of
people: one file per flag means two people changing two flags never conflict.
Full reference including every field:
the API reference
documents POST /api/v1/manage/sync, and the JSON Schema
for editor autocompletion is at https://flagdash.io/schemas/flagdash-v1.json.
Interactive terminal UI for managing feature flags, experiments, remote configs, AI configs, webhooks, and environments. Browse and edit your FlagDash resources without leaving the terminal.
brew install flagdash/tap/flagdash
curl -fsSL https://flagdash.io/install.sh | sh
npm install -g @flagdashio/cli
cargo install flagdash-cli
Run flagdash
to launch the TUI. On first run, the CLI will open your browser for authentication
using a device authorization flow. Approve the login, then select a project and environment
to work with. Your session is saved for future use.
# Launch the TUI (opens browser for login on first run)
flagdash
# Or pass a PAT directly (skips browser auth)
flagdash --api-key pat_xxx --project-id prj_xxx
The TUI is fully keyboard-driven. Use the shortcuts below to navigate between screens and manage resources.
| Key | Action |
|---|---|
| 1-7 | Switch sections (Dashboard, Flags, Configs, AI Configs, Experiments, Webhooks, Environments) |
| j/k / arrows | Navigate lists |
| Enter | Open detail view |
| Esc | Go back |
| / | Search (flags, configs, AI configs, experiments) |
| q | Quit |
| Key | Action |
|---|---|
| c | Create new resource |
| d | Delete selected |
| t | Toggle flag (flags list only) |
| Key | Action |
|---|---|
| e | Edit resource |
| t | Toggle flag on/off (flags only) |
| r | Edit rollout percentage (flags only) |
| u | Edit targeting rules (flags only) |
| v | Edit variations (flags) / Edit value (configs) |
| s | View schedules (flags only) |
| Screen | Description |
|---|---|
| Dashboard | Overview of projects, flags, configs, and recent activity |
| Flags | List, toggle, edit rollout, manage targeting rules, A/B variations, and schedules |
| Configs | View and edit remote config values per environment |
| AI Configs | Manage AI config files (agent, skills, rules) with markdown preview |
| Experiments | Create safe 50/50 drafts, inspect assignment configuration, and record decision notes. Available on every plan. |
| Webhooks | View webhook endpoints and delivery logs |
| Environments | Browse projects and environments, switch active context |
Configuration is loaded in this priority order (highest wins):
--api-key, --project-id)
~/.config/flagdash/config.toml)
[auth]
# Session token (issued after browser login)
session_token = "session_xxx"
# Or use a Personal Access Token instead
# api_key = "pat_xxx"
[connection]
base_url = "https://flagdash.io"
[defaults]
project_id = "prj_xxx"
environment_id = "env_xxx"
| Environment Variable | Description |
|---|---|
| FLAGDASH_API_KEY | Personal Access Token (pat_) or API key (sk_) |
| FLAGDASH_BASE_URL | FlagDash instance URL (default: https://flagdash.io) |
| FLAGDASH_PROJECT_ID | Default project ID |
| FLAGDASH_ENVIRONMENT_ID | Default environment ID |
Beyond the TUI, the CLI drives FlagDash from a file you keep in your repository: flagdash plan,
flagdash apply
and flagdash gitops export. Declare your flags in flagdash.yaml, review changes in a pull request, apply
them on merge. See GitOps, flags as code
for the setup, a worked example and the CI recipes.
Connect AI assistants like Claude, Cursor, and Windsurf directly to FlagDash. Manage flags, configs, and AI configs through natural language — no dashboard needed.
The Model Context Protocol lets AI assistants call tools on external services. FlagDash exposes 57 tools and 4 resources, so your AI can create flags, toggle rollouts, and update configs through conversation.
FlagDash exposes a single MCP endpoint over HTTP:
https://flagdash.io/mcp
Uses the StreamableHTTP transport. Compatible with Claude Code, Cursor, Windsurf, Claude Desktop, and any MCP client that supports HTTP.
There are two ways to authenticate, and both are fully supported. Pick by whether a person is present:
| Method | Best for | Token |
|---|---|---|
| Browser authorization (OAuth 2.1) | Your own editor. No token to copy, paste, or rotate — you approve a consent screen once. | mcp_at_ |
| Device grant | A CLI, TUI or container. Approve a short code in any browser; the client gets the same scoped token. | mcp_at_ |
| Personal access token | CI and automation, where no person is present to approve anything. | pat_ |
Add the server with no credentials at all. The client discovers that FlagDash supports OAuth, registers itself, and opens your browser the first time it needs access.
POST /oauth/register)
Access tokens last 24 hours and refresh silently, so you approve once and not again. A connection you keep using is never re-authorized; the 90-day refresh window is how long it may sit idle before consent is asked for again. Revoke access at any time from Settings.
A terminal, a TUI or a container has no browser of its own, so it uses the device grant: it shows you a short code, you approve it in a browser on
any device, and it receives the same scoped token a desktop client would. The FlagDash
CLI does this for you with flagdash login.
# The CLI drives the whole device grant for you
flagdash login
# Or point it at another environment
FLAGDASH_BASE_URL=https://stg.flagdash.io flagdash login
# In CI, where no browser exists, supply a token instead
export FLAGDASH_ACCESS_TOKEN=mcp_at_...
Codes last 15 minutes and are single-use. Always confirm the code in your browser matches the one your terminal is showing — a link containing a code is something someone else could send you.
Create one under Settings → Personal Access Tokens and send it as a Bearer token in your client's headers. A configured token is always preferred over the browser flow — you will never be prompted for consent while it is valid.
{
"mcpServers": {
"flagdash": {
"type": "http",
"url": "https://flagdash.io/mcp",
"headers": {
"Authorization": "Bearer pat_your_token_here"
}
}
}
}
| Scope | Description |
|---|---|
| mcp:read | Read flags, configs, AI configs, segments, projects, environments |
| mcp:write | Create and change flags, configs, AI configs — bounded by your own role. Deleting additionally needs the owner or manager role. |
Default consent grants mcp:read mcp:write. A client may request less —
approve mcp:read and the token is refused for every write, whatever your
own permissions allow.
Your permissions are still the ceiling.
A token — either kind — can only do what your own account role allows, every write is
recorded in the audit log under your name, and MCP requires a Premium plan or higher.
SDK keys (sk_) are not accepted here; use the
REST Management API
for server-to-server automation.
MCP clients find all of this automatically. These are the endpoints they use:
GET https://flagdash.io/.well-known/oauth-protected-resource
GET https://flagdash.io/.well-known/oauth-authorization-server
PKCE (S256) is required, clients are public (no client secret), and the resource
indicator must name https://flagdash.io/mcp
if sent.
One command — run this in your terminal:
# No token needed — opens your browser for consent on first use
claude mcp add -t http flagdash "https://flagdash.io/mcp"
# Or skip the browser entirely with a personal access token
claude mcp add -t http flagdash "https://flagdash.io/mcp" \
-H "Authorization: Bearer pat_your_token_here"
Or add manually to .mcp.json in your project root:
{
"mcpServers": {
"flagdash": {
"type": "http",
"url": "https://flagdash.io/mcp"
}
}
}
Go to Settings → Connectors → Create and enter the endpoint URL,
or add manually to claude_desktop_config.json:
{
"mcpServers": {
"flagdash": {
"url": "https://flagdash.io/mcp"
}
}
}
Open Settings → MCP Tools → New MCP Server,
or add manually to .cursor/mcp.json in your project root:
{
"mcpServers": {
"flagdash": {
"type": "http",
"url": "https://flagdash.io/mcp"
}
}
}
Open Cascade → MCP → View raw config,
or edit ~/.codeium/windsurf/mcp_config.json directly:
{
"mcpServers": {
"flagdash": {
"serverUrl": "https://flagdash.io/mcp"
}
}
}
ChatGPT supports MCP via Developer Mode (Pro and Plus plans). The endpoint must be reachable over HTTPS.
https://flagdash.io/mcp
Authorization: Bearer pat_your_token_here
— ChatGPT cannot complete a browser consent flow, so a personal access token
is required here
Verify the MCP endpoint is working:
# Settings → Personal Access Tokens. Use a test account first.
export MCP_TOKEN='pat_your_token_here'
MCP_URL='https://flagdash.io/mcp'
MCP_HEADERS=$(mktemp)
curl -sS -D "$MCP_HEADERS" "$MCP_URL" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-H "Authorization: Bearer $MCP_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-03-26","capabilities":{},
"clientInfo":{"name":"test","version":"0.1"}}}'
MCP_SESSION=$(awk 'tolower($1)=="mcp-session-id:" {gsub("\r", "", $2); print $2}' "$MCP_HEADERS")
rm "$MCP_HEADERS"
curl -sS "$MCP_URL" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-H "Authorization: Bearer $MCP_TOKEN" -H "Mcp-Session-Id: $MCP_SESSION" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# Expect result.tools: an array of the tools available to your assistant.
curl -sS "$MCP_URL" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-H "Authorization: Bearer $MCP_TOKEN" -H "Mcp-Session-Id: $MCP_SESSION" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
curl -sS -X DELETE "$MCP_URL" \
-H "Authorization: Bearer $MCP_TOKEN" -H "Mcp-Session-Id: $MCP_SESSION"
unset MCP_TOKEN MCP_SESSION MCP_HEADERS MCP_URL
57 tools organized by domain. Your AI assistant discovers these automatically. Names below are the identifiers your client sees — use them verbatim.
| Tool | Description | Parameters |
|---|---|---|
| list_flags | List all feature flags in a project | project_id |
| get_flag | Get a feature flag with environment states | key, project_id |
| create_flag | Create a new feature flag | key, name, project_id, description?, flag_type?, tags? |
| update_flag | Update a feature flag's metadata (name, description, tags) | key, project_id, description?, name?, tags? |
| delete_flag | Delete a feature flag | confirm, key, project_id |
| toggle_flag | Enable or disable a feature flag in a specific environment | environment_id, key, project_id |
| set_flag_rollout | Set rollout percentage (0-100) for a flag in an environment | environment_id, key, project_id, rollout_percentage |
| set_flag_rules | Set targeting rules for a flag in an environment | environment_id, key, project_id, rules |
| evaluate_flag | Evaluate a single flag with optional user context | environment_id, key, project_id, context? |
| evaluate_all_flags | Evaluate all flags in an environment with optional user context | environment_id, project_id, context? |
| Tool | Description | Parameters |
|---|---|---|
| list_experiments | List all experiments in a project | project_id |
| get_experiment | Get an experiment definition by stable key | key, project_id |
| create_experiment | Create an A/B experiment with weighted variants | key, name, project_id, variants, description?, hypothesis?, layer_key?, metrics?, parameters?, randomization_unit?, target_duration_days?, target_exposures?, targeting? |
| update_experiment | Update an experiment's configuration, evidence target, or final decision | key, project_id, decision?, decision_notes?, description?, hypothesis?, metrics?, name?, parameters?, target_duration_days?, target_exposures?, targeting?, variants? |
| Tool | Description | Parameters |
|---|---|---|
| list_session_replays | List recorded session replays for a project, with optional environment and type filters | project_id, environment_id?, limit?, offset?, search?, type? |
| get_session_replay | Get one session replay's metadata and its linked experiments. Recorded page content is never returned. | project_id, replay_id |
| list_experiment_replays | List the session replays recorded while one experiment assigned a variant | key, project_id, environment_id?, limit?, variant_key? |
| get_session_replay_config | Get session replay capture configuration for one project environment | environment_id, project_id |
| update_session_replay_config | Enable, sample, or adjust privacy selectors for session replay in one project environment | environment_id, project_id, allowed_text_selectors?, blocked_selectors?, capture_exception_messages?, enabled?, masked_selectors?, sample_rate? |
| delete_session_replay | Permanently delete one session replay and its stored recording | confirm, project_id, replay_id |
| Tool | Description | Parameters |
|---|---|---|
| list_segments | List all reusable audience segments for a project | project_id |
| get_segment | Get a single audience segment by key | key, project_id |
| create_segment | Create a reusable audience segment that flag targeting rules can reference | key, name, project_id, description?, excluded_keys?, included_keys?, rules? |
| update_segment | Update an audience segment's name, description, rules, or key lists | key, project_id, description?, excluded_keys?, included_keys?, name?, rules? |
| delete_segment | Delete an audience segment | confirm, key, project_id |
| Tool | Description | Parameters |
|---|---|---|
| list_configs | List all remote configs in a project | project_id |
| get_config | Get a remote config with environment values | key, project_id |
| create_config | Create a new remote config | key, name, project_id, config_type?, description?, tags? |
| update_config | Update a remote config's metadata (name, description, tags) | key, project_id, description?, name?, tags? |
| delete_config | Delete a remote config | confirm, key, project_id |
| toggle_config | Toggle a remote config active/inactive in a specific environment | environment_id, key, project_id |
| set_config_value | Set the value for a remote config in a specific environment | environment_id, key, project_id, value |
| Tool | Description | Parameters |
|---|---|---|
| manage_secrets | Refuses every secret operation and points at the portal. Secrets cannot be read or changed through MCP. | operation? |
| Tool | Description | Parameters |
|---|---|---|
| list_translation_catalogs | List translation namespaces and draft/published catalogs for an environment | environment_id, project_id |
| create_translation_namespace | Create a translation namespace that groups related application copy | key, name, project_id, description? |
| add_translation_locale | Add a locale to a project and optionally make it the default fallback | locale, project_id, make_default? |
| put_translation_draft | Create or replace a locale draft for a namespace in one environment | environment_id, locale, messages, namespace, project_id |
| publish_translation_release | Publish all saved translation drafts atomically for one environment | environment_id, project_id |
| Tool | Description | Parameters |
|---|---|---|
| use_ai_configs | Load all AI config files and apply them as context instructions. | — |
| list_ai_configs | List all AI config files for a project and environment | environment_id, project_id |
| get_ai_config | Get an AI config file with content | environment_id, file_name, project_id |
| create_ai_config | Create a new AI config file | content, environment_id, file_name, file_type, project_id, folder? |
| update_ai_config | Update an AI config file's content | content, environment_id, file_name, project_id, folder? |
| delete_ai_config | Delete an AI config file | confirm, environment_id, file_name, project_id |
| initialize_ai_configs | Initialize default AI config files for an environment | environment_id, project_id |
| Tool | Description | Parameters |
|---|---|---|
| list_ai_releases | List AI releases for a project and environment. | environment_id, project_id |
| get_ai_release | Get one AI release by key, with its baseline, candidate variants and rollout | environment_id, key, project_id |
| create_ai_release | Create an AI release: a baseline configuration, optional candidate variants, and the percentage of users the candidates receive. | baseline, environment_id, key, project_id, rollout_percentage?, status?, variants? |
| update_ai_release | Update an AI release's baseline, candidates, rollout or status. | environment_id, key, project_id, baseline?, rollout_percentage?, status?, variants? |
| set_ai_release_rollout | Set the percentage of users an AI release's candidates are served to (0-100). | environment_id, key, project_id, rollout_percentage, status? |
| evaluate_ai_release | Evaluate an AI release against a user context without changing anything. | environment_id, key, project_id, context? |
| Tool | Description | Parameters |
|---|---|---|
| list_skills | List the skills available in an environment. | environment_id, project_id |
| get_skill | Load one skill's full instructions. | environment_id, key, project_id |
| get_skill_file | Load one supporting file from a skill bundle. | environment_id, key, path, project_id |
| Tool | Description | Parameters |
|---|---|---|
| list_projects | List all projects for the authenticated account | — |
| get_project | Get details of a specific project | project_id |
| list_environments | List all environments in a project | project_id |
Parameters marked ? are optional.
MCP resources provide read-only snapshots. AI clients can read these to load context before performing actions.
| URI | Description |
|---|---|
| flagdash://ai-configs | All AI config files for the authenticated account |
| flagdash://configs | All remote configs for the authenticated account |
| flagdash://flags | All feature flags for the authenticated account |
| flagdash://projects | All projects and environments for the authenticated account |
Once connected, ask your AI assistant things like:
"List all my projects and their environments"
"Create a boolean flag called dark-mode"
"Enable new-checkout in production with 25% rollout"
"Evaluate all flags for user_id=alice, plan=premium"
"Create an AI config skill called code-review.md"
"What flags are enabled in staging right now?"
AI config files (agent, skills, rules) define how your AI should behave.
With the UseAiConfigs tool, you can load them directly into the
LLM’s context in one call.
1. Create your AI config files
in the dashboard or via MCP — write an agent.md
with your AI’s persona, add rule
files for guardrails,
and skill
files for capabilities.
2. Say "use ai configs"
in any MCP conversation. The LLM calls UseAiConfigs,
which fetches all your files and returns them as instructions
the LLM must follow.
3. The LLM follows your rules. Your agent persona, skills, and guardrails are now active. Update them in the dashboard — changes take effect on the next load.
"Use ai configs, then review this PR"
"Load ai configs and help me debug this error"
"Apply ai configs, then summarize our API changes"
"Use ai configs" (just that — it works)
Use a client supporting Streamable HTTP and MCP 2025-03-26 or newer. MCP transport sessions expire after 30 minutes of inactivity and are lost on server restart. A valid credential with an expired transport session receives HTTP 404; the client should initialize a new session and retry. This does not require a new token. An invalid or expired credential receives HTTP 401 instead. OAuth clients should refresh their token or request browser authorization again.
If your assistant still shows connected but calls fail, reconnect the server and make a read-only call such as list_projects to verify access before changing anything. A connection badge alone does not verify authentication. Never share tokens in logs.
Connect through browser authorization, or create a personal access token in Settings → Personal Access Tokens and send it as a Bearer token. OAuth access tokens, personal access tokens and device session tokens are accepted; project SDK keys are not.
Reload your editor window after saving the MCP config file. Verify the endpoint is reachable by running the curl test above.
Make sure the URL in your MCP config matches
https://flagdash.io/mcp
and your network can reach it.
Get instant updates when flags, configs, or AI configs change using Server-Sent Events (SSE). Available on client-side SDKs (JavaScript, React).
realtime: true
credential: read-scoped key
environment: Development
Opt in on a supported client SDK using the environment's read key.
JavaScript / React / VueA server event tells the client to refresh its cached values.
Server-Sent EventsTest a real edit, keep a fallback, and destroy the client when it is no longer needed.
Application lifecyclePick your runtime. Each example includes setup, credentials, and the feature call.
import { FlagDash } from "@flagdashio/sdk";
const client = FlagDash.init({
sdkKey: import.meta.env.VITE_FLAGDASH_SDK_KEY,
realtime: true,
});
// The cache refreshes itself; this fires after it has.
client.on("flags_updated", () => render());
client.on("configs_updated", () => render());
import { FlagDashProvider, useFlag } from "@flagdashio/react";
export function App() {
return <FlagDashProvider sdkKey={import.meta.env.VITE_FLAGDASH_SDK_KEY} realtime>
<Feature />
</FlagDashProvider>;
}
function Feature() {
// Re-renders on its own when the flag changes upstream.
const value = useFlag("checkout-v2", false);
return value ? <NewExperience /> : <SafeFallback />;
}
Not available in the Node.js SDK
Server SDKs have no SSE subscription — they serve from a TTL cache and re-fetch when it lapses. For push on the server, use webhooks. Tune the window with `cacheTTL`.
Not available in the Go SDK
Server SDKs have no SSE subscription — they serve from a TTL cache and re-fetch when it lapses. For push on the server, use webhooks. Tune the window with `flagdash.WithCacheTTL`.
Not available in the Python SDK
Server SDKs have no SSE subscription — they serve from a TTL cache and re-fetch when it lapses. For push on the server, use webhooks. The client's TTL cache defaults to 60 seconds.
curl -N "$FLAGDASH_URL/api/v1/sse?api_key=$FLAGDASH_CLIENT_KEY"
When you enable realtime: true in the SDK,
it opens an SSE connection to the server. When any flag, config, or AI config
changes in your environment, the server pushes an event and the SDK automatically
refreshes its local cache. Your UI re-renders with the new values instantly.
/api/v1/sse?api_key=YOUR_KEY
SSE stream for real-time updates (requires API key with read scope)
The SSE endpoint uses the api_key query parameter
for authentication (since EventSource cannot set custom headers). The SDK handles
this automatically.
| Event | Description |
|---|---|
| connected | SSE connection established |
| flag_updated | A flag was toggled, updated, or had its rollout changed |
| config_updated | A remote config value was changed |
| ai_config_updated | An AI config file was created, updated, or deleted |
| ping | Keep-alive signal (every 20 seconds) |
import { FlagDash } from '@flagdashio/sdk';
const client = FlagDash.init({
sdkKey: 'sk_...',
realtime: true, // Enable SSE
});
// Flags, configs, and AI configs auto-refresh on changes
client.on('flags_updated', (flags) => {
console.log('Flags changed:', flags);
});
client.on('configs_updated', (configs) => {
console.log('Configs changed:', configs);
});
// Toggle SSE at runtime
client.disableRealtime();
client.enableRealtime();
import { FlagDashProvider, useFlag } from '@flagdashio/react';
// Enable SSE in the provider — all hooks auto-update
function App() {
return (
<FlagDashProvider sdkKey="sk_..." realtime>
<FeatureToggle />
</FlagDashProvider>
);
}
function FeatureToggle() {
// Automatically re-renders when the flag changes via SSE
const enabled = useFlag('new-checkout', false);
return enabled ? <NewCheckout /> : <OldCheckout />;
}
If SSE is not suitable for your environment, you can use polling instead.
Set refreshInterval to a millisecond interval
and the SDK will periodically refetch all data.
const client = FlagDash.init({
sdkKey: 'sk_...',
refreshInterval: 30000, // Poll every 30 seconds
});
Server-side SDKs (Node.js, Go, Python) use TTL-based caching instead of SSE. For server-side real-time updates, use webhooks to receive HTTP callbacks when resources change.
Send project changes to your team's communication tools and inspect delivery health from the dashboard.
Slack
Storefront
OAuth
Connect Slack for the intended project and workspace.
Integrations → SlackSend a test, then check a real Development change.
Connection → Test ConnectionInspect failed responses and remove unused connections.
Connection detailThis is a control-plane workflow. Use the dashboard or REST; runtime SDKs observe the resulting flag or config state.
No public REST endpoint
Provider connections and Slack consent use the authenticated dashboard. No public integration-management REST endpoint or runtime SDK connection method is shipped.
No runtime SDK method connects Slack or sends these notifications. Configure the integration once in the dashboard; your application continues normal flag and config reads. For a custom HTTP receiver, use Webhooks and its signature-verification examples.
Inspect failed delivery responses before changing credentials. Confirm the provider authorization and destination are still valid, then use Test Connection again. Disable notifications while investigating if needed. Delete an obsolete connection in its detail view and revoke the provider-side grant when it is no longer used. Keep credentials out of application bundles and logs.
Get notified when flags or configs change. Webhooks are available on Premium and Enterprise plans.
Return a successful response for valid events requiring no work. Reserve retryable failures for work worth repeating. Keep tokens and payload secrets out of logs, disable obsolete destinations, and review environment and event filters when a delivery appears missing. Runtime SDKs evaluate values; a webhook receiver is your backend HTTP handler.
For Slack notifications, follow the Integrations guide.
Your HTTPS receiver
Development
flag.updated
Choose an environment and the event types your receiver handles.
Webhooks → Create Endpointx-flagdash-signature••••••••Authenticate the raw request body before parsing or processing it.
Your backend HTTP handlerTrigger a safe change and inspect the receiver's response in delivery history.
Endpoint → delivery historyPick your runtime. Each example includes setup, credentials, and the feature call.
Not available in the JavaScript SDK
Webhooks are delivered server-to-server and are never received in a browser. For live updates in a client, use realtime updates (SSE).
Not available in the React SDK
Webhooks are delivered server-to-server and are never received in a browser. For live updates in a client, use realtime updates (SSE).
const crypto = require("node:crypto");
// Mount with the RAW body — a parsed-and-restringified payload will not
// reproduce the bytes that were signed.
function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = new Map(header.split(",").map(p => p.split("=")));
const timestamp = Number(parts.get("t"));
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
// timingSafeEqual throws on a length mismatch, so check that first — a
// truncated or missing v1 must be a rejection, not a 500.
const received = Buffer.from(parts.get("v1") ?? "");
const digest = Buffer.from(expected);
return received.length === digest.length && crypto.timingSafeEqual(digest, received);
}
app.post("/flagdash", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body.toString(), req.get("x-flagdash-signature"), process.env.FLAGDASH_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
console.log(req.get("x-flagdash-event"), JSON.parse(req.body.toString()));
res.sendStatus(200);
});
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"math"
"strconv"
"strings"
"time"
)
func verify(rawBody []byte, header, secret string) bool {
var timestamp, signature string
for _, part := range strings.Split(header, ",") {
key, value, _ := strings.Cut(part, "=")
switch key {
case "t":
timestamp = value
case "v1":
signature = value
}
}
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
fmt.Fprintf(mac, "%s.%s", timestamp, rawBody)
return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(signature))
}
import hashlib
import hmac
import os
import time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(part.split("=", 1) for part in header.split(","))
try:
timestamp = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - timestamp) > tolerance:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
# Flask: request.get_data() is the raw body; request.json is not.
verify(request.get_data(), request.headers["x-flagdash-signature"], os.environ["FLAGDASH_WEBHOOK_SECRET"])
curl -X POST "$FLAGDASH_URL/api/v1/manage/webhooks" \
-H "Authorization: Bearer $FLAGDASH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"project_id\":\"$PROJECT_ID\",\"environment_id\":\"$ENVIRONMENT_ID\",\"url\":\"https://example.com/flagdash\",\"event_types\":[\"flag.toggled\"]}"
| Event | Description |
|---|---|
| flag.created | A new feature flag was created |
| flag.updated | A flag was modified (rules, rollout, settings) |
| flag.toggled | A flag was enabled or disabled in an environment |
| flag.deleted | A feature flag was deleted |
| config.created | A new remote config was created |
| config.updated | A remote config value or settings were changed |
| config.toggled | A remote config was activated or deactivated in an environment |
| config.deleted | A remote config was deleted |
{
"id": "whevt_a1b2c3d4e5f6g7h8",
"type": "flag.toggled",
"timestamp": "2026-02-14T10:30:00Z",
"project_id": "prj_abc123",
"environment_id": "env_prod456",
"data": {
"flag": {
"id": "flag_xyz789",
"key": "new-checkout",
"name": "New Checkout Flow",
"description": "Enables the redesigned checkout",
"flag_type": "boolean",
"default_value": null,
"tags": [],
"is_archived": false,
"project_id": "prj_abc123"
},
"changes": {
"enabled": { "from": false, "to": true }
}
}
}