Appearance
Rules & Targeting
Target specific users, plans, or percentages with conditional rules. Rules are evaluated per flag, per environment, and the first matching rule determines the flag value.
How Evaluation Works
When you call POST /flags/evaluate with a context object, the engine processes rules in this order:
- If the flag is disabled, return the flag's
default_valueimmediately - Find all enabled rules for the flag in the requested environment
- Evaluate rules top-to-bottom by
position(0 is first) - The first rule whose conditions match the context determines the value
- If no rule matches, return the flag's
default_value
TIP
Rule order matters. Place more specific rules (e.g., individual users) above broader rules (e.g., percentage rollouts). You control the order with the position field.
Creating a Rule
Add a rule to an existing flag. This example targets users on the pro plan in production:
bash
curl -X POST https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"position": 0,
"conditions": [
{"attribute": "plan", "operator": "equals", "value": "pro"}
],
"value": true,
"enabled": true
}'json
{
"data": {
"id": "r1a2b3c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
"flag_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"environment": "production",
"position": 0,
"conditions": [
{"attribute": "plan", "operator": "equals", "value": "pro"}
],
"value": true,
"percentage": null,
"enabled": true,
"segment_id": null,
"created_at": "2025-01-15T11:00:00Z"
}
}Now when you evaluate the flag with {"plan": "pro"} in the context, the rule matches and the flag returns true.
Condition Operators
Each condition compares an attribute from the evaluation context against a value using an operator. The complete supported set (Krafter.Flags.Evaluator) is:
| Operator | Description | Example |
|---|---|---|
equals | Exact match (compared as strings) | {"attribute": "plan", "operator": "equals", "value": "pro"} |
not_equals | Does not match (compared as strings) | {"attribute": "plan", "operator": "not_equals", "value": "free"} |
contains | String contains substring | {"attribute": "email", "operator": "contains", "value": "@company.com"} |
not_contains | String does not contain substring | {"attribute": "email", "operator": "not_contains", "value": "test"} |
starts_with | String starts with prefix | {"attribute": "user_id", "operator": "starts_with", "value": "usr_"} |
ends_with | String ends with suffix | {"attribute": "domain", "operator": "ends_with", "value": ".io"} |
in | Value is in array OR comma-separated string | {"attribute": "country", "operator": "in", "value": ["US", "CA", "GB"]} |
not_in | Value is not in array / list | {"attribute": "country", "operator": "not_in", "value": "CN,RU"} |
greater_than | Numeric comparison (parses both sides as floats) | {"attribute": "age", "operator": "greater_than", "value": "18"} |
less_than | Numeric comparison (parses both sides as floats) | {"attribute": "age", "operator": "less_than", "value": "65"} |
regex | Pattern match — see safety notes below | {"attribute": "email", "operator": "regex", "value": "^[^@]+@krafter\\."} |
is_true | Truthy: true, "true", or "1" | {"attribute": "beta", "operator": "is_true", "value": null} |
is_false | Anything not truthy (incl. nil) | {"attribute": "beta", "operator": "is_false", "value": null} |
exists | Attribute is present and non-nil | {"attribute": "user_id", "operator": "exists", "value": null} |
not_exists | Attribute is missing or nil | {"attribute": "user_id", "operator": "not_exists", "value": null} |
Notes:
- A missing context attribute makes most operators evaluate to
false. The exceptions arenot_in(returnstrue),not_exists(returnstrue),is_false(returnstruebecausenilis not truthy), andexists(returnsfalse). - An unknown operator name evaluates to
false(safe fallback). - When a rule has multiple conditions, all conditions must match (AND logic).
Regex safety
The regex operator caches compiled patterns in ETS for the lifetime of the node and applies two guard rails before compiling, both enforced by Krafter.Flags.Evaluator:
- Length cap. Patterns over 500 bytes are rejected.
- Nested-quantifier ReDoS guard. Patterns matching
\([^)]*[+*][^)]*\)[+*](e.g.(a+)+,(.*x.*)*) are rejected.
Patterns that fail either guard, or that fail to compile, evaluate the condition to false (no match). There is no error surfaced to the API caller — design rule values that don't depend on regex success/failure being signalled.
Multiple Conditions
You can combine conditions within a single rule. This rule targets pro users in the US:
bash
curl -X POST https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"position": 0,
"conditions": [
{"attribute": "plan", "operator": "equals", "value": "pro"},
{"attribute": "country", "operator": "equals", "value": "US"}
],
"value": true,
"enabled": true
}'Percentage Rollouts
Instead of returning a fixed value, a rule can roll out to a percentage of users. Set the percentage field (0--100) and the engine uses a consistent hash of user_id from the context so the same user always gets the same result.
bash
curl -X POST https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"position": 1,
"conditions": [],
"value": true,
"percentage": 25,
"enabled": true
}'This rule has no conditions, so it applies to all users -- but only 25% of them will receive true. The remaining 75% fall through to the next rule or the default value.
INFO
Percentage rollouts require user_id in the evaluation context. Without it, the rule is skipped.
Segments
Segments are reusable targeting groups that can be referenced by any rule instead of duplicating conditions.
Creating a Segment
bash
curl -X POST https://app.krafter.dev/api/v1/flags/segments \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro Users",
"rules": [
{"key": "plan", "operator": "equals", "value": "pro"}
],
"match_type": "all"
}'json
{
"data": {
"id": "s1a2b3c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
"name": "Pro Users",
"rules": [
{"key": "plan", "operator": "equals", "value": "pro"}
],
"match_type": "all",
"created_at": "2025-01-15T12:00:00Z"
}
}The match_type field controls how the segment's rules are combined:
| Match type | Behavior |
|---|---|
all | All rules must match (AND) |
any | At least one rule must match (OR) |
Using a Segment in a Rule
Reference a segment by its ID instead of writing inline conditions:
bash
curl -X POST https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"position": 0,
"segment_id": "s1a2b3c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
"value": true,
"enabled": true
}'When a rule has a segment_id, its conditions are ignored and the segment's rules are used for matching instead.
Environment-Based Rules
Rules are scoped to an environment. A flag can have different rules in each environment, so you can enable a feature for all users in staging while rolling it out to 10% in production.
bash
# Enable for everyone in staging
curl -X POST https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"environment": "staging",
"position": 0,
"conditions": [],
"value": true,
"enabled": true
}'
# 10% rollout in production
curl -X POST https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"position": 0,
"conditions": [],
"value": true,
"percentage": 10,
"enabled": true
}'When evaluating, pass the environment in the request to get the correct rules:
bash
curl -X POST https://app.krafter.dev/api/v1/flags/evaluate \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"project_id": "PROJECT_ID",
"environment": "production",
"context": {"user_id": "usr_123"}
}'Managing Rules
List Rules
bash
curl https://app.krafter.dev/api/v1/flags/FLAG_ID/rules \
-H "Authorization: Bearer kr_live_abc123def456"Update a Rule
bash
curl -X PATCH https://app.krafter.dev/api/v1/flags/FLAG_ID/rules/RULE_ID \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"percentage": 50
}'Delete a Rule
bash
curl -X DELETE https://app.krafter.dev/api/v1/flags/FLAG_ID/rules/RULE_ID \
-H "Authorization: Bearer kr_live_abc123def456"Next Steps
- Experiments -- Run A/B tests with variant flags
- Quickstart -- Create your first flag
- API Reference -- Full endpoint documentation