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