Skip to main content

Targeting Rules

Advanced

Define who sees your features with user segments, percentage rollouts, and rule-based targeting. Combine multiple conditions for precise control.

User Segments

Target by plan, country, age, or custom properties

Percentage Rollout

Gradual release from 1% to 100%

Consistent Bucketing

Users stay in same group across rollouts

Rule Priority

First matching rule wins

Percentage-Based Rollouts

Release features gradually to a percentage of users. Start with a small group and increase as confidence grows.

Gradual Rollout
// Create a flag with 10% rollout
await platform.flags.create({
  key: 'new_dashboard',
  name: 'New Dashboard',
  enabled: true,
  rolloutPercentage: 10, // 10% of users
})

// Monitor metrics, then increase
await platform.flags.update('new_dashboard', {
  rolloutPercentage: 25, // Increase to 25%
})

// Continue until full rollout
await platform.flags.update('new_dashboard', {
  rolloutPercentage: 50,  // Then 50%
})

await platform.flags.update('new_dashboard', {
  rolloutPercentage: 100, // Full release
})

How Bucketing Works

Users are assigned a hash based on their ID. This hash determines their bucket (0-100). If your rollout is 25%, users with hash 0-24 see the feature. Increasing to 50% adds users 25-49 while keeping the original 25%.

User Property Targeting

Target users based on their properties like plan, country, email domain, or any custom attribute you track.

Property Targeting
// Check flag with user context
const isEnabled = await platform.flags.isEnabled('premium_feature', {
  userId: user.id,
  context: {
    // Standard properties
    email: user.email,
    plan: user.subscription?.plan,
    country: user.country,

    // Custom properties
    companySize: user.company?.size,
    accountAge: daysSinceCreated(user.createdAt),
    totalOrders: user.stats.orders,
  },
})

// Create a flag targeting pro users
await platform.flags.create({
  key: 'advanced_analytics',
  name: 'Advanced Analytics',
  targeting: {
    rules: [
      {
        name: 'Pro Users',
        conditions: [
          { field: 'plan', operator: 'in', value: ['pro', 'enterprise'] }
        ],
        percentage: 100, // 100% of pro users
      },
    ],
    defaultPercentage: 0, // Others don't see it
  },
})

Available Operators

OperatorDescriptionExample
equalsExact matchplan equals 'pro'
notEqualsDoes not equalstatus notEquals 'banned'
inValue in arraycountry in ['US', 'CA', 'GB']
notInValue not in arrayrole notIn ['guest', 'trial']
containsString containsemail contains '@company'
startsWithString prefixuserId startsWith 'test_'
endsWithString suffixemail endsWith '@company.com'
matchesRegex matchemail matches '^[a-z]+@beta\.'
gt / gteGreater thanage gte 18
lt / lteLess thandaysActive lt 30
existsProperty existssubscription exists true
semverVersion comparisonappVersion semver '>=2.0.0'

Segment-Based Targeting

Create reusable segments to target groups of users across multiple flags. Define once, use everywhere.

// Create reusable segments
await platform.segments.create({
  key: 'beta_users',
  name: 'Beta Program Users',
  conditions: [
    { field: 'plan', operator: 'equals', value: 'beta' }
  ],
})

await platform.segments.create({
  key: 'enterprise_accounts',
  name: 'Enterprise Accounts',
  conditions: [
    { field: 'plan', operator: 'equals', value: 'enterprise' },
    { field: 'companySize', operator: 'gte', value: 100 },
  ],
})

await platform.segments.create({
  key: 'internal_team',
  name: 'Internal Team',
  conditions: [
    { field: 'email', operator: 'endsWith', value: '@sylphx.dev' }
  ],
})

Segment Best Practices

Create segments for commonly targeted groups like beta users, enterprise accounts, or internal team. This keeps your flag rules clean and ensures consistent targeting across features.

Consistent Bucketing

