Skip to content

Targeting Rules

Targeting rules enable you to control which users see a feature based on their attributes. Instead of a simple on/off toggle, you can create rules like “enable for users with @acme.com emails” or “enable for enterprise plan customers.”

How Targeting Works

When you call evaluate() with a user context, Featsync evaluates targeting rules server-side:

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your App │ │ Featsync │ │ Database │
│ │ │ Server │ │ │
│ evaluate( │───>│ 1. Validate │───>│ Fetch flag & │
│ "feature", │ │ 2. Get rules │ │ targeting │
│ { context } │ │ 3. Evaluate │<───│ rules │
│ ) │<───│ 4. Return │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
  1. Your app sends the flag key and user context
  2. Server fetches the flag and its targeting rules
  3. Rules are evaluated in priority order
  4. Result is returned with enabled state and reason

Rule Structure

Each targeting rule has:

  • Name: Human-readable description (e.g., “Beta Testers”)
  • Priority: Evaluation order (lower = higher priority)
  • Conditions: What must be true for the rule to match
  • Serve: What value to return when matched

Conditions

Conditions are groups of comparisons with AND/OR logic:

┌─────────────────────────────────────────────────────────────┐
│ Rule: Enterprise Users in US │
├─────────────────────────────────────────────────────────────┤
│ Conditions: ALL must match │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ email ends_with "@acme.com" │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ country equals "US" │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ custom.plan equals "enterprise" │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Serve: Enabled │
└─────────────────────────────────────────────────────────────┘

Evaluation Flow

Rules are evaluated in priority order. The first matching rule determines the result:

Request: evaluate("new-checkout", { email: "john@beta.com", country: "CA" })
Priority 1: Beta Testers
├─ email ends_with "@beta.com" → ✓ MATCH
└─ Result: ENABLED
Priority 2: US Enterprise (skipped - already matched)
Priority 3: Default 20% rollout (skipped - already matched)
Final: enabled: true, reason: "rule"

Rule Examples

Internal Beta Testing

Release to employees before public launch:

RuleConditionsServe
Internal Teamemail ends_with "@yourcompany.com"Enabled
Default(no rules match)Disabled

Gradual Rollout by Region

Roll out to specific countries first:

PriorityRuleConditionsServe
1US Launchcountry equals "US"Enabled
2Canadacountry equals "CA"50% rollout
-Default(no match)Disabled

Feature Gating by Plan

Enable premium features for paying customers:

RuleConditionsServe
Enterprisecustom.plan equals "enterprise"Enabled
Pro Userscustom.plan equals "pro"Enabled
Default(no match)Disabled

A/B Testing by Cohort

Split users into test groups:

PriorityRuleConditionsServe
1Test Group Acustom.test_group equals "A"Enabled
2Test Group Bcustom.test_group equals "B"Disabled
-Default(no match)50% rollout

Default Behavior

When no targeting rules match, the flag falls back to:

  1. Percentage rollout: If the flag type is “percentage” and has a percentage set, it’s applied using the userId for consistency
  2. “Enable for everyone else” setting: Each environment has a toggle that controls whether non-matching users get the flag enabled or disabled

Enable for Everyone Else

When you add targeting rules, you can control what happens for users who don’t match any rules:

SettingBehavior
On (default)Users who don’t match any rule get the flag enabled
OffUsers who don’t match any rule get the flag disabled

This allows two common targeting patterns:

Allowlist mode (“Enable for everyone else” = Off):

  • Only users matching rules get the feature
  • Everyone else is excluded
  • Example: Beta testers only

Blocklist mode (“Enable for everyone else” = On):

  • Everyone gets the feature by default
  • Rules can disable the feature for specific users
  • Example: Disable for specific regions

Percentage Within Rules

Rules can have their own percentage rollout:

Rule: Beta Testers - 20% Rollout
├─ Conditions: email ends_with "@beta.com"
└─ Serve: 20% enabled (using userId for consistency)

This lets you gradually roll out to a specific group without affecting other users.

Why Server-Side Evaluation?

Featsync evaluates targeting rules on the server for several reasons:

Security

User attributes stay on the server. Your targeting rules aren’t exposed to clients.

Consistency

All SDKs get the same evaluation logic. No client-side inconsistencies.

Flexibility

Change rules without updating client code. Rules take effect immediately.

Accuracy

Complex evaluations (regex, semver) are done reliably server-side.

Best Practices

1. Order Rules by Specificity

Put more specific rules at higher priority:

✓ Good
Priority 1: VIP customers (specific)
Priority 2: Pro plan (less specific)
Priority 3: All users 10% (least specific)
✗ Bad
Priority 1: All users 10% (matches everyone first!)
Priority 2: VIP customers (never reached)

2. Use Meaningful Names

Name rules descriptively for easier debugging:

✓ Good: "Enterprise customers in US"
✗ Bad: "Rule 1"

3. Include userId for Consistency

Always pass userId so users get consistent experiences:

// Same user always gets same result
await featsync.evaluate('feature', {
userId: user.id, // ← Important!
email: user.email,
});

4. Test Your Rules

Before releasing:

  1. Test with users who should match
  2. Test with users who shouldn’t match
  3. Check the reason field to verify which rule matched
const result = await featsync.evaluateWithDetails('feature', context);
console.log(result.reason); // "rule", "segment", "percentage", "default"
console.log(result.ruleName); // Which rule matched

Debugging Tips

Check the Reason

The reason field tells you why a flag was enabled/disabled:

ReasonWhat it means
ruleA targeting rule matched
segmentA segment rule matched
percentagePercentage rollout applied
defaultNo rules matched, using default
disabledFlag is disabled in environment

Verify Attribute Names

Targeting won’t match if attribute names don’t match exactly:

// In your code
custom: { userPlan: "enterprise" }
// In your rule
custom.user_plan equals "enterprise" // ✗ Won't match! (underscore vs camelCase)

Check Data Types

Operators are type-sensitive:

// Number operators need numbers
custom: { age: "25" } // String
custom.age gt 18 // ✗ Comparing string to number
custom: { age: 25 } // Number
custom.age gt 18 // ✓ Works