or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-configuration.mderror-tracking.mdevent-tracking.mdexperimental.mdexpress-integration.mdfeature-flags.mdidentification.mdindex.mdsentry-integration.md
tile.json

feature-flags.mddocs/

Feature Flags

Feature flags in PostHog enable you to control feature rollouts, run A/B tests, and manage remote configuration without deploying new code. The Node.js SDK supports both local and remote evaluation of feature flags with extensive configuration options.

Capabilities

Feature Flag Evaluation

Check if a feature flag is enabled for a specific user.

/**
 * Check if a feature flag is enabled for a specific user
 * @param key - The feature flag key
 * @param distinctId - The user's distinct ID
 * @param options - Optional configuration for flag evaluation
 * @returns Promise that resolves to true if enabled, false if disabled, undefined if not found
 */
async isFeatureEnabled(
  key: string,
  distinctId: string,
  options?: {
    groups?: Record<string, string>
    personProperties?: Record<string, string>
    groupProperties?: Record<string, Record<string, string>>
    onlyEvaluateLocally?: boolean
    sendFeatureFlagEvents?: boolean
    disableGeoip?: boolean
  }
): Promise<boolean | undefined>

Usage Examples:

import { PostHog } from 'posthog-node'

const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com'
})

// Basic feature flag check
const isEnabled = await client.isFeatureEnabled('new-feature', 'user_123')
if (isEnabled) {
  // Feature is enabled
  console.log('New feature is active')
} else {
  // Feature is disabled
  console.log('New feature is not active')
}

// With groups and properties
const isEnabled = await client.isFeatureEnabled('org-feature', 'user_123', {
  groups: { organization: 'acme-corp' },
  personProperties: { plan: 'enterprise' }
})

// Only evaluate locally (no server call if local evaluation not ready)
const isEnabled = await client.isFeatureEnabled('quick-check', 'user_123', {
  onlyEvaluateLocally: true
})

Get Feature Flag Value

Get the value of a feature flag for multivariate flags or string-based flags.

/**
 * Get the value of a feature flag for a specific user
 * @param key - The feature flag key
 * @param distinctId - The user's distinct ID
 * @param options - Optional configuration for flag evaluation
 * @returns Promise that resolves to the flag value (boolean | string) or undefined
 */
async getFeatureFlag(
  key: string,
  distinctId: string,
  options?: {
    groups?: Record<string, string>
    personProperties?: Record<string, string>
    groupProperties?: Record<string, Record<string, string>>
    onlyEvaluateLocally?: boolean
    sendFeatureFlagEvents?: boolean
    disableGeoip?: boolean
  }
): Promise<FeatureFlagValue | undefined>

type FeatureFlagValue = boolean | string

Usage Examples:

// Basic feature flag check
const flagValue = await client.getFeatureFlag('new-feature', 'user_123')
if (flagValue === 'variant-a') {
  // Show variant A
} else if (flagValue === 'variant-b') {
  // Show variant B
} else {
  // Flag is disabled or not found
}

// With groups and properties for targeted rollout
const flagValue = await client.getFeatureFlag('org-feature', 'user_123', {
  groups: { organization: 'acme-corp' },
  personProperties: { plan: 'enterprise' },
  groupProperties: { organization: { tier: 'premium' } }
})

// Only evaluate locally
const flagValue = await client.getFeatureFlag('local-flag', 'user_123', {
  onlyEvaluateLocally: true
})

// Disable feature flag events
const flagValue = await client.getFeatureFlag('silent-flag', 'user_123', {
  sendFeatureFlagEvents: false // Don't send $feature_flag_called event
})

Get Feature Flag Payload

Get the JSON payload attached to a feature flag.

/**
 * Get the payload for a feature flag
 * @param key - The feature flag key
 * @param distinctId - The user's distinct ID
 * @param matchValue - Optional match value to get payload for specific variant
 * @param options - Optional configuration for flag evaluation
 * @returns Promise that resolves to the flag payload or undefined
 */
