# Feature Flags

Feature flags allow you to control feature rollouts, run A/B tests, and manage feature access dynamically without deploying new code.

## Capabilities

### Get Feature Flag Value

Gets the value of a feature flag (boolean, string, or undefined).

```typescript { .api }
/**
 * Gets the value of a feature flag
 * @param key - Feature flag key
 * @param options - Options for flag evaluation
 * @returns Flag value (boolean for boolean flags, string for multivariate flags, undefined if not found)
 */
function getFeatureFlag(
    key: string,
    options?: { send_event?: boolean }
): boolean | string | undefined;
```

**Usage Examples:**

```typescript
// Boolean flag
const newFeatureEnabled = posthog.getFeatureFlag('new-dashboard');
if (newFeatureEnabled) {
    showNewDashboard();
}

// Multivariate flag
const buttonColor = posthog.getFeatureFlag('button-color-test');
if (buttonColor === 'blue') {
    applyBlueButton();
} else if (buttonColor === 'green') {
    applyGreenButton();
}

// Suppress $feature_flag_called event
const flag = posthog.getFeatureFlag('silent-flag', { send_event: false });
```

### Check If Feature Is Enabled

Checks if a boolean feature flag is enabled.

```typescript { .api }
/**
 * Checks if a feature flag is enabled (boolean flags only)
 * @param key - Feature flag key
 * @param options - Options for flag evaluation
 * @returns True if enabled, false if disabled, undefined if not found or not boolean
 */
function isFeatureEnabled(
    key: string,
    options?: { send_event: boolean }
): boolean | undefined;
```

**Usage Examples:**

```typescript
// Simple boolean check
if (posthog.isFeatureEnabled('new-checkout')) {
    renderNewCheckout();
} else {
    renderOldCheckout();
}

// With default fallback
const showBeta = posthog.isFeatureEnabled('beta-features') ?? false;

// Suppress event tracking
const flag = posthog.isFeatureEnabled('feature', { send_event: false });
```

### Get Feature Flag Payload

Gets the JSON payload associated with a feature flag.

```typescript { .api }
/**
 * Gets the payload associated with a feature flag
 * @param key - Feature flag key
 * @returns JSON payload or undefined if not found
 */
function getFeatureFlagPayload(key: string): JsonType;
```

**Usage Examples:**

```typescript
// Get configuration from flag payload
const config = posthog.getFeatureFlagPayload('experiment-config');
if (config) {
    applyConfig({
        variant: config.variant,
        settings: config.settings
    });
}

// Get complex data structures
const pricing = posthog.getFeatureFlagPayload('pricing-experiment');
if (pricing) {
    setPrices({
        monthly: pricing.monthly_price,
        annual: pricing.annual_price,
        discount: pricing.discount_percent
    });
}

// Type-safe payload handling
interface PricingConfig {
    monthly_price: number;
    annual_price: number;
    discount_percent: number;
}

const typedPricing = posthog.getFeatureFlagPayload('pricing-experiment') as PricingConfig | undefined;
if (typedPricing) {
    setPrices(typedPricing);
}
```

### Reload Feature Flags

Manually reloads all feature flags from the server.

```typescript { .api }
/**
 * Manually reloads feature flags from the server
 * Useful after identify() or group() calls to get updated flags
 */
function reloadFeatureFlags(): void;
```

**Usage Examples:**

```typescript
// Reload after identification
posthog.identify('user-123', { plan: 'enterprise' });
posthog.reloadFeatureFlags();

// Reload after group change
posthog.group('company', 'acme-inc');
posthog.reloadFeatureFlags();

// Force refresh
function refreshFeatures() {
    posthog.reloadFeatureFlags();
}
```

### Update Feature Flags Locally

Updates feature flags locally for testing or overrides.

```typescript { .api }
/**
 * Updates feature flags locally (for testing/overrides)
 * @param flags - Object mapping flag keys to values
 * @param payloads - Object mapping flag keys to payloads (optional)
 * @param options - Options for update behavior
 */
function updateFlags(
    flags: Record<string, boolean | string>,
    payloads?: Record<string, JsonType>,
    options?: { merge?: boolean }
): void;
```

**Usage Examples:**

```typescript
// Override flags for testing
posthog.updateFlags({
    'new-feature': true,
    'experiment-variant': 'control'
});

// Override with payloads
posthog.updateFlags(
    { 'config-flag': true },
    { 'config-flag': { timeout: 5000, retries: 3 } }
);

// Merge with existing flags (default: replace)
posthog.updateFlags(
    { 'additional-flag': true },
    undefined,
    { merge: true }
);
```

### Feature Flags Callback

Registers a callback that fires when feature flags are loaded or updated.

```typescript { .api }
/**
 * Registers a callback for when feature flags are loaded
 * @param callback - Function to call when flags are loaded
 * @returns Function to unsubscribe from callback
 */
function onFeatureFlags(callback: FeatureFlagsCallback): () => void;
```

**Usage Examples:**

```typescript
// Listen for flag changes
const unsubscribe = posthog.onFeatureFlags((flags, variants, context) => {
    console.log('Flags loaded:', flags);
    console.log('Variants:', variants);

    if (context?.errorsLoading) {
        console.error('Error loading flags:', context.errorsLoading);
    }

    // Update UI based on flags
    updateFeatureUI(flags);
});

// Later, stop listening
unsubscribe();

// One-time callback
posthog.onFeatureFlags((flags) => {
    initializeApp(flags);
})();
```

### Set Person Properties for Flags

Sets person property overrides for local feature flag evaluation.

```typescript { .api }
/**
 * Sets person property overrides for local flag evaluation
 * @param properties - Person properties to use for flag evaluation
 * @param reloadFeatureFlags - Whether to reload flags after setting (default: true)
 */
function setPersonPropertiesForFlags(
    properties: Properties,
    reloadFeatureFlags?: boolean
): void;
```

**Usage Examples:**

```typescript
// Override properties for flag evaluation
posthog.setPersonPropertiesForFlags({
    email: 'test@example.com',
    plan: 'enterprise'
});

// Set without reloading flags
posthog.setPersonPropertiesForFlags(
    { role: 'admin' },
    false
);
```

### Reset Person Properties for Flags

Clears person property overrides for flag evaluation.

```typescript { .api }
/**
 * Clears person property overrides for flags
 */
function resetPersonPropertiesForFlags(): void;
```

**Usage Example:**

```typescript
// Clear overrides
posthog.resetPersonPropertiesForFlags();
```

### Set Group Properties for Flags

Sets group property overrides for local feature flag evaluation.

```typescript { .api }
/**
 * Sets group property overrides for local flag evaluation
 * @param properties - Object mapping group types to properties
 * @param reloadFeatureFlags - Whether to reload flags after setting (default: true)
 */
function setGroupPropertiesForFlags(
    properties: { [type: string]: Properties },
    reloadFeatureFlags?: boolean
): void;
```

**Usage Examples:**

```typescript
// Override group properties
posthog.setGroupPropertiesForFlags({
    company: { plan: 'enterprise', seats: 100 },
    team: { role: 'engineering' }
});

// Set without reloading
posthog.setGroupPropertiesForFlags(
    { company: { industry: 'tech' } },
    false
);
```

### Reset Group Properties for Flags

Clears group property overrides for flag evaluation.

```typescript { .api }
/**
 * Clears group property overrides for flags
 * @param group_type - Optional group type to clear (clears all if not specified)
 */
function resetGroupPropertiesForFlags(group_type?: string): void;
```

**Usage Examples:**

```typescript
// Clear all group overrides
posthog.resetGroupPropertiesForFlags();

// Clear specific group type
posthog.resetGroupPropertiesForFlags('company');
```

## Early Access Features

### Get Early Access Features

Gets available early access features for the current user.

```typescript { .api }
/**
 * Gets available early access features
 * @param callback - Function to call with early access features
 * @param force_reload - Force reload from server (default: false)
 * @param stages - Filter by specific stages (optional)
 */
function getEarlyAccessFeatures(
    callback: EarlyAccessFeatureCallback,
    force_reload?: boolean,
    stages?: EarlyAccessFeatureStage[]
): void;
```

**Usage Examples:**

```typescript
// Get all early access features
posthog.getEarlyAccessFeatures((features, context) => {
    console.log('Available features:', features);

    if (context?.error) {
        console.error('Error loading features:', context.error);
    }

    features.forEach(feature => {
        console.log(`${feature.name} (${feature.stage})`);
    });
});

// Force reload from server
posthog.getEarlyAccessFeatures((features) => {
    displayEarlyAccessUI(features);
}, true);

// Filter by specific stages
posthog.getEarlyAccessFeatures(
    (features) => {
        // Only beta features
        features.forEach(f => console.log(f.name));
    },
    false,
    [EarlyAccessFeatureStage.Beta]
);

// Filter multiple stages
posthog.getEarlyAccessFeatures(
    (features) => {
        // Alpha and beta features
        displayFeatureList(features);
    },
    false,
    [EarlyAccessFeatureStage.Alpha, EarlyAccessFeatureStage.Beta]
);
```

### Update Early Access Feature Enrollment

Updates enrollment status for an early access feature.

```typescript { .api }
/**
 * Updates enrollment status for an early access feature
 * @param key - Early access feature key
 * @param isEnrolled - Whether user is enrolled
 * @param stage - Feature stage (optional)
 */
function updateEarlyAccessFeatureEnrollment(
    key: string,
    isEnrolled: boolean,
    stage?: string
): void;
```

**Usage Examples:**

```typescript
// Enroll user in beta feature
posthog.updateEarlyAccessFeatureEnrollment('new-editor', true, 'beta');

// Unenroll from feature
posthog.updateEarlyAccessFeatureEnrollment('new-editor', false);

// Enroll without stage
posthog.updateEarlyAccessFeatureEnrollment('feature-x', true);
```

## Types

### FeatureFlagsCallback

```typescript { .api }
/**
 * Callback invoked when feature flags are loaded
 */
type FeatureFlagsCallback = (
    flags: Record<string, boolean | string>,
    variants: Record<string, string | undefined>,
    context?: { errorsLoading?: string }
) => void;
```

### FeatureFlagDetail

```typescript { .api }
/**
 * Detailed information about a feature flag
 */
interface FeatureFlagDetail {
    /**
     * Flag key
     */
    key: string;

    /**
     * Whether the flag is enabled
     */
    enabled: boolean;

    /**
     * Variant value for multivariate flags
     */
    variant?: string;

    /**
     * Reason for the flag evaluation result
     */
    reason?: EvaluationReason;

    /**
     * Additional metadata about the flag
     */
    metadata?: {
        /**
         * Flag ID
         */
        id?: number;

        /**
         * Flag version
         */
        version?: number;

        /**
         * Flag description
         */
        description?: string;

        /**
         * Flag payload
         */
        payload?: JsonType;
    };
}
```

### EvaluationReason

```typescript { .api }
/**
 * Reason for feature flag evaluation result
 */
type EvaluationReason =
    | 'local_evaluation'
    | 'remote_evaluation'
    | 'override'
    | 'bootstrap'
    | 'fallback'
    | 'disabled'
    | 'error';
```

### EarlyAccessFeature

```typescript { .api }
/**
 * Early access feature information
 */
interface EarlyAccessFeature {
    /**
     * Unique identifier
     */
    id: string;

    /**
     * Feature key
     */
    key: string;

    /**
     * Feature name
     */
    name: string;

    /**
     * Feature description
     */
    description?: string;

    /**
     * Current stage
     */
    stage: EarlyAccessFeatureStage;
}
```

### EarlyAccessFeatureStage

```typescript { .api }
/**
 * Early access feature stage
 */
enum EarlyAccessFeatureStage {
    Development = 'development',
    Alpha = 'alpha',
    Beta = 'beta',
    GeneralAvailability = 'general-availability',
}
```

### EarlyAccessFeatureCallback

```typescript { .api }
/**
 * Callback for early access feature loading
 */
type EarlyAccessFeatureCallback = (
    features: EarlyAccessFeature[],
    context?: { isLoaded: boolean; error?: string }
) => void;
```

### JsonType

```typescript { .api }
/**
 * JSON-serializable value
 */
type JsonType = any;
```

### Properties

```typescript { .api }
/**
 * Object containing properties
 */
type Properties = Record<string, Property | Property[]>;
```

### Property

```typescript { .api }
/**
 * A single property value
 */
type Property = string | number | boolean | null | undefined;
```

## Deprecated Properties

### decideEndpointWasHit

**Deprecated**: Use other flag status checking methods instead.

```typescript { .api }
/**
 * @deprecated Use flagsEndpointWasHit instead
 * Checks if the decide endpoint (flags endpoint) has been hit
 * @returns True if endpoint was hit, false otherwise
 */
get decideEndpointWasHit(): boolean;
```

**Usage Example:**

```typescript
// Check if flags endpoint was called
const wasHit = posthog.decideEndpointWasHit;
console.log('Flags endpoint hit:', wasHit);
```

## Configuration

### Feature Flag Settings

Configure feature flag behavior during initialization:

```typescript { .api }
// Disable feature flags entirely
posthog.init('token', {
    advanced_disable_flags: true
});

// Disable flags on first load (load them later)
posthog.init('token', {
    advanced_disable_feature_flags_on_first_load: true
});

// Set timeout for flag requests
posthog.init('token', {
    feature_flag_request_timeout_ms: 5000 // 5 seconds
});

// Use separate API host for flags
posthog.init('token', {
    flags_api_host: 'https://flags.example.com'
});

// Bootstrap flags (skip initial load)
posthog.init('token', {
    bootstrap: {
        featureFlags: {
            'new-feature': true,
            'variant-test': 'control'
        },
        featureFlagPayloads: {
            'new-feature': { config: 'value' }
        }
    }
});
```

## Best Practices

### Defensive Flag Checks

Always handle undefined flags gracefully:

```typescript
// ✅ Good: Handle undefined with default
const showNewUI = posthog.isFeatureEnabled('new-ui') ?? false;
if (showNewUI) {
    renderNewUI();
} else {
    renderOldUI();
}

// ✅ Good: Explicit undefined check
const flag = posthog.getFeatureFlag('experiment');
if (flag === undefined) {
    // Flags not loaded yet or flag doesn't exist
    showLoadingState();
} else if (flag) {
    showExperiment();
}

// ❌ Bad: Assumes flag exists
if (posthog.isFeatureEnabled('new-ui')) {
    renderNewUI();
}
// If flag is undefined, old UI won't render either
```

### Wait for Flags to Load

Use callbacks to ensure flags are loaded:

```typescript
// ✅ Good: Wait for flags
posthog.onFeatureFlags((flags) => {
    if (flags['critical-feature']) {
        initializeCriticalFeature();
    }
});

// ✅ Good: Check if loaded
function checkFeature() {
    const flag = posthog.getFeatureFlag('feature');
    if (flag !== undefined) {
        return flag;
    }
    // Wait for flags to load
    posthog.onFeatureFlags(() => {
        checkFeature();
    });
}

// ❌ Bad: Check immediately (may not be loaded)
if (posthog.isFeatureEnabled('feature')) {
    // This may not work if flags haven't loaded yet
}
```

### Reload After Identity Changes

Reload flags after identifying users or changing groups:

```typescript
// ✅ Good: Reload after identify
posthog.identify('user-123', { plan: 'enterprise' });
posthog.reloadFeatureFlags();

// ✅ Good: Reload after group change
posthog.group('company', 'acme-inc', { plan: 'enterprise' });
posthog.reloadFeatureFlags();

// ❌ Bad: Don't reload (flags may be stale)
posthog.identify('user-123');
// Flags still evaluated for anonymous user
```

### Use Payloads for Configuration

Store configuration data in flag payloads:

```typescript
// Flag payload in PostHog dashboard
{
    "timeout_ms": 5000,
    "max_retries": 3,
    "endpoint": "https://api.example.com/v2"
}

// Application code
const config = posthog.getFeatureFlagPayload('api-config');
if (config) {
    apiClient.configure({
        timeout: config.timeout_ms,
        retries: config.max_retries,
        endpoint: config.endpoint
    });
}
```

### Testing with Flag Overrides

Override flags for local development and testing:

```typescript
// Development mode overrides
if (process.env.NODE_ENV === 'development') {
    posthog.updateFlags({
        'new-feature': true,
        'debug-mode': true,
        'experiment-variant': 'test'
    });
}

// Testing specific scenarios
function testCheckoutFlow() {
    posthog.updateFlags({
        'new-checkout': true,
        'payment-provider': 'stripe-test'
    });

    // Run tests...

    // Reset after tests
    posthog.reloadFeatureFlags();
}

// URL-based overrides
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('feature_flags')) {
    const flags = JSON.parse(urlParams.get('feature_flags'));
    posthog.updateFlags(flags);
}
```

## Advanced Feature Flags Sub-API

The `posthog.featureFlags` namespace provides additional methods for advanced feature flag management and introspection.

### Get All Flag Keys

Returns an array of all feature flag keys that have been loaded.

```typescript { .api }
/**
 * Gets all feature flag keys
 * @returns Array of flag keys
 */
posthog.featureFlags.getFlags(): string[];
```

**Usage Example:**

```typescript
// Get all available flag keys
const flagKeys = posthog.featureFlags.getFlags();
console.log('Available flags:', flagKeys);

// Check if a specific flag exists
if (flagKeys.includes('new-feature')) {
    console.log('new-feature flag is available');
}
```

### Get Flags With Details

Returns detailed information about all feature flags including evaluation metadata.

```typescript { .api }
/**
 * Gets all flags with evaluation details
 * @returns Object mapping flag keys to FeatureFlagDetail objects
 */
posthog.featureFlags.getFlagsWithDetails(): Record<string, FeatureFlagDetail>;

interface FeatureFlagDetail {
    value: boolean | string;
    reason?: EvaluationReason;
    metadata?: FeatureFlagMetadata;
}

type EvaluationReason =
    | 'targeting_match'
    | 'condition_match'
    | 'rollout'
    | 'bootstrap'
    | 'override'
    | 'unknown';

interface FeatureFlagMetadata {
    evaluatedLocally?: boolean;
    flagType?: 'boolean' | 'multivariate';
}
```

**Usage Example:**

```typescript
// Get detailed flag information
const details = posthog.featureFlags.getFlagsWithDetails();

Object.entries(details).forEach(([key, detail]) => {
    console.log(`Flag: ${key}`);
    console.log(`  Value: ${detail.value}`);
    console.log(`  Reason: ${detail.reason}`);
    console.log(`  Evaluated locally: ${detail.metadata?.evaluatedLocally}`);
});
```

### Get All Flag Variants

Returns the variant values for all loaded feature flags.

```typescript { .api }
/**
 * Gets all flag variants
 * @returns Object mapping flag keys to variant values
 */
posthog.featureFlags.getFlagVariants(): Record<string, string | boolean>;
```

**Usage Example:**

```typescript
// Get all flag variants
const variants = posthog.featureFlags.getFlagVariants();
console.log('Flag variants:', variants);

// Use for analytics or debugging
posthog.capture('page_viewed', {
    active_variants: variants
});
```

### Get All Flag Payloads

Returns all feature flag payloads in a single object.

```typescript { .api }
/**
 * Gets all flag payloads
 * @returns Object mapping flag keys to payload values
 */
posthog.featureFlags.getFlagPayloads(): Record<string, JsonType>;
```

**Usage Example:**

```typescript
// Get all payloads at once
const payloads = posthog.featureFlags.getFlagPayloads();

// Apply multiple configurations
Object.entries(payloads).forEach(([key, payload]) => {
    if (payload) {
        applyFeatureConfig(key, payload);
    }
});
```

### Ensure Flags Loaded

Ensures that feature flags have been loaded from the server before proceeding. Useful for preventing race conditions.

```typescript { .api }
/**
 * Ensures feature flags are loaded before proceeding
 * Blocks until flags are loaded or timeout occurs
 */
posthog.featureFlags.ensureFlagsLoaded(): void;
```

**Usage Example:**

```typescript
// Wait for flags before critical initialization
async function initializeApp() {
    // Ensure flags are loaded first
    posthog.featureFlags.ensureFlagsLoaded();

    // Now safely check flags
    if (posthog.isFeatureEnabled('new-ui')) {
        loadNewUI();
    } else {
        loadLegacyUI();
    }
}
```

### Set Anonymous Distinct ID

Sets the anonymous distinct ID used for feature flag evaluation. Useful for consistent flag evaluation before user identification.

```typescript { .api }
/**
 * Sets the anonymous distinct ID for flag evaluation
 * @param anon_distinct_id - Anonymous distinct ID to use
 */
posthog.featureFlags.setAnonymousDistinctId(anon_distinct_id: string): void;
```

**Usage Example:**

```typescript
// Set a custom anonymous ID
posthog.featureFlags.setAnonymousDistinctId('custom-anon-id');

// Useful for A/B testing before user signs in
const anonId = localStorage.getItem('experimental_user_id');
if (anonId) {
    posthog.featureFlags.setAnonymousDistinctId(anonId);
    posthog.reloadFeatureFlags();
}
```

### Pause/Resume Flag Reloading

Controls whether feature flags should be automatically reloaded. Useful for performance optimization or testing scenarios.

```typescript { .api }
/**
 * Pause or resume automatic flag reloading
 * @param isPaused - Whether to pause reloading
 */
posthog.featureFlags.setReloadingPaused(isPaused: boolean): void;
```

**Usage Example:**

```typescript
// Pause reloading during batch operations
posthog.featureFlags.setReloadingPaused(true);

// Perform multiple operations
posthog.identify('user-123');
posthog.group('company', 'acme-inc');
posthog.setPersonProperties({ plan: 'enterprise' });

// Resume and reload once
posthog.featureFlags.setReloadingPaused(false);
posthog.reloadFeatureFlags();

// Pause during tests
beforeEach(() => {
    posthog.featureFlags.setReloadingPaused(true);
});

afterEach(() => {
    posthog.featureFlags.setReloadingPaused(false);
});
```

### Check If Flags Are Loaded

Checks if feature flags have finished loading from the server.

```typescript { .api }
/**
 * Checks if feature flags have finished loading
 * @returns True if flags have been loaded, false otherwise
 */
readonly hasLoadedFlags: boolean; // Accessed as posthog.featureFlags.hasLoadedFlags
```

**Usage Examples:**

```typescript
// Check if flags are ready before using them
if (posthog.featureFlags.hasLoadedFlags) {
    const variant = posthog.getFeatureFlag('experiment');
    applyVariant(variant);
} else {
    // Show loading state or use defaults
    applyDefaultVariant();
}

// Wait for flags before critical decision
function renderApp() {
    if (!posthog.featureFlags.hasLoadedFlags) {
        return <LoadingSpinner />;
    }

    const showNewUI = posthog.isFeatureEnabled('new-ui');
    return showNewUI ? <NewApp /> : <OldApp />;
}
```

### Get Feature Flag Details

Gets detailed evaluation information for a single feature flag, including the value, metadata, and evaluation reason.

```typescript { .api }
/**
 * Gets detailed evaluation information for a feature flag
 * @param key - Feature flag key
 * @returns FeatureFlagDetail object with value and metadata, or undefined if not found
 */
posthog.featureFlags.getFeatureFlagDetails(key: string): FeatureFlagDetail | undefined;
```

**Usage Examples:**

```typescript
// Get detailed flag information for debugging
const details = posthog.featureFlags.getFeatureFlagDetails('new-feature');
if (details) {
    console.log('Flag value:', details.value);
    console.log('Evaluation reason:', details.reason);
    console.log('Metadata:', details.metadata);
}

// Debug why a flag has a specific value
function debugFeatureFlag(flagKey: string) {
    const details = posthog.featureFlags.getFeatureFlagDetails(flagKey);

    if (!details) {
        console.log(`Flag "${flagKey}" not found`);
        return;
    }

    console.log(`Flag: ${flagKey}`);
    console.log(`Value: ${details.value}`);
    console.log(`Reason: ${details.reason}`);

    if (details.metadata) {
        console.log('Metadata:', JSON.stringify(details.metadata, null, 2));
    }
}

// Use evaluation reason for analytics
const flagDetails = posthog.featureFlags.getFeatureFlagDetails('experiment');
if (flagDetails) {
    posthog.capture('flag_evaluated', {
        flag_key: 'experiment',
        flag_value: flagDetails.value,
        evaluation_reason: flagDetails.reason
    });
}
```

### Get Remote Config Payload

Fetch encrypted remote config feature flag payloads from PostHog servers. This method is useful for retrieving sensitive configuration data that is encrypted server-side.

```typescript { .api }
/**
 * Fetch encrypted remote config feature flag payload
 * @param key - Feature flag key to retrieve payload for
 * @param callback - Callback function that receives the payload
 */
posthog.featureFlags.getRemoteConfigPayload(key: string, callback: RemoteConfigFeatureFlagCallback): void;
```

**Usage Examples:**

```typescript
import posthog from 'posthog-js';

// Fetch remote config payload
posthog.featureFlags.getRemoteConfigPayload('api-config', (payload) => {
    if (payload) {
        console.log('Remote config loaded:', payload);
        // Use encrypted config data
        initializeAPI(payload);
    }
});

// Example with type-safe payload handling
posthog.featureFlags.getRemoteConfigPayload('home-page-welcome-message', (payload) => {
    if (payload && typeof payload === 'object') {
        const config = payload as { message: string; style: string };
        displayWelcomeMessage(config.message, config.style);
    }
});
```

### Override Feature Flags

Override feature flag values and payloads for local testing and development. This is the modern replacement for the deprecated `override()` method.

```typescript { .api }
/**
 * Override feature flag values and/or payloads for testing
 * @param overrideOptions - Configuration for flag overrides
 */
posthog.featureFlags.overrideFeatureFlags(overrideOptions: OverrideFeatureFlagsOptions): void;
```

**Usage Examples:**

```typescript
import posthog from 'posthog-js';

// Clear all overrides
posthog.featureFlags.overrideFeatureFlags(false);

// Enable specific flags (as boolean array)
posthog.featureFlags.overrideFeatureFlags(['beta-feature', 'new-ui']);

// Set specific flag variants (as object)
posthog.featureFlags.overrideFeatureFlags({
    'beta-feature': 'variant-a',
    'new-ui': true,
    'pricing-test': 'control'
});

// Override both flags and payloads
posthog.featureFlags.overrideFeatureFlags({
    flags: {
        'beta-feature': 'variant-a',
        'config-flag': true
    },
    payloads: {
        'config-flag': {
            apiEndpoint: 'https://staging.api.example.com',
            maxRetries: 5
        }
    }
});

// Override only payloads (keep flag values from server)
posthog.featureFlags.overrideFeatureFlags({
    payloads: {
        'api-config': { environment: 'development' }
    }
});

// Suppress override warnings
posthog.featureFlags.overrideFeatureFlags({
    flags: ['test-flag'],
    suppressWarning: true
});
```

## Common Patterns

### Feature Rollout

Gradually roll out new features:

```typescript
// Check if new feature is enabled
if (posthog.isFeatureEnabled('new-dashboard')) {
    loadComponent(() => import('./NewDashboard'));
} else {
    loadComponent(() => import('./OldDashboard'));
}

// Rollout based on user properties
posthog.identify(userId, {
    email: user.email,
    plan: user.plan,
    signup_date: user.signupDate
});
posthog.reloadFeatureFlags();
```

### A/B Testing

Run experiments with multivariate flags:

```typescript
// Get experiment variant
const variant = posthog.getFeatureFlag('checkout-experiment');

switch (variant) {
    case 'control':
        renderControlCheckout();
        break;
    case 'variant-a':
        renderVariantACheckout();
        break;
    case 'variant-b':
        renderVariantBCheckout();
        break;
    default:
        // Flag not loaded or user not in experiment
        renderControlCheckout();
}

// Track experiment exposure
if (variant) {
    posthog.capture('experiment_viewed', {
        experiment: 'checkout-experiment',
        variant: variant
    });
}
```

### Progressive Feature Rollout

Enable features based on user segments:

```typescript
// Enterprise users get new features first
posthog.onFeatureFlags((flags) => {
    const features = {
        advancedAnalytics: flags['advanced-analytics'],
        customReports: flags['custom-reports'],
        apiAccess: flags['api-access']
    };

    updateUIFeatures(features);
});

// Beta testers
posthog.setPersonProperties({
    beta_tester: true
});
posthog.reloadFeatureFlags();
```

### Kill Switch

Disable features remotely without deployment:

```typescript
// Check kill switch before critical operations
async function processCriticalOperation() {
    if (posthog.isFeatureEnabled('critical-operation-enabled') === false) {
        console.log('Operation disabled via feature flag');
        return;
    }

    await performOperation();
}

// Conditional feature initialization
posthog.onFeatureFlags((flags) => {
    if (flags['third-party-integration']) {
        initializeThirdPartyIntegration();
    }
});
```

### Feature-Specific Configuration

Use flags with payloads for dynamic configuration:

```typescript
// Get feature configuration
const searchConfig = posthog.getFeatureFlagPayload('search-config');

if (searchConfig) {
    searchService.configure({
        algorithm: searchConfig.algorithm,
        maxResults: searchConfig.max_results,
        fuzzyMatching: searchConfig.fuzzy_matching,
        boost: searchConfig.boost_factors
    });
}

// Fallback to defaults
const config = posthog.getFeatureFlagPayload('feature-config') || {
    enabled: false,
    settings: defaultSettings
};
```

## Advanced Edge Cases

### Handling Undefined Flags

```typescript { .api }
// Always handle undefined cases
function getFeatureWithDefault(flagKey: string, defaultValue: boolean): boolean {
    const value = posthog.isFeatureEnabled(flagKey);
    return value !== undefined ? value : defaultValue;
}

// Usage
const showNewFeature = getFeatureWithDefault('new-feature', false);

// Type-safe multivariate flags
function getVariantWithDefault<T extends string>(
    flagKey: string,
    defaultVariant: T
): T {
    const variant = posthog.getFeatureFlag(flagKey);
    return (variant as T) || defaultVariant;
}

const buttonColor = getVariantWithDefault('button-color', 'blue');
```

### Race Conditions

```typescript { .api }
// ❌ Bad: Check flag before it's loaded
function Component() {
    const enabled = posthog.isFeatureEnabled('new-ui');  // May be undefined
    return enabled ? <NewUI /> : <OldUI />;
}

// ✅ Good: Wait for flags to load
function Component() {
    const [flagsLoaded, setFlagsLoaded] = useState(false);
    const [enabled, setEnabled] = useState(false);
    
    useEffect(() => {
        const unsubscribe = posthog.onFeatureFlags((flags) => {
            setEnabled(flags['new-ui'] || false);
            setFlagsLoaded(true);
        });
        
        return unsubscribe;
    }, []);
    
    if (!flagsLoaded) {
        return <Loading />;
    }
    
    return enabled ? <NewUI /> : <OldUI />;
}
```

### Network Failures

```typescript { .api }
// Handle flag loading failures gracefully
function initializeWithFlagFallbacks() {
    const flagDefaults = {
        'new-dashboard': false,
        'beta-features': false,
        'experimental-mode': false
    };
    
    posthog.onFeatureFlags((flags, variants, context) => {
        if (context?.errorsLoading) {
            console.error('Flag loading error:', context.errorsLoading);
            // Use defaults
            initializeApp(flagDefaults);
        } else {
            // Use loaded flags
            initializeApp({ ...flagDefaults, ...flags });
        }
    });
}
```

### Flag Evaluation Timing

```typescript { .api }
// Check if flags are ready
if (posthog.featureFlags.hasLoadedFlags) {
    const variant = posthog.getFeatureFlag('experiment');
    applyVariant(variant);
} else {
    // Wait for flags
    posthog.onFeatureFlags(() => {
        const variant = posthog.getFeatureFlag('experiment');
        applyVariant(variant);
    });
}

// Or use a promise wrapper
function waitForFlags(): Promise<void> {
    return new Promise((resolve) => {
        if (posthog.featureFlags.hasLoadedFlags) {
            resolve();
        } else {
            posthog.onFeatureFlags(() => resolve())();
        }
    });
}

// Usage
await waitForFlags();
const variant = posthog.getFeatureFlag('experiment');
```

### Caching and Staleness

```typescript { .api }
// Force fresh flag evaluation after important events
async function onUserUpgrade() {
    await upgradeUserPlan(userId);
    
    // Reload flags to get updated targeting
    posthog.reloadFeatureFlags();
    
    // Wait for reload
    await new Promise((resolve) => {
        posthog.onFeatureFlags(() => resolve(undefined))();
    });
    
    // Now flags reflect new plan
    const premiumFeatures = posthog.isFeatureEnabled('premium-features');
}
```

## Performance Considerations

### Minimize Flag Checks

```typescript { .api }
// ❌ Bad: Check flag repeatedly in render loop
function render() {
    for (let i = 0; i < 1000; i++) {
        if (posthog.isFeatureEnabled('optimization')) {
            // Checked 1000 times!
        }
    }
}

// ✅ Good: Cache flag value
function render() {
    const optimizationEnabled = posthog.isFeatureEnabled('optimization');
    for (let i = 0; i < 1000; i++) {
        if (optimizationEnabled) {
            // Checked once, used 1000 times
        }
    }
}
```

### Batch Flag Evaluations

```typescript { .api }
// Get all flags at once
const allFlags = {
    newUI: posthog.isFeatureEnabled('new-ui'),
    betaFeatures: posthog.isFeatureEnabled('beta-features'),
    experimentalMode: posthog.isFeatureEnabled('experimental-mode')
};

// Or get all flag details
const flagDetails = posthog.featureFlags.getFlagsWithDetails();
```

### Reduce Reload Frequency

```typescript { .api }
// Pause reloading during batch operations
posthog.featureFlags.setReloadingPaused(true);

// Perform multiple operations
await batchUserUpdate();
await batchGroupUpdate();
await batchPropertyUpdate();

// Resume and reload once
posthog.featureFlags.setReloadingPaused(false);
posthog.reloadFeatureFlags();
```

## Troubleshooting

### Flags Not Loading

```typescript { .api }
// Debug flag loading
console.log('Flags endpoint hit:', posthog.flagsEndpointWasHit);
console.log('Has loaded flags:', posthog.featureFlags.hasLoadedFlags);
console.log('All flags:', posthog.featureFlags.getFlags());

// Check configuration
console.log('Flags disabled:', posthog.config.advanced_disable_feature_flags);
console.log('Flags API host:', posthog.config.flags_api_host || posthog.config.api_host);
console.log('Timeout:', posthog.config.feature_flag_request_timeout_ms);
```

### Flag Value Unexpected

```typescript { .api }
// Debug specific flag
const flagKey = 'my-flag';
const detail = posthog.featureFlags.getFeatureFlagDetails(flagKey);

console.log(`Flag: ${flagKey}`);
console.log(`Value: ${detail?.value}`);
console.log(`Reason: ${detail?.reason}`);
console.log(`Metadata:`, detail?.metadata);

// Check targeting
console.log('Distinct ID:', posthog.get_distinct_id());
console.log('Groups:', posthog.getGroups());
console.log('Person properties:', posthog.get_property('email'));
```

### Override Not Working

```typescript { .api }
// Verify override
posthog.featureFlags.overrideFeatureFlags({
    'test-flag': true
});

// Check if applied
const flagValue = posthog.getFeatureFlag('test-flag');
const detail = posthog.featureFlags.getFeatureFlagDetails('test-flag');

console.log('Flag value:', flagValue);
console.log('Evaluation reason:', detail?.reason);  // Should be 'override'

// Clear overrides
posthog.featureFlags.overrideFeatureFlags(false);
```

### Bootstrap Flags Not Applied

```typescript { .api }
// Verify bootstrap config
posthog.init('token', {
    bootstrap: {
        featureFlags: {
            'feature-1': true,
            'feature-2': 'variant-a'
        },
        featureFlagPayloads: {
            'feature-1': { config: 'value' }
        }
    }
});

// Check if bootstrap worked
setTimeout(() => {
    console.log('Flags loaded:', posthog.featureFlags.hasLoadedFlags);
    console.log('Flag values:', posthog.featureFlags.getFlagVariants());
}, 0);
```

## Testing Strategies

### Local Testing

```typescript { .api }
// Override flags for testing
if (process.env.NODE_ENV === 'test') {
    posthog.featureFlags.overrideFeatureFlags({
        'new-feature': true,
        'experiment': 'variant-a',
        'beta-mode': false
    }, true);  // suppressWarning: true
}

// Test with different variants
describe('Feature variations', () => {
    it('handles control variant', () => {
        posthog.featureFlags.overrideFeatureFlags({
            'experiment': 'control'
        });
        
        const result = renderComponent();
        expect(result).toMatchControlVariant();
    });
    
    it('handles test variant', () => {
        posthog.featureFlags.overrideFeatureFlags({
            'experiment': 'test'
        });
        
        const result = renderComponent();
        expect(result).toMatchTestVariant();
    });
});
```

### Integration Testing

```typescript { .api }
// Mock flag API responses
async function testWithMockedFlags() {
    // Intercept decide endpoint
    mockHTTP.intercept('POST', '/decide/', {
        featureFlags: {
            'new-feature': true,
            'experiment': 'variant-a'
        },
        featureFlagPayloads: {
            'new-feature': { config: 'test' }
        }
    });
    
    // Initialize and test
    posthog.init('token');
    await waitForFlags();
    
    expect(posthog.isFeatureEnabled('new-feature')).toBe(true);
    expect(posthog.getFeatureFlag('experiment')).toBe('variant-a');
}
```

### URL-Based Testing

```typescript { .api }
// Test different variants via URL
// URL: https://app.com?ph_feature_flag_experiment=variant-a

// PostHog automatically applies URL overrides
const variant = posthog.getFeatureFlag('experiment');
console.log('Testing variant:', variant);  // 'variant-a'
```

## Migration from Other Systems

### From LaunchDarkly

```typescript { .api }
// LaunchDarkly: ldClient.variation('flag-key', false)
const value = posthog.isFeatureEnabled('flag-key') ?? false;

// LaunchDarkly: ldClient.variation('flag-key', 'default')
const variant = posthog.getFeatureFlag('flag-key') ?? 'default';

// LaunchDarkly: ldClient.allFlags()
const allFlags = posthog.featureFlags.getFlagVariants();
```

### From Split.io

```typescript { .api }
// Split.io: client.getTreatment('flag-key')
const treatment = posthog.getFeatureFlag('flag-key') || 'control';

// Split.io: client.getTreatmentWithConfig('flag-key')
const treatment = posthog.getFeatureFlag('flag-key');
const config = posthog.getFeatureFlagPayload('flag-key');
```

### From Optimizely

```typescript { .api }
// Optimizely: optimizely.activate('experiment-key', userId)
const variant = posthog.getFeatureFlag('experiment-key');

// Optimizely: optimizely.getVariation('experiment-key', userId)
const variant = posthog.getFeatureFlag('experiment-key');

// Optimizely: optimizely.isFeatureEnabled('feature-key', userId)
const enabled = posthog.isFeatureEnabled('feature-key') ?? false;
```
