Skip to content

Targeting Rules

Targeting rules let you enable flags for specific users based on attributes like email, userId, country, or custom properties. This is more powerful than percentage rollouts because you can precisely control who sees a feature.

When to Use Targeting

Use targeting rules when you want to:

  • Release features to specific users (beta testers, employees)
  • Enable features based on user attributes (enterprise plans, specific regions)
  • Gradually roll out to specific groups before general availability
  • A/B test with specific user segments

Basic Usage

The evaluate() method evaluates targeting rules locally using cached flag data:

import { Featsync } from 'featsync';
const featsync = new Featsync({ apiKey: 'fs_your_api_key' });
// Evaluate with user context
const enabled = await featsync.evaluate('new-checkout', {
userId: 'user_123',
email: 'john@acme.com',
country: 'US',
});
if (enabled) {
showNewCheckout();
}

User Context

The evaluation context contains user attributes that are compared against your targeting rules:

interface EvaluationContext {
// Standard attributes
userId?: string; // User identifier
email?: string; // User's email
country?: string; // ISO country code (e.g., "US", "GB")
// Custom attributes
custom?: Record<string, string | number | boolean>;
}

Standard Attributes

AttributeTypeDescriptionExample
userIdstringUnique user identifier"user_123"
emailstringUser’s email address"john@acme.com"
countrystringISO country code"US", "GB", "DE"

Custom Attributes

Pass any custom attributes in the custom object:

const enabled = await featsync.evaluate('enterprise-feature', {
userId: currentUser.id,
email: currentUser.email,
custom: {
plan: 'enterprise', // String
company_size: 500, // Number
beta_tester: true, // Boolean
app_version: '2.1.0', // Semver
roles: 'admin,editor', // Comma-separated list
},
});

In your targeting rules, access custom attributes with the custom. prefix:

  • custom.plan equals "enterprise"
  • custom.company_size greater than 100
  • custom.beta_tester equals true

Detailed Results

Use evaluateWithDetails() to get metadata about why a flag was enabled/disabled:

const result = await featsync.evaluateWithDetails('new-checkout', {
userId: 'user_123',
email: 'john@acme.com',
});
console.log(result.enabled); // true
console.log(result.reason); // "rule"
console.log(result.ruleName); // "Enterprise users"
console.log(result.ruleId); // "rule_abc123"

Evaluation Reasons

ReasonDescription
defaultNo rules matched, using flag’s default state
ruleA targeting rule matched
segmentA segment rule matched (Pro)
percentagePercentage rollout applied
disabledFlag is disabled in this environment
not_foundFlag does not exist
errorEvaluation error occurred

Supported Operators

String Operators

OperatorDescriptionExample
equalsExact matchemail equals "admin@acme.com"
not_equalsNot equalcountry not_equals "CN"
containsSubstring matchemail contains "@acme.com"
not_containsNo substringemail not_contains "test"
starts_withPrefix matchemail starts_with "admin"
ends_withSuffix matchemail ends_with "@acme.com"
in_listValue in listcountry in ["US", "CA", "UK"]
not_in_listNot in listcountry not_in ["CN", "RU"]
matches_regexRegex matchemail matches ".*@(acme|corp)\\.com"

Number Operators

OperatorDescriptionExample
eqEqualcustom.age eq 18
neqNot equalcustom.age neq 0
gtGreater thancustom.age gt 18
gteGreater or equalcustom.requests gte 100
ltLess thancustom.age lt 65
lteLess or equalcustom.requests lte 1000

Semver Operators

OperatorDescriptionExample
semver_eqVersion equalcustom.app_version semver_eq "2.0.0"
semver_neqVersion not equalcustom.app_version semver_neq "1.0.0"
semver_gtVersion greatercustom.app_version semver_gt "2.0.0"
semver_gteVersion greater or equalcustom.app_version semver_gte "1.5.0"
semver_ltVersion lesscustom.app_version semver_lt "3.0.0"
semver_lteVersion less or equalcustom.app_version semver_lte "2.9.9"

AND/OR Logic

Rules can be combined with AND (all) or OR (any) logic:

AND (all): All conditions must match

IF email ends_with "@acme.com" AND country equals "US"
THEN enable

OR (any): At least one condition must match

IF email ends_with "@beta.com" OR custom.beta_tester equals true
THEN enable

Evaluation Priority

Rules are evaluated in priority order (lower number = higher priority):

  1. Priority 1: Beta testers rule
  2. Priority 2: Enterprise users rule
  3. Priority 3: US users rule
  4. Default: “Enable for everyone else” setting (if no rules match)

The first matching rule wins. If no rules match, the “Enable for everyone else” setting determines if the flag is enabled or disabled for that user.

Enable for Everyone Else

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

  • 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 toggle only appears in the dashboard when you have targeting rules configured. It allows you to create:

  • Allowlist: Only users matching rules get the feature (toggle Off)
  • Blocklist: Everyone gets the feature except those matching “disable” rules (toggle On)

Plan Limits

FeatureFreePro
Targeting rules2 per flagUnlimited
Segments-Unlimited
All operatorsYesYes

Migration from isEnabled

If you’re currently using isEnabled(), you can gradually migrate to evaluate():

// Simple check (no targeting)
const enabled = await featsync.isEnabled("new-checkout");

Both methods work for the same flags. Use isEnabled() for simple boolean checks, and evaluate() when you need targeting.

Best Practices

1. Include userId for Consistency

Always include userId to ensure users get consistent flag values:

// Good - user gets consistent experience
const enabled = await featsync.evaluate('feature', {
userId: currentUser.id,
// ... other attributes
});

2. Use Specific Attributes

Be specific with your attributes to make targeting rules clearer:

// Good - clear attributes
custom: {
subscription_plan: "enterprise",
company_size: 500
}
// Avoid - vague attributes
custom: {
type: "big",
level: "high"
}

3. Handle Errors Gracefully

evaluate() returns false on errors, but you can provide a default:

// Custom default value
const enabled = await featsync.evaluate('feature', context, true);

4. Debug with evaluateWithDetails

Use evaluateWithDetails() during development to understand targeting:

if (process.env.NODE_ENV === 'development') {
const result = await featsync.evaluateWithDetails('feature', context);
console.log(`Flag: ${result.enabled}, Reason: ${result.reason}`);
}

How Client-Side Evaluation Works

The SDK uses client-side evaluation for optimal performance:

  1. Fetch once: When you call evaluate(), the SDK fetches all flags with their targeting rules
  2. Cache locally: Flags and rules are cached (default: 5 minutes)
  3. Evaluate locally: Targeting rules are evaluated in your app, not on the server
  4. No extra calls: Subsequent evaluate() calls use the cache
First call:
App → SDK → API (fetches flags + rules) → Cache
Local evaluation → Result
Subsequent calls:
App → SDK → Cache
Local evaluation → Result (no API call!)

This approach minimizes API calls while providing instant flag evaluations.