async getFeatureFlagPayload(
  key: string,
  distinctId: string,
  matchValue?: FeatureFlagValue,
  options?: {
    groups?: Record<string, string>
    personProperties?: Record<string, string>
    groupProperties?: Record<string, Record<string, string>>
    onlyEvaluateLocally?: boolean
    sendFeatureFlagEvents?: boolean
    disableGeoip?: boolean
  }
): Promise<JsonType | undefined>

type JsonType = string | number | boolean | null | JsonType[] | { [key: string]: JsonType }

Usage Examples:

// Get payload for a feature flag
const payload = await client.getFeatureFlagPayload('flag-key', 'user_123')
if (payload) {
  console.log('Flag payload:', payload)
  // payload can be any JSON structure: string, number, boolean, object, array
}

// Get payload with specific match value
const payload = await client.getFeatureFlagPayload('flag-key', 'user_123', 'variant-a')
// This gets the payload specifically for variant-a

// With groups and properties
const payload = await client.getFeatureFlagPayload('org-flag', 'user_123', undefined, {
  groups: { organization: 'acme-corp' },
  personProperties: { plan: 'enterprise' }
})

// Example: Using payload for configuration
const payload = await client.getFeatureFlagPayload('button-config', 'user_123')
if (payload && typeof payload === 'object') {
  const config = payload as { color: string; text: string; size: string }
  console.log(`Button color: ${config.color}`)
}

Get Remote Config Payload

Get remote configuration payload for encrypted feature flags.

/**
 * Get the remote config payload for a feature flag
 * @param flagKey - The feature flag key
 * @returns Promise that resolves to the remote config payload or undefined
 * @throws Error if personal API key is not provided
 */
async getRemoteConfigPayload(flagKey: string): Promise<JsonType | undefined>

Usage Examples:

// Initialize client with personal API key (required)
const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com',
  personalApiKey: 'phx_...' // Required for remote config
})

// Get remote config payload
const payload = await client.getRemoteConfigPayload('flag-key')
if (payload) {
  console.log('Remote config payload:', payload)
}

// Example: Using remote config for sensitive settings
const dbConfig = await client.getRemoteConfigPayload('database-config')
if (dbConfig && typeof dbConfig === 'object') {
  const config = dbConfig as { host: string; port: number; name: string }
  // Use encrypted config values
  connectToDatabase(config)
}

Get All Flags

Get all feature flag values for a specific user.

/**
 * Get all feature flag values for a specific user
 * @param distinctId - The user's distinct ID
 * @param options - Optional configuration for flag evaluation
 * @returns Promise that resolves to a record of flag keys and their values
 */
async getAllFlags(
  distinctId: string,
  options?: {
    groups?: Record<string, string>
    personProperties?: Record<string, string>
    groupProperties?: Record<string, Record<string, string>>
    onlyEvaluateLocally?: boolean
    disableGeoip?: boolean
    flagKeys?: string[]
  }
): Promise<Record<string, FeatureFlagValue>>

Usage Examples:

// Get all flags for a user
const allFlags = await client.getAllFlags('user_123')
console.log('User flags:', allFlags)
// Output: { 'flag-1': 'variant-a', 'flag-2': false, 'flag-3': 'variant-b' }

// Check multiple flags efficiently
if (allFlags['new-ui']) {
  // Show new UI
}
if (allFlags['experimental-feature'] === 'test') {
  // Enable test mode
}

// With specific flag keys (only evaluate these flags)
const specificFlags = await client.getAllFlags('user_123', {
  flagKeys: ['flag-1', 'flag-2', 'flag-3']
})

// With groups and properties
const orgFlags = await client.getAllFlags('user_123', {
  groups: { organization: 'acme-corp' },
  personProperties: { plan: 'enterprise' },
  groupProperties: { organization: { employees: 500 } }
})

