BETA
Skip to content

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:

  1. If the flag is disabled, return the flag's default_value immediately
  2. Find all enabled rules for the flag in the requested environment
  3. Evaluate rules top-to-bottom by position (0 is first)
  4. The first rule whose conditions match the context determines the value
  5. 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:

OperatorDescriptionExample
equalsExact match (compared as strings){"attribute": "plan", "operator": "equals", "value": "pro"}
not_equalsDoes not match (compared as strings){"attribute": "plan", "operator": "not_equals", "value": "free"}
containsString contains substring{"attribute": "email", "operator": "contains", "value": "@company.com"}
not_containsString does not contain substring{"attribute": "email", "operator": "not_contains", "value": "test"}
starts_withString starts with prefix{"attribute": "user_id", "operator": "starts_with", "value": "usr_"}
ends_withString ends with suffix{"attribute": "domain", "operator": "ends_with", "value": ".io"}
inValue is in array OR comma-separated string{"attribute": "country", "operator": "in", "value": ["US", "CA", "GB"]}
not_inValue is not in array / list{"attribute": "country", "operator": "not_in", "value": "CN,RU"}
greater_thanNumeric comparison (parses both sides as floats){"attribute": "age", "operator": "greater_than", "value": "18"}
less_thanNumeric comparison (parses both sides as floats){"attribute": "age", "operator": "less_than", "value": "65"}
regexPattern match — see safety notes below{"attribute": "email", "operator": "regex", "value": "^[^@]+@krafter\\."}
is_trueTruthy: true, "true", or "1"{"attribute": "beta", "operator": "is_true", "value": null}
is_falseAnything not truthy (incl. nil){"attribute": "beta", "operator": "is_false", "value": null}
existsAttribute is present and non-nil{"attribute": "user_id", "operator": "exists", "value": null}
not_existsAttribute 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 are not_in (returns true), not_exists (returns true), is_false (returns true because nil is not truthy), and exists (returns false).
  • 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 typeBehavior
allAll rules must match (AND)
anyAt 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 ​

Built by Krafter Studio