Users are assigned to consistent buckets based on a hash of their ID. This ensures stable experiences even as you adjust rollout percentages.

Deterministic Assignment

Same user + same flag = same bucket, every time. No database lookups needed.

Additive Rollout

Increasing from 10% to 20% adds 10% of users without removing the original 10%.

Experiment Isolation

Different flags use different hash seeds, so experiment assignments are independent.

Bucketing Example
// How bucketing works internally
function getBucket(userId: string, flagKey: string): number {
  // Create a deterministic hash from userId + flagKey
  const hash = murmurhash3(`${flagKey}:${userId}`)

  // Convert to 0-100 range
  return hash % 100
}

// User "user_123" with flag "new_feature"
const bucket = getBucket('user_123', 'new_feature') // e.g., 42

// At 10% rollout: bucket 42 > 10, user doesn't see feature
// At 50% rollout: bucket 42 < 50, user now sees feature
// At 25% rollout: bucket 42 > 25, user doesn't see feature
//   (going backwards removes users from the feature)

// For predictable rollouts, only increase percentages

Rollback Considerations

Reducing the rollout percentage will remove users from the feature. If a user at bucket 42 had the feature at 50% rollout, they lose it at 25% rollout. Consider using a kill switch instead for emergency disables.

Combining Rules (AND/OR)

Build complex targeting logic by combining multiple conditions with AND/OR operators.

// All conditions must match (AND)
{
  name: 'US Enterprise Users',
  conditions: [
    // Must match ALL of these
    { field: 'country', operator: 'equals', value: 'US' },
    { field: 'plan', operator: 'equals', value: 'enterprise' },
    { field: 'verified', operator: 'equals', value: true },
  ],
  percentage: 100,
}

// Only users who are:
// - In the US AND
// - On enterprise plan AND
// - Have verified email

Priority and Rule Ordering

Rules are evaluated in order from top to bottom. The first matching rule determines the outcome, so order matters.

Evaluation Flow
1
Kill Switch-If flag is disabled globally, return false
2
Rule 1-Check conditions, if match apply percentage
3
Rule 2-If Rule 1 didnt match, check Rule 2
4
Rule N-Continue through all rules
5
Default-If no rules match, use default percentage
Rule Ordering Example
// Order matters! Put specific rules first
targeting: {
  rules: [
    // Most specific first: blocked users never see features
    {
      name: 'Blocked Users',
      conditions: [{ field: 'status', operator: 'equals', value: 'blocked' }],
      percentage: 0, // Always disabled
    },

    // Internal team always gets features
    {
      name: 'Internal Team',
      conditions: [{ field: 'email', operator: 'endsWith', value: '@company.com' }],
      percentage: 100,
    },

    // Enterprise gets priority access
    {
      name: 'Enterprise',
      conditions: [{ field: 'plan', operator: 'equals', value: 'enterprise' }],
      percentage: 100,
    },

    // Pro users get gradual rollout
    {
      name: 'Pro Users',
      conditions: [{ field: 'plan', operator: 'equals', value: 'pro' }],
      percentage: 50,
    },

    // Free users get slower rollout
    {
      name: 'Free Users',
      conditions: [{ field: 'plan', operator: 'equals', value: 'free' }],
      percentage: 10,
    },
  ],
  // Catch-all for any unmatched users
  defaultPercentage: 0,
}

Rule Ordering Best Practices

  • Put deny rules (blocked users) at the top
  • Put internal/testing rules next for easy overrides
  • Order by priority: enterprise, pro, free
  • Use default percentage as a catch-all
  • Keep rules to 5-7 maximum for maintainability

API Reference

MethodDescription
segments.create(config)Create a reusable user segment
segments.update(key, config)Update segment conditions
segments.delete(key)Delete a segment
segments.evaluate(key, ctx)Check if user matches segment
flags.targeting.addRule()Add a rule to flag targeting
flags.targeting.reorder()Change rule priority order