Security
User attributes stay on the server. Your targeting rules aren’t exposed to clients.
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.”
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 │ │ │└─────────────────┘ └─────────────────┘ └─────────────────┘Each targeting rule has:
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 │└─────────────────────────────────────────────────────────────┘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"Release to employees before public launch:
| Rule | Conditions | Serve |
|---|---|---|
| Internal Team | email ends_with "@yourcompany.com" | Enabled |
| Default | (no rules match) | Disabled |
Roll out to specific countries first:
| Priority | Rule | Conditions | Serve |
|---|---|---|---|
| 1 | US Launch | country equals "US" | Enabled |
| 2 | Canada | country equals "CA" | 50% rollout |
| - | Default | (no match) | Disabled |
Enable premium features for paying customers:
| Rule | Conditions | Serve |
|---|---|---|
| Enterprise | custom.plan equals "enterprise" | Enabled |
| Pro Users | custom.plan equals "pro" | Enabled |
| Default | (no match) | Disabled |
Split users into test groups:
| Priority | Rule | Conditions | Serve |
|---|---|---|---|
| 1 | Test Group A | custom.test_group equals "A" | Enabled |
| 2 | Test Group B | custom.test_group equals "B" | Disabled |
| - | Default | (no match) | 50% rollout |
When no targeting rules match, the flag falls back to:
userId for consistencyWhen you add targeting rules, you can control what happens for users who don’t match any rules:
| Setting | Behavior |
|---|---|
| On (default) | Users who don’t match any rule get the flag enabled |
| Off | Users who don’t match any rule get the flag disabled |
This allows two common targeting patterns:
Allowlist mode (“Enable for everyone else” = Off):
Blocklist mode (“Enable for everyone else” = On):
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.
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
Put more specific rules at higher priority:
✓ GoodPriority 1: VIP customers (specific)Priority 2: Pro plan (less specific)Priority 3: All users 10% (least specific)
✗ BadPriority 1: All users 10% (matches everyone first!)Priority 2: VIP customers (never reached)Name rules descriptively for easier debugging:
✓ Good: "Enterprise customers in US"✗ Bad: "Rule 1"Always pass userId so users get consistent experiences:
// Same user always gets same resultawait featsync.evaluate('feature', { userId: user.id, // ← Important! email: user.email,});Before releasing:
reason field to verify which rule matchedconst result = await featsync.evaluateWithDetails('feature', context);console.log(result.reason); // "rule", "segment", "percentage", "default"console.log(result.ruleName); // Which rule matchedThe reason field tells you why a flag was enabled/disabled:
| Reason | What it means |
|---|---|
rule | A targeting rule matched |
segment | A segment rule matched |
percentage | Percentage rollout applied |
default | No rules matched, using default |
disabled | Flag is disabled in environment |
Targeting won’t match if attribute names don’t match exactly:
// In your codecustom: { userPlan: "enterprise" }
// In your rulecustom.user_plan equals "enterprise" // ✗ Won't match! (underscore vs camelCase)Operators are type-sensitive:
// Number operators need numberscustom: { age: "25" } // Stringcustom.age gt 18 // ✗ Comparing string to number
custom: { age: 25 } // Numbercustom.age gt 18 // ✓ Works