// Only use local evaluation
const localFlags = await client.getAllFlags('user_123', {
  onlyEvaluateLocally: true
})

Get All Flags and Payloads

Get all feature flag values and their payloads for a specific user.

/**
 * Get all feature flag values and payloads for a specific user
 * @param distinctId - The user's distinct ID
 * @param options - Optional configuration for flag evaluation
 * @returns Promise that resolves to flags and payloads
 */
async getAllFlagsAndPayloads(
  distinctId: string,
  options?: {
    groups?: Record<string, string>
    personProperties?: Record<string, string>
    groupProperties?: Record<string, Record<string, string>>
    onlyEvaluateLocally?: boolean
    disableGeoip?: boolean
    flagKeys?: string[]
  }
): Promise<PostHogFlagsAndPayloadsResponse>

interface PostHogFlagsAndPayloadsResponse {
  featureFlags: Record<string, FeatureFlagValue>
  featureFlagPayloads: Record<string, JsonType>
}

Usage Examples:

// Get all flags and payloads for a user
const result = await client.getAllFlagsAndPayloads('user_123')
console.log('Flags:', result.featureFlags)
console.log('Payloads:', result.featureFlagPayloads)

// Use flags and payloads together
if (result.featureFlags['new-button']) {
  const config = result.featureFlagPayloads['new-button']
  if (config && typeof config === 'object') {
    renderButton(config as { color: string; text: string })
  }
}

// With specific flag keys
const result = await client.getAllFlagsAndPayloads('user_123', {
  flagKeys: ['flag-1', 'flag-2']
})

// Only evaluate locally
const result = await client.getAllFlagsAndPayloads('user_123', {
  onlyEvaluateLocally: true
})

// With groups for organization-level flags
const result = await client.getAllFlagsAndPayloads('user_123', {
  groups: { organization: 'acme-corp', team: 'engineering' }
})

Local Evaluation Status

Check if local evaluation is ready for feature flags.

/**
 * Check if local evaluation of feature flags is ready
 * @returns true if local evaluation is ready, false otherwise
 */
isLocalEvaluationReady(): boolean

Usage Examples:

// Check if ready
if (client.isLocalEvaluationReady()) {
  // Local evaluation is ready, can evaluate flags locally
  const flag = await client.getFeatureFlag('flag-key', 'user_123')
} else {
  // Local evaluation not ready, will use remote evaluation
  const flag = await client.getFeatureFlag('flag-key', 'user_123')
}

// Use in conditional logic
const evaluationMode = client.isLocalEvaluationReady() ? 'local' : 'remote'
console.log(`Using ${evaluationMode} evaluation`)

Wait for Local Evaluation

Wait for local evaluation to be ready with optional timeout.

/**
 * Wait for local evaluation of feature flags to be ready
 * @param timeoutMs - Timeout in milliseconds (default: 30000)
 * @returns Promise that resolves to true if ready, false if timed out
 */
async waitForLocalEvaluationReady(timeoutMs?: number): Promise<boolean>

Usage Examples:

// Wait for local evaluation
const isReady = await client.waitForLocalEvaluationReady()
if (isReady) {
  console.log('Local evaluation is ready')
  // Now safe to use flags with onlyEvaluateLocally: true
} else {
  console.log('Local evaluation timed out')
}

// Wait with custom timeout
const isReady = await client.waitForLocalEvaluationReady(10000) // 10 seconds
if (isReady) {
  // Proceed with local evaluation
}

// Use at application startup
async function initializeApp() {
  const client = new PostHog('your-api-key', {
    host: 'https://app.posthog.com',
    personalApiKey: 'phx_...'
  })

  // Wait for flags to be ready before serving requests
  await client.waitForLocalEvaluationReady(5000)

  // Start server
  app.listen(3000)
}

Reload Feature Flags

Force reload feature flag definitions from the server.

/**
 * Reload feature flag definitions from the server for local evaluation
 * @returns Promise that resolves when flags are reloaded
 */
