Flagfile
it's developer and devops friendly feature flagging solution with cli command to manage thise where you define all your flags with rules and tests for those in Flagfile in this format: Flagfile.example
it has a cli available through brew install dzhibas/tap/flagfile-cli which allows you to init, run tests, validate syntax, serve flags through openfeature-api, find flags in codebase and evaluate flags through command line
it also have libraries to work with flagfile in each language, check our examples/ folder
Add the dependency to your Cargo.toml:
[dependencies]
flagfile-lib = "0.4"or until it becomes more stable and will be published into crates.io
[depedencies]
flagfile-lib = { git = "https://github.com/dzhibas/flagfile.git" }Then use init() to load a Flagfile from the current directory and ff() to evaluate flags:
use flagfile_lib::{Context, ff};
use std::collections::HashMap;
fn main() {
flagfile_lib::init();
let ctx: Context = HashMap::from([("tier", "premium".into()), ("country", "nl".into())]);
let flag: bool = ff("FF-feature-y", &ctx).expect("Flag not found").into();
if flag {
println!("Flag is on");
} else {
println!("Flag is off");
}
}| Function | Description |
|---|---|
init() |
Reads Flagfile from current directory |
init_with_env(env) |
Reads Flagfile and sets the environment for @env rules |
init_from_str(content) |
Parses flagfile content from a string |
init_from_str_with_env(content, env) |
Parses from string with environment |
If your Flagfile uses @env rules to vary behavior per environment, use init_with_env():
use flagfile_lib::{Context, ff};
use std::collections::HashMap;
fn main() {
// Set environment — @env rules will be evaluated against this
flagfile_lib::init_with_env("prod");
let ctx: Context = HashMap::from([("userId", "42".into())]);
let flag: bool = ff("FF-new-feature", &ctx).expect("Flag not found").into();
println!("Flag: {}", flag);
}Given a Flagfile like:
FF-new-feature {
@env dev -> true
@env stage -> true
@env prod {
percentage(25%, userId) -> true
false
}
}
init_with_env("dev")—@env dev -> truematches, flag returnstrueinit_with_env("prod")—@env prod { ... }block is evaluated with its sub-rulesinit()(no env) — all@envrules are skipped, falls through to non-env rules or default
Add dependency in package.json of flagfile-js. Then ff init and then:
import { init, ff } from "flagfile-ts";
init();
const ctx = {
tier: "premium",
countryCode: "nl",
};
if (ff("FF-feature-y", ctx)) {
console.log("Flag is on");
} else {
console.log("Flag is off");
}| Function | Description |
|---|---|
init() |
Reads Flagfile from current directory (Node.js only) |
initWithEnv(env) |
Reads Flagfile and sets environment for @env rules |
initFromString(content) |
Parses flagfile content from a string |
initFromStringWithEnv(content, env) |
Parses from string with environment |
import { initWithEnv, ff } from "flagfile-ts";
// Set environment — @env rules will be evaluated against this
initWithEnv("prod");
if (ff("FF-new-feature", { userId: "42" })) {
console.log("Feature enabled");
}- Feature flag name definition:
FF-<name>orFF_<name>
it's choosen specifically so that it prefixed with FF, cause cli provides a way to find it in codebase repo if you will need to clean it up with ff find or ff find -s premium
- Short notation:
FF-name -> value - Block notation:
FF-name { rules... defaultValue } - Values:
true,TRUE,false,FALSE,json({"key": "value"}), int or string - Rules:
condition1 && condition2 -> value - Default value in block: A final
valuewithout-> - Conditions:
- Comparisons:
==,!=(implicit fromnot),>=,<=,>,< - Logical op (case-insensitive):
and,or - Grouping:
(...) - Membership (case-insensitive):
in,not in - Reverse membership:
"value" in contextVariable(where variable holds an array) - Contains / regex match:
~(contains or regex match),!~(does not contain or does not match regex) - Starts/ends with:
^~,~$,!^~,!~$ - Operands: Identifiers, string literals, number literals, date literals (
YYYY-MM-DD), datetime literals (YYYY-MM-DDTHH:MM:SSZ), regex literals (/pattern/),NOW() - Tuple/List for
in/not in:(1,2,3) - String contains with
name ~ nikand negating does not containsname !~ nik - Regex match with
name ~ /.*nik.*/and negating with!~ - Function calls:
upper(),lower(),now(),coalesce(),segment(),percentage() - SemVer check so that
appVersion >= 5.3.2
- Comparisons:
- Comments: singleline
// ...and multiline/* ... */ - In Block notation can have multiple rules to evaluate
- Multi-line rules
- Inline tests:
// @test flag(context) == trueor standalone@test flag(context) == true - Reusable segments:
@segment name { expression }andsegment(name)in rules - Environment rules:
@env dev -> trueor@env prod { rules... } - Flag dependencies:
@requires FF-other-flag(flag only evaluates if prerequisite is true) - Null handling:
coalesce(var1, var2, "fallback")returns first non-null value - Flag metadata annotations:
@owner "team-name"— flag ownership@expires 2026-06-01— expiry date@ticket "JIRA-1234"— tracking ticket@description "Human readable description"— flag description@type release|experiment|ops|permission— flag type@deprecated "Use FF-xyz instead"— deprecation notice
it's a flagfile in your application root folder to control behaviour and feature flagging in your app
VSCode extension for syntax highlighting, linting, validating and running tests: code --install-extension flagfile-vscode-plugin/flagfile-0.1.1.vsix needs flagfile-cli installed through brew install dzhibas/tap/flagfile-cli
Example DSL syntax
// Basic on/off switches
FF-new-ui -> true
FF-beta-features -> false
FF-maintenance-mode -> false
// Configuration values
FF-api-timeout -> 5000
FF-max-retries -> 3
FF-log-level -> "debug"
// Feature variants
FF-button-color -> "blue"
FF-theme-config -> json({
"primaryColor": "#007bff",
"secondaryColor": "#6c757d",
"darkMode": true,
"animations": true
})
// ==== Complext features with multiple rules block ===
// Geographic-based features
FF-regional-features {
countryCode == "US" -> json({
"paymentMethods": ["card", "paypal", "apple_pay"],
"currency": "USD",
"taxCalculation": "state_based"
})
countryCode == "EU" -> json({
"paymentMethods": ["card", "paypal", "sepa"],
"currency": "EUR",
"taxCalculation": "vat_based"
})
countryCode in ("NL", "BE", "DE") -> json({
"paymentMethods": ["card", "ideal", "sepa"],
"currency": "EUR",
"taxCalculation": "vat_based",
"languageOptions": ["en", "nl", "de"]
})
// Default for other countries
json({
"paymentMethods": ["card"],
"currency": "USD",
"taxCalculation": "basic"
})
}
// User tier based features
FF-premium-features {
tier == "enterprise" and seats >= 100 -> json({
"features": ["advanced_analytics", "custom_integrations", "priority_support", "sso"],
"apiRateLimit": 10000,
"storageLimit": "unlimited"
})
tier == "premium" -> json({
"features": ["analytics", "integrations", "priority_support"],
"apiRateLimit": 5000,
"storageLimit": "100GB"
})
tier == "basic" -> json({
"features": ["basic_analytics"],
"apiRateLimit": 1000,
"storageLimit": "10GB"
})
// Free tier
json({
"features": [],
"apiRateLimit": 100,
"storageLimit": "1GB"
})
}
// Time-based feature rollout
FF-new-dashboard {
// Enable for internal users immediately
userType == "internal" -> true
// Gradual rollout for external users
userType == "external" and rolloutPercentage <= 25 and NOW() > "2024-01-15" -> true
userType == "external" and rolloutPercentage <= 50 and NOW() > "2024-02-01" -> true
userType == "external" and rolloutPercentage <= 75 and NOW() > "2024-02-15" -> true
userType == "external" and NOW() > "2024-03-01" -> true
false
}
// A/B testing configuration
FF-checkout-flow {
// Variant A: Traditional checkout
experimentGroup == "A" -> json({
"variant": "traditional",
"steps": ["cart", "shipping", "payment", "confirmation"],
"guestCheckout": false
})
// Variant B: One-page checkout
experimentGroup == "B" -> json({
"variant": "onepage",
"steps": ["onepage_checkout", "confirmation"],
"guestCheckout": true
})
// Control group gets traditional
json({
"variant": "traditional",
"steps": ["cart", "shipping", "payment", "confirmation"],
"guestCheckout": false
})
}
// A/B testing configuration
FF-checkout-flow {
// Variant A: Traditional checkout
experimentGroup == "A" -> json({
"variant": "traditional",
"steps": ["cart", "shipping", "payment", "confirmation"],
"guestCheckout": false
})
// Variant B: One-page checkout
experimentGroup == "B" -> json({
"variant": "onepage",
"steps": ["onepage_checkout", "confirmation"],
"guestCheckout": true
})
// Control group gets traditional
json({
"variant": "traditional",
"steps": ["cart", "shipping", "payment", "confirmation"],
"guestCheckout": false
})
}
// Device and platform specific features
FF-mobile-features {
platform == "ios" and appVersion >= "2.1.0" -> json({
"features": ["push_notifications", "biometric_auth", "offline_mode"],
"ui": "ios_native"
})
platform == "android" and appVersion >= "2.1.0" -> json({
"features": ["push_notifications", "fingerprint_auth", "offline_mode"],
"ui": "material_design"
})
platform == "web" and browserName in ("chrome", "firefox", "safari") -> json({
"features": ["push_notifications", "pwa_install"],
"ui": "responsive"
})
// Fallback for older versions or unsupported platforms
json({
"features": ["basic_auth"],
"ui": "basic"
})
}
// Performance and load based features
FF-performance-mode {
// Reduce features under high load
serverLoad > 80 -> json({
"enableAnimations": false,
"enableAnalytics": false,
"cacheStrategy": "aggressive",
"imageQuality": "low"
})
serverLoad > 60 -> json({
"enableAnimations": true,
"enableAnalytics": false,
"cacheStrategy": "moderate",
"imageQuality": "medium"
})
// Normal performance mode
json({
"enableAnimations": true,
"enableAnalytics": true,
"cacheStrategy": "normal",
"imageQuality": "high"
})
}
// Multi-condition complex feature
FF-advanced-search {
// Enterprise customers with specific requirements
tier == "enterprise" and
searchVolume > 10000 and
dataSize in ("large", "xlarge") and
region in ("us-east", "eu-west") -> json({
"searchEngine": "elasticsearch",
"features": ["fuzzy_search", "autocomplete", "faceted_search", "ml_ranking"],
"indexStrategy": "distributed",
"cacheSize": "256MB"
})
// Premium customers
tier == "premium" and searchVolume > 1000 -> json({
"searchEngine": "solr",
"features": ["fuzzy_search", "autocomplete", "faceted_search"],
"indexStrategy": "single_node",
"cacheSize": "64MB"
})
// Basic search for everyone else
json({
"searchEngine": "basic",
"features": ["exact_match"],
"indexStrategy": "in_memory",
"cacheSize": "16MB"
})
}// once you dont have rules you can use short notation to return boolean
FF-feature-flat-on-off -> true
// can be snake_case as well as kebab-case
FF_feature_can_be_snake_case_213213 -> FALSE
// can be camelCase
FF_featureOneOrTwo -> FALSE
// can be PascalCase
FF_Feature23432 -> TRUE
// you can return non-boolean in this example json. or empty json object json({})
FF-feature-json-variant -> json({"success": true})
// features are forced to start with FF- case-sensitive as
// it allows you later to find all flags through the codebase
FF-feature-name-specifics -> false
// you can have feature with multiple rules in it with default flag value returned in the end
// you can have comments or comment blocks with // or /* comment */
FF-feature-y {
// if country is NL return True
countryCode == "NL" -> true
// else default to false
false
}
// you can also return different variations (non-boolean) as example json
FF-testing {
// default variant
json({"success": true})
}
// and have more complex feature with multiple rules in it and some rules multiline rule, which at the end defaults to false
// aswel capitalize for visibility boolean TRUE/FALSE
FF-feature-complex-ticket-234234 {
// complex bool expression
a = b and c=d and (dd not in (1,2,3) or z == "demo car") -> TRUE
// another one
z == "demo car" -> FALSE
// with checking more
g in (4,5,6) and z == "demo car" -> TRUE
// and multi-line rule works
model in (ms,mx,m3,my) and created >= 2024-01-01
and demo == false -> TRUE
FALSE
}
// different kind of comments inside
FF-feature1 {
/* comment like this */
true
a == "something" -> false
false
json({})
}
/* this is multi-line commented feature
FF-timer-feature {
// turn on only on evaluation time after 22nd feb
NOW() > 2024-02-22 -> true
false
}
*/