Learn-skills.dev api-flags-posthog-flags

PostHog feature flags, rollouts, A/B testing. Use when implementing gradual rollouts, A/B tests, kill switches, remote configuration, beta features, or user targeting with PostHog.

install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/api-flags-posthog-flags" ~/.claude/skills/neversight-learn-skills-dev-api-flags-posthog-flags && rm -rf "$T"
manifest: data/skills-md/agents-inc/skills/api-flags-posthog-flags/SKILL.md
source content

Feature Flags with PostHog

Quick Guide: Use PostHog feature flags for gradual rollouts, A/B testing, and remote configuration. Client-side:

useFeatureFlagEnabled
hook. Server-side:
posthog-node
with local evaluation. Always pair
useFeatureFlagPayload
with
useFeatureFlagEnabled
for experiments. Handle the
undefined
loading state on every flag check.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,

import type
, named constants)

(You MUST always pair

useFeatureFlagPayload
with
useFeatureFlagEnabled
or
useFeatureFlagVariantKey
for experiments - payload hooks don't send exposure events)

(You MUST use the feature flags secure API key (phs_*) for server-side local evaluation - personal API keys are deprecated for this use)

(You MUST handle the

undefined
state when flags are loading - never assume a flag is immediately available)

(You MUST include flag owner and expiry date in flag metadata - flags without owners become orphaned debt)

(You MUST wrap flag usage in a single function when used in multiple places - prevents orphaned flag code on cleanup)

</critical_requirements>


Auto-detection: PostHog feature flags, useFeatureFlagEnabled, useFeatureFlagPayload, useFeatureFlagVariantKey, PostHogFeature, isFeatureEnabled, getFeatureFlag, gradual rollout, A/B test, experiment, multivariate flag

When to use:

  • Gradual rollouts (deploy to 10% users, then 50%, then 100%)
  • A/B testing with experiments (measure impact of changes)
  • Kill switches (instantly disable features without deploy)
  • Remote configuration (change behavior without code changes)
  • Beta features opt-in (let users try new features)
  • User targeting (show features to specific cohorts)

When NOT to use:

  • Simple on/off switches that never change (use environment variables)
  • Configuration that must be compile-time (use build flags)
  • Secrets or sensitive data (use secret management)
  • Features that should always be on (just ship the code)

Key patterns covered:

  • Client-side flag evaluation with React hooks
  • Server-side local evaluation for performance
  • Boolean vs multivariate flags
  • Gradual rollouts with percentage targeting
  • A/B testing and experiments
  • Payloads for remote configuration
  • Local development overrides
  • Flag cleanup and lifecycle management

Detailed Resources:


<philosophy>

Philosophy

Feature flags decouple deployment from release. You can ship code to production but control who sees it and when. This enables:

  1. Safe releases - Roll out to 1% first, monitor, then expand
  2. Fast rollback - Toggle off instantly without deploying
  3. Data-driven decisions - A/B test to measure impact
  4. Progressive delivery - Beta users first, then everyone

Core principles:

  • Flags are temporary - plan for cleanup from day one
  • Flags have owners - someone is responsible for each flag
  • Simple flags are better - percentage rollouts over complex conditions
  • Handle undefined - flags load asynchronously

When to use feature flags:

  • Risky features that need gradual rollout
  • Features requiring A/B testing for validation
  • Features that may need instant rollback
  • Beta programs with user opt-in

When NOT to use feature flags:

  • Every feature (creates maintenance burden)
  • Permanent configuration (use config files)
  • Features that are ready for 100% release
</philosophy>
<patterns>

Core Patterns

Pattern 1: Client-Side Boolean Flags

Use

useFeatureFlagEnabled
for simple on/off features. Always handle the
undefined
loading state -- treating it as
false
causes a flash of wrong UI.

const isNewCheckout = useFeatureFlagEnabled(FLAG_NEW_CHECKOUT);

if (isNewCheckout === undefined) return <Skeleton />;   // Loading
if (isNewCheckout) return <NewCheckout />;               // Enabled
return <LegacyCheckout />;                               // Disabled

Store flag keys as named constants in

lib/feature-flags.ts
to prevent typos and enable cleanup-by-grep.

See examples/core.md for full good/bad examples.


Pattern 2: Multivariate Flags and Variants

Use

useFeatureFlagVariantKey
for A/B tests with multiple variants. Define variant constants alongside the flag key. Switch on variants with a default fallback to
control
.

const variant = useFeatureFlagVariantKey(FLAG_PRICING_PAGE);
if (variant === undefined) return <Skeleton />;

switch (variant) {
  case VARIANT_SIMPLE: return <SimplePricing />;
  case VARIANT_DETAILED: return <DetailedPricing />;
  default: return <ControlPricing />;
}

See examples/core.md for full example.


Pattern 3: PostHogFeature Component

The

PostHogFeature
component provides automatic exposure tracking and built-in fallback handling with less boilerplate. Use
match={true}
for boolean flags or
match={VARIANT_KEY}
for specific variants.

<PostHogFeature flag={FLAG_BETA} match={true} fallback={<Legacy />}>
  <NewFeature />
</PostHogFeature>

See examples/core.md for boolean and variant examples.


Pattern 4: Payloads for Remote Configuration

Use

useFeatureFlagPayload
for dynamic JSON configuration. Always pair with
useFeatureFlagEnabled
-- the payload hook alone does NOT send exposure events, breaking experiment tracking.

const isEnabled = useFeatureFlagEnabled(FLAG_BANNER); // Sends exposure event
const payload = useFeatureFlagPayload(FLAG_BANNER); // Gets config
const config = payload ?? DEFAULT_BANNER_CONFIG;

See examples/core.md for full good/bad examples.


Pattern 5: Server-Side Flag Evaluation

Use

posthog-node
with the Feature Flags Secure API Key (
phs_*
) for local evaluation. This reduces latency from ~500ms (network call) to ~10-50ms (local). The
personalApiKey
config option takes the
phs_*
key despite its legacy name.

export const posthog = new PostHog(process.env.POSTHOG_API_KEY!, {
  host: process.env.POSTHOG_HOST || "https://us.i.posthog.com",
  personalApiKey: process.env.POSTHOG_FEATURE_FLAGS_KEY, // phs_* key
  featureFlagsPollingInterval: POSTHOG_POLL_INTERVAL_MS, // default 30s
});

See examples/server-side.md for API handler usage, local-only evaluation, and distributed/serverless environments.


Pattern 6: Flag Lifecycle and Cleanup

Every flag needs an owner, a creation date, and an expected removal date. Wrap flag checks in a single helper function so cleanup is a one-file change.

/**
 * Owner: @john-doe | Created: 2025-01-15 | Remove by: 2025-02-15
 */
export const FLAG_NEW_CHECKOUT = "new-checkout-flow";

export function isNewCheckoutEnabled(flag: boolean | undefined): boolean {
  return flag === true; // When removing: change to `return true;`
}

See examples/core.md for full documentation patterns and stale flag detection.

</patterns>

<red_flags>

RED FLAGS

High Priority Issues:

  • Using
    useFeatureFlagPayload
    alone for experiments (no exposure tracking)
  • Exposing Feature Flags Secure API key (
    phs_*
    ) on client (security violation)
  • No loading state handling (causes UI flash)
  • Flags without owners or expiry dates (becomes permanent debt)

Medium Priority Issues:

  • Magic string flag keys instead of constants (typos, hard to grep)
  • Complex targeting rules on high-traffic flags (performance hit)
  • Local evaluation in serverless/edge without external cache (cold start issues)
  • Not using PostHog toolbar for local testing (harder debugging)

Common Mistakes:

  • Checking flag in multiple places instead of wrapper function
  • Not bootstrapping flags for SSR (content flash on hydration)
  • Running experiments without defined primary metric
  • Peeking at experiment results before completion
  • Rolling out to 100% without cleanup plan

Gotchas & Edge Cases:

  • PostHog uses deterministic hashing - same user always gets same variant
  • Decreasing rollout percentage can remove users who were previously included
  • Local evaluation requires Feature Flags Secure API Key (
    phs_*
    ) - personal API keys are deprecated
  • Flags load asynchronously - first render always has undefined
  • GeoIP targeting uses server IP by default in posthog-node v3+
  • Experiments need minimum 50 exposures per variant for results
  • Stale flag = 100% rollout + not evaluated in 30 days
  • onFeatureFlags
    callback receives three parameters:
    flags
    ,
    flagVariants
    ,
    { errorsLoading }
    (third parameter)
  • External cache providers (Redis, KV) are experimental - Node.js/Python SDKs only

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,

import type
, named constants)

(You MUST always pair

useFeatureFlagPayload
with
useFeatureFlagEnabled
or
useFeatureFlagVariantKey
for experiments - payload hooks don't send exposure events)

(You MUST use the feature flags secure API key (phs_*) for server-side local evaluation - personal API keys are deprecated for this use)

(You MUST handle the

undefined
state when flags are loading - never assume a flag is immediately available)

(You MUST include flag owner and expiry date in flag metadata - flags without owners become orphaned debt)

(You MUST wrap flag usage in a single function when used in multiple places - prevents orphaned flag code on cleanup)

Failure to follow these rules will cause incorrect experiment results, security vulnerabilities, UI flashing, and technical debt.

</critical_reminders>


Sources