async reloadFeatureFlags(): Promise<void>

Usage Examples:

// Force reload of feature flags
await client.reloadFeatureFlags()
console.log('Feature flags reloaded')

// Reload before checking a specific flag
await client.reloadFeatureFlags()
const flag = await client.getFeatureFlag('flag-key', 'user_123')

// Use in a scheduled job
setInterval(async () => {
  await client.reloadFeatureFlags()
  console.log('Flags refreshed')
}, 60000) // Refresh every minute

// Reload on demand (e.g., webhook endpoint)
app.post('/webhooks/flags-updated', async (req, res) => {
  await client.reloadFeatureFlags()
  res.json({ success: true })
})

Local vs Remote Evaluation

PostHog feature flags can be evaluated in two ways: locally or remotely. Understanding the difference is crucial for optimal performance.

Local Evaluation

Local evaluation evaluates feature flags within your application using cached flag definitions. This is faster and reduces API calls.

Requirements:

  • Personal API key must be provided in initialization
  • Flag definitions are fetched and cached periodically

Benefits:

  • Near-instant flag evaluation (no network call)
  • Reduced load on PostHog servers
  • Works offline with cached flags
  • Lower latency for feature checks

Setup:

const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com',
  personalApiKey: 'phx_...', // Required for local evaluation
  enableLocalEvaluation: true, // Default: true
  featureFlagsPollingInterval: 30000 // Default: 30 seconds (minimum: 100ms)
})

// Wait for flags to load
await client.waitForLocalEvaluationReady()

// Now flags are evaluated locally
const flag = await client.getFeatureFlag('flag-key', 'user_123')

Example:

// Initialize with local evaluation
const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com',
  personalApiKey: 'phx_...',
  featureFlagsPollingInterval: 60000 // Poll every 60 seconds
})

// Check readiness
console.log('Local eval ready:', client.isLocalEvaluationReady())

// Wait for ready state
const ready = await client.waitForLocalEvaluationReady(5000)
if (ready) {
  // Fast local evaluation
  const flag = await client.getFeatureFlag('my-flag', 'user_123')
}

Remote Evaluation

Remote evaluation makes an API call to PostHog servers for each flag check. This ensures you always have the latest flag values but is slower.

Use When:

  • Personal API key not available
  • Need real-time flag updates
  • Flag depends on server-side user properties
  • GeoIP-based targeting is required

Setup:

// Without personal API key, uses remote evaluation
const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com'
})

// Each call makes an API request
const flag = await client.getFeatureFlag('flag-key', 'user_123')

Example:

// Remote evaluation with user properties
const flag = await client.getFeatureFlag('premium-feature', 'user_123', {
  personProperties: {
    email: 'user@example.com',
    plan: 'premium'
  }
})

// Remote evaluation with GeoIP
const flag = await client.getFeatureFlag('region-feature', 'user_123', {
  disableGeoip: false // Allow GeoIP-based targeting
})

Hybrid Approach

You can combine both approaches for optimal performance:

const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com',
  personalApiKey: 'phx_...'
})

// Try local first, fallback to remote if needed
async function getFlag(key: string, userId: string) {
  if (client.isLocalEvaluationReady()) {
    // Use local evaluation
    return await client.getFeatureFlag(key, userId, {
      onlyEvaluateLocally: false // Allow fallback to remote
    })
  } else {
    // Use remote evaluation
    return await client.getFeatureFlag(key, userId)
  }
}

Feature Flag Options

All feature flag methods support a rich set of options for advanced use cases.

Groups

Associate the flag check with groups (e.g., organizations, teams).

const flag = await client.getFeatureFlag('org-feature', 'user_123', {
  groups: {
    organization: 'acme-corp',
    team: 'engineering'
  }
})

Use Cases:

  • Organization-level feature rollouts
  • Team-based access control
  • Multi-tenant applications

Person Properties

Provide user properties for local evaluation without making additional API calls.

const flag = await client.getFeatureFlag('premium-feature', 'user_123', {
  personProperties: {
    plan: 'enterprise',
    role: 'admin',
    email: 'user@example.com'
  }
})

Use Cases:

  • Local evaluation with targeting rules
  • Avoiding additional API calls for user data
  • Performance optimization

Group Properties

Provide group properties for local evaluation.

const flag = await client.getFeatureFlag('org-feature', 'user_123', {
  groups: { organization: 'acme-corp' },
  groupProperties: {
    organization: {
      tier: 'enterprise',
      employees: 500,
      region: 'us-west'
    }
  }
})

Use Cases:

  • Organization-level targeting
  • Multi-tenant feature rollouts
  • Local evaluation with group rules

Only Evaluate Locally

Force local evaluation only, never fall back to remote.

const flag = await client.getFeatureFlag('flag-key', 'user_123', {
  onlyEvaluateLocally: true
})

// If local evaluation not ready or flag requires server evaluation,
// returns undefined instead of making API call

Use Cases:

  • Performance-critical paths
  • Offline operation
  • Reducing API calls

Send Feature Flag Events

Control whether $feature_flag_called events are sent.

const flag = await client.getFeatureFlag('flag-key', 'user_123', {
  sendFeatureFlagEvents: false // Don't send events
})

Use Cases:

  • Reducing event volume
  • Flags checked frequently (e.g., every request)
  • Internal/system flags

Disable GeoIP

Disable GeoIP-based targeting for the flag check.

const flag = await client.getFeatureFlag('flag-key', 'user_123', {
  disableGeoip: true
})

Use Cases:

  • Privacy compliance
  • Known user location
  • Testing

Flag Keys

Specify which flags to evaluate (for getAllFlags and getAllFlagsAndPayloads).

const flags = await client.getAllFlags('user_123', {
  flagKeys: ['flag-1', 'flag-2', 'flag-3']
})

// Only returns values for specified flags

Use Cases:

  • Performance optimization
  • Loading specific feature sets
  • Reducing payload size

Common Patterns

A/B Testing

Run A/B tests with multivariate flags and payloads.

// Define flag with variants in PostHog UI:
// - variant-a (50%)
// - variant-b (50%)

async function renderPage(userId: string) {
  const variant = await client.getFeatureFlag('homepage-test', userId)

  switch (variant) {
    case 'variant-a':
      return renderVariantA()
    case 'variant-b':
      return renderVariantB()
    default:
      return renderDefault()
  }
}

// Track conversion
async function trackPurchase(userId: string, amount: number) {
  client.capture({
    distinctId: userId,
    event: 'purchase',
    properties: { amount }
  })
}

Feature Rollout

Gradually roll out features to users.

async function showNewFeature(userId: string) {
  const isEnabled = await client.isFeatureEnabled('new-feature', userId)

  if (isEnabled) {
    return renderNewFeature()
  } else {
    return renderOldFeature()
  }
}

// In PostHog UI, gradually increase rollout percentage:
// 5% -> 10% -> 25% -> 50% -> 100%

Experiments

Run experiments with payload-based configuration.

// Configure in PostHog UI with payloads:
// variant-a: { color: "blue", cta: "Buy Now" }
// variant-b: { color: "green", cta: "Get Started" }

async function renderCTA(userId: string) {
  const payload = await client.getFeatureFlagPayload('cta-experiment', userId)

  if (payload && typeof payload === 'object') {
    const config = payload as { color: string; cta: string }
    return `<button style="background: ${config.color}">${config.cta}</button>`
  }

  // Default
  return '<button>Learn More</button>'
}

Remote Configuration

Use feature flags as remote configuration.

// Configure in PostHog UI with payload:
// { apiUrl: "https://api.example.com", timeout: 5000, retries: 3 }

async function getApiConfig() {
  const config = await client.getRemoteConfigPayload('api-config')

  if (config && typeof config === 'object') {
    return config as {
      apiUrl: string
      timeout: number
      retries: number
    }
  }

  // Default config
  return {
    apiUrl: 'https://api.example.com',
    timeout: 3000,
    retries: 1
  }
}

// Use config
const config = await getApiConfig()
const response = await fetch(config.apiUrl, {
  timeout: config.timeout
})

Targeted Feature Access

Enable features based on user properties and groups.

async function hasAccessToFeature(userId: string, userPlan: string, orgId: string) {
  const hasAccess = await client.isFeatureEnabled('premium-feature', userId, {
    personProperties: { plan: userPlan },
    groups: { organization: orgId }
  })

  return hasAccess
}

// Usage
const canAccess = await hasAccessToFeature('user_123', 'enterprise', 'org_456')
if (canAccess) {
  showPremiumFeature()
}

Bootstrap Application with Flags

Load all flags at application startup.

async function bootstrapApp() {
  const client = new PostHog('your-api-key', {
    host: 'https://app.posthog.com',
    personalApiKey: 'phx_...'
  })

  // Wait for flags to load
  const ready = await client.waitForLocalEvaluationReady(5000)

  if (!ready) {
    console.warn('Flags not ready, using defaults')
  }

  // Get all flags for current user
  const flags = await client.getAllFlags('current-user')

  // Store in app state
  app.state.featureFlags = flags

  // Start app
  app.listen(3000)
}

Feature Flag with Events

Send feature flag values with events automatically.

// Capture event with feature flags
client.capture({
  distinctId: 'user_123',
  event: 'button_clicked',
  properties: { button_id: 'cta' },
  sendFeatureFlags: true // Include all active flags
})

// With specific flags
client.capture({
  distinctId: 'user_123',
  event: 'page_viewed',
  properties: { page: '/pricing' },
  sendFeatureFlags: {
    flagKeys: ['pricing-test', 'new-plans']
  }
})

// Only use local evaluation for flags
client.capture({
  distinctId: 'user_123',
  event: 'checkout_started',
  sendFeatureFlags: {
    onlyEvaluateLocally: true
  }
})

Best Practices

1. Use Local Evaluation

Always provide a personal API key for better performance:

// Good
const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com',
  personalApiKey: 'phx_...' // Enable local evaluation
})

// Not optimal (remote evaluation only)
const client = new PostHog('your-api-key', {
  host: 'https://app.posthog.com'
})

2. Wait for Local Evaluation at Startup

Ensure flags are ready before serving requests:

async function startServer() {
  const client = new PostHog('your-api-key', {
    host: 'https://app.posthog.com',
    personalApiKey: 'phx_...'
  })

  // Wait for flags (with timeout)
  await client.waitForLocalEvaluationReady(5000)

  app.listen(3000)
  console.log('Server started with feature flags loaded')
}

3. Cache Flag Values Appropriately

For frequently checked flags, cache the value per request or session:

class FeatureFlagCache {
  private cache: Map<string, { value: any; timestamp: number }> = new Map()
  private ttl = 60000 // 1 minute

  async getFlag(key: string, userId: string): Promise<any> {
    const cacheKey = `${key}:${userId}`
    const cached = this.cache.get(cacheKey)

    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.value
    }

    const value = await client.getFeatureFlag(key, userId)
    this.cache.set(cacheKey, { value, timestamp: Date.now() })

    return value
  }
}

4. Provide Properties for Better Targeting

Pass person and group properties for accurate local evaluation:

// Good - properties included
const flag = await client.getFeatureFlag('feature', 'user_123', {
  personProperties: { plan: 'premium', email: 'user@example.com' },
  groups: { organization: 'acme' },
  groupProperties: { organization: { tier: 'enterprise' } }
})

// Not optimal - may require server evaluation
const flag = await client.getFeatureFlag('feature', 'user_123')

5. Use Groups for Organization Features

For B2B applications, use groups for organization-level flags:

// Associate user with organization
client.capture({
  distinctId: 'user_123',
  event: 'page_view',
  groups: { organization: 'acme-corp' }
})

// Check organization flag
const hasFeature = await client.isFeatureEnabled('org-feature', 'user_123', {
  groups: { organization: 'acme-corp' }
})

6. Handle Undefined Gracefully

Always handle the case where a flag is undefined:

// Good
const flag = await client.getFeatureFlag('new-feature', 'user_123')
if (flag === 'variant-a') {
  renderVariantA()
} else if (flag === 'variant-b') {
  renderVariantB()
} else {
  // Flag disabled, not found, or error
  renderDefault()
}

// Not safe
const flag = await client.getFeatureFlag('new-feature', 'user_123')
// flag might be undefined!

7. Disable Events for High-Volume Flags

For flags checked on every request, disable event tracking:

// Checked frequently - disable events
const flag = await client.getFeatureFlag('rate-limit-config', userId, {
  sendFeatureFlagEvents: false
})

// Important experiment - keep events
const variant = await client.getFeatureFlag('pricing-test', userId, {
  sendFeatureFlagEvents: true // Default
})

8. Use getAllFlags for Multiple Checks

If checking multiple flags, use getAllFlags for better performance:

// Good - single call
const flags = await client.getAllFlags('user_123')
if (flags['feature-a']) { /* ... */ }
if (flags['feature-b']) { /* ... */ }
if (flags['feature-c']) { /* ... */ }

// Not optimal - multiple calls
const featureA = await client.getFeatureFlag('feature-a', 'user_123')
const featureB = await client.getFeatureFlag('feature-b', 'user_123')
const featureC = await client.getFeatureFlag('feature-c', 'user_123')

9. Reload Flags After Updates

After updating flag definitions, force a reload:

// Webhook endpoint for flag updates
app.post('/webhooks/posthog/flags-updated', async (req, res) => {
  await client.reloadFeatureFlags()
  console.log('Feature flags reloaded')
  res.json({ success: true })
})

10. Monitor Local Evaluation Status

Track when local evaluation is not ready:

async function checkFlag(key: string, userId: string) {
  if (!client.isLocalEvaluationReady()) {
    console.warn('Local evaluation not ready, using remote evaluation')
  }

  return await client.getFeatureFlag(key, userId)
}

// Or use events
client._events.on('error', (error) => {
  console.error('Feature flag error:', error)
})

client._events.on('localEvaluationFlagsLoaded', (count) => {
  console.log(`Loaded ${count} feature flags for local evaluation`)
})

11. Test with Different Variants

Create test utilities for flag variants:

// Test utility
class FeatureFlagTestHelper {
  private overrides: Map<string, any> = new Map()

  setFlag(key: string, value: any) {
    this.overrides.set(key, value)
  }

  async getFlag(key: string, userId: string) {
    if (this.overrides.has(key)) {
      return this.overrides.get(key)
    }
    return await client.getFeatureFlag(key, userId)
  }

  clearOverrides() {
    this.overrides.clear()
  }
}

// In tests
const helper = new FeatureFlagTestHelper()
helper.setFlag('new-feature', 'variant-a')
// Test variant-a behavior

12. Use TypeScript for Type Safety

Define types for flag values and payloads:

// Define flag types
type FeatureFlags = {
  'new-ui': boolean
  'pricing-test': 'control' | 'variant-a' | 'variant-b'
  'button-config': { color: string; text: string }
}

// Type-safe wrapper
async function getFlag<K extends keyof FeatureFlags>(
  key: K,
  userId: string
): Promise<FeatureFlags[K] | undefined> {
  return await client.getFeatureFlag(key, userId) as FeatureFlags[K] | undefined
}

// Usage
const uiEnabled = await getFlag('new-ui', 'user_123')
// Type: boolean | undefined

const variant = await getFlag('pricing-test', 'user_123')
// Type: 'control' | 'variant-a' | 'variant-b' | undefined