tessl install tessl/npm-posthog-js@1.335.0PostHog Browser JS Library is a comprehensive browser analytics and feature management SDK that enables developers to capture user events, track product analytics, manage feature flags, record session replays, and implement feedback mechanisms like surveys and conversations in web applications.
Feature flags allow you to control feature rollouts, run A/B tests, and manage feature access dynamically without deploying new code.
Gets the value of a feature flag (boolean, string, or undefined).
/**
* 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:
// 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 });Checks if a boolean feature flag is enabled.
/**
* 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:
// 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 });Gets the JSON payload associated with a feature flag.
/**
* 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:
// 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);
}Manually reloads all feature flags from the server.
/**
* Manually reloads feature flags from the server
* Useful after identify() or group() calls to get updated flags
*/
function reloadFeatureFlags(): void;Usage Examples:
// 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();
}Updates feature flags locally for testing or overrides.
/**
* 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:
// 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 }
);Registers a callback that fires when feature flags are loaded or updated.
/**
* 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:
// 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);
})();Sets person property overrides for local feature flag evaluation.
/**
* 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:
// Override properties for flag evaluation
posthog.setPersonPropertiesForFlags({
email: 'test@example.com',
plan: 'enterprise'
});
// Set without reloading flags
posthog.setPersonPropertiesForFlags(
{ role: 'admin' },
false
);Clears person property overrides for flag evaluation.
/**
* Clears person property overrides for flags
*/
function resetPersonPropertiesForFlags(): void;Usage Example:
// Clear overrides
posthog.resetPersonPropertiesForFlags();Sets group property overrides for local feature flag evaluation.
/**
* 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:
// Override group properties
posthog.setGroupPropertiesForFlags({
company: { plan: 'enterprise', seats: 100 },
team: { role: 'engineering' }
});
// Set without reloading
posthog.setGroupPropertiesForFlags(
{ company: { industry: 'tech' } },
false
);Clears group property overrides for flag evaluation.
/**
* 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:
// Clear all group overrides
posthog.resetGroupPropertiesForFlags();
// Clear specific group type
posthog.resetGroupPropertiesForFlags('company');Gets available early access features for the current user.
/**
* 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:
// 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]
);Updates enrollment status for an early access feature.
/**
* 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:
// 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);/**
* Callback invoked when feature flags are loaded
*/
type FeatureFlagsCallback = (
flags: Record<string, boolean | string>,
variants: Record<string, string | undefined>,
context?: { errorsLoading?: string }
) => void;/**
* 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;
};
}/**
* Reason for feature flag evaluation result
*/
type EvaluationReason =
| 'local_evaluation'
| 'remote_evaluation'
| 'override'
| 'bootstrap'
| 'fallback'
| 'disabled'
| 'error';/**
* 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;
}/**
* Early access feature stage
*/
enum EarlyAccessFeatureStage {
Development = 'development',
Alpha = 'alpha',
Beta = 'beta',
GeneralAvailability = 'general-availability',
}/**
* Callback for early access feature loading
*/
type EarlyAccessFeatureCallback = (
features: EarlyAccessFeature[],
context?: { isLoaded: boolean; error?: string }
) => void;/**
* JSON-serializable value
*/
type JsonType = any;/**
* Object containing properties
*/
type Properties = Record<string, Property | Property[]>;/**
* A single property value
*/
type Property = string | number | boolean | null | undefined;Deprecated: Use other flag status checking methods instead.
/**
* @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:
// Check if flags endpoint was called
const wasHit = posthog.decideEndpointWasHit;
console.log('Flags endpoint hit:', wasHit);Configure feature flag behavior during initialization:
// 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' }
}
}
});Always handle undefined flags gracefully:
// ✅ 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 eitherUse callbacks to ensure flags are loaded:
// ✅ 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 flags after identifying users or changing groups:
// ✅ 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 userStore configuration data in flag payloads:
// 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
});
}Override flags for local development and testing:
// 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);
}The posthog.featureFlags namespace provides additional methods for advanced feature flag management and introspection.
Returns an array of all feature flag keys that have been loaded.
/**
* Gets all feature flag keys
* @returns Array of flag keys
*/
posthog.featureFlags.getFlags(): string[];Usage Example:
// 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');
}Returns detailed information about all feature flags including evaluation metadata.
/**
* 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:
// 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}`);
});Returns the variant values for all loaded feature flags.
/**
* Gets all flag variants
* @returns Object mapping flag keys to variant values
*/
posthog.featureFlags.getFlagVariants(): Record<string, string | boolean>;Usage Example:
// 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
});Returns all feature flag payloads in a single object.
/**
* Gets all flag payloads
* @returns Object mapping flag keys to payload values
*/
posthog.featureFlags.getFlagPayloads(): Record<string, JsonType>;Usage Example:
// Get all payloads at once
const payloads = posthog.featureFlags.getFlagPayloads();
// Apply multiple configurations
Object.entries(payloads).forEach(([key, payload]) => {
if (payload) {
applyFeatureConfig(key, payload);
}
});Ensures that feature flags have been loaded from the server before proceeding. Useful for preventing race conditions.
/**
* Ensures feature flags are loaded before proceeding
* Blocks until flags are loaded or timeout occurs
*/
posthog.featureFlags.ensureFlagsLoaded(): void;Usage Example:
// 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();
}
}Sets the anonymous distinct ID used for feature flag evaluation. Useful for consistent flag evaluation before user identification.
/**
* 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:
// 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();
}Controls whether feature flags should be automatically reloaded. Useful for performance optimization or testing scenarios.
/**
* Pause or resume automatic flag reloading
* @param isPaused - Whether to pause reloading
*/
posthog.featureFlags.setReloadingPaused(isPaused: boolean): void;Usage Example:
// 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);
});Checks if feature flags have finished loading from the server.
/**
* Checks if feature flags have finished loading
* @returns True if flags have been loaded, false otherwise
*/
readonly hasLoadedFlags: boolean; // Accessed as posthog.featureFlags.hasLoadedFlagsUsage Examples:
// 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 />;
}Gets detailed evaluation information for a single feature flag, including the value, metadata, and evaluation reason.
/**
* 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:
// 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
});
}Fetch encrypted remote config feature flag payloads from PostHog servers. This method is useful for retrieving sensitive configuration data that is encrypted server-side.
/**
* 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:
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 flag values and payloads for local testing and development. This is the modern replacement for the deprecated override() method.
/**
* Override feature flag values and/or payloads for testing
* @param overrideOptions - Configuration for flag overrides
*/
posthog.featureFlags.overrideFeatureFlags(overrideOptions: OverrideFeatureFlagsOptions): void;Usage Examples:
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
});Gradually roll out new features:
// 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();Run experiments with multivariate flags:
// 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
});
}Enable features based on user segments:
// 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();Disable features remotely without deployment:
// 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();
}
});Use flags with payloads for dynamic configuration:
// 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
};// 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');// ❌ 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 />;
}// 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 });
}
});
}// 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');// 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');
}// ❌ 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
}
}
}// 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();// 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();// 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);// 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'));// 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);// 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);// 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();
});
});// 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');
}// 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'// 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();// 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');// 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;