mcpField
F
feature-flags

feature-flags

github.com/tushaarmehtaaDevelopment1240 installsUpdated 2w ago
Repository

About feature-flags

A development skill for wiring PostHog feature flags into an application, covering server-side evaluation, client-side hooks, and local overrides. It first checks the existing project setup and extends PostHog integration where present.

Key features

  1. 01Inspect existing PostHog setup
  2. 02Evaluate flags on the server
  3. 03Use client hooks after interaction
  4. 04Override flags in local development
  5. 05Guide flag setup in PostHog

Use cases

  1. 01Gate routes or page content without initial flicker
  2. 02Control API behavior by user flag
  3. 03Test flag variants locally

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/tushaarmehtaa/tushar-skills --skill feature-flags

After installing, type/feature-flagsin Claude Code to confirm it loaded.

Details

From the project README

Wire PostHog feature flags end-to-end — server-side evaluation, client-side hook, and local dev overrides. Reads the project first, extends the existing PostHog setup if present.

Two things that silently break PostHog flags

  1. Evaluating flags client-side when you need server-side behavior. useFeatureFlagEnabled runs in the browser after hydration. If you're using a flag to show/hide a page section, that section flashes on then disappears. For anything above the fold or that affects routing, evaluate the flag on the server.
  2. Missing distinctId in server-side calls. PostHog's server SDK requires a distinct user identifier to evaluate flags. Using a placeholder like "anonymous" means every unauthenticated user gets the same flag variant — not a rollout, just a constant.

Phase 1: Detect the Project

cat package.json | grep -E "posthog"
grep -r "posthog" src/ app/ --include="*.ts" --include="*.tsx" -l 2>/dev/null | head -5
  • posthog-js installed + provider found? → PostHog client-side already set up. Add server-side SDK.
  • posthog-node already installed? → extend it, don't reinstall.
  • Nothing? → wire up PostHog from scratch with the add-analytics skill first, then return here.

Phase 2: Install Server SDK

npm install posthog-node

Add to .env.example (already set if the add-analytics skill was run):

NEXT_PUBLIC_POSTHOG_KEY=
POSTHOG_API_KEY=   # same as POSTHOG_KEY — use for server-side calls

Phase 3: Server-Side Flag Client

Create a singleton for server-side evaluation:

// lib/posthog-server.ts
import { PostHog } from 'posthog-node';

let client: PostHog | null = null;

export function getPostHogServer(): PostHog {
  if (!client) {
    client = new PostHog(process.env.POSTHOG_API_KEY!, {
      host: 'https://us.i.posthog.com',
      flushAt: 1,      // flush immediately in serverless
      flushInterval: 0,
    });
  }
  return client;
}

export async function isFeatureEnabled(
  flagKey: string,
  distinctId: string,
  groups?: Record<string, string>
): Promise<boolean> {
  const ph = getPostHogServer();
  const result = await ph.isFeatureEnabled(flagKey, distinctId, { groups });
  return result ?? false;
}

export async function getFeatureFlagVariant(
  flagKey: string,
  distinctId: string
): Promise<string | boolean | undefined> {
  const ph = getPostHogServer();
  return ph.getFeatureFlag(flagKey, distinctId);
}

Phase 4: Server Component Usage

Evaluate flags in Server Components and route handlers — no client-side flicker:

// app/dashboard/page.tsx (Server Component)
import { isFeatureEnabled } from '@/lib/posthog-server';
import { getAuthUser } from '@/lib/auth';

export default async function DashboardPage() {
  const user = await getAuthUser();

  const showNewDashboard = user
    ? await isFeatureEnabled('new-dashboard', user.id)
    : false;

  return showNewDashboard ? <NewDashboard /> : <OldDashboard />;
}
// app/api/generate/route.ts (Route Handler)
import { isFeatureEnabled } from '@/lib/posthog-server';

export async function POST(req: Request) {
  const user = await getAuthUser(req);
  if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });

  const hasStreamingAccess = await isFeatureEnabled('streaming-responses', user.id);

  if (hasStreamingAccess) {
    return handleStreaming(req, user);
  }
  return handleNonStreaming(req, user);
}

Phase 5: Client-Side Usage

For flags that change UI after user interaction (not on initial render):

// components/feature-flagged-button.tsx
'use client';
import { useFeatureFlagEnabled } from 'posthog-js/react';

export function NewFeatureButton() {
  const isEnabled = useFeatureFlagEnabled('new-export-feature');

  // useFeatureFlagEnabled returns undefined while loading
  if (isEnabled === undefined) return null;
  if (!isEnabled) return null;

  return <button>Export (New)</button>;
}

Never use the client hook for above-the-fold content. The hook returns undefined on first render, which causes layout shift. Use the server-side approach for anything visible on page load.

Phase 6: Local Dev Overrides

Add a dev-only flag override mechanism so you can test both variants locally without touching PostHog:

// lib/posthog-server.ts (add to existing file)
export async function isFeatureEnabled(
  flagKey: string,
  distinctId: string,
  groups?: Record<string, string>
): Promise<boolean> {
  // Local override for development
  if (process.env.NODE_ENV === 'development') {
    const override = process.env[`FLAG_${flagKey.toUpperCase().replace(/-/g, '_')}`];
    if (override === 'true') return true;
    if (override === 'false') return false;
  }

  const ph = getPostHogServer();
  const result = await ph.isFeatureEnabled(flagKey, distinctId, { groups });
  return result ?? false;
}

In .env.local:

FLAG_NEW_DASHBOARD=true    # force-enable new-dashboard flag locally
FLAG_STREAMING_RESPONSES=false  # force-disable

Phase 7: Creating Flags in PostHog

Tell the user exactly what to set up. In PostHog dashboard:

  1. Feature Flags → New Feature Flag
  2. Key: new-dashboard (matches what's in code)
  3. Rollout: start at 0%, increment to 10%, 50%, 100%
  4. Conditions: can target by user properties (plan = 'pro'), cohorts, or percentage

For A/B tests: use "Multiple Variants" instead of boolean. Then use getFeatureFlagVariant() and return 'control' or 'test'.

Verify

[ ] posthog-node installed
[ ] POSTHOG_API_KEY in .env.example
[ ] Server-side evaluation uses real user ID as distinctId — not "anonymous"
[ ] Server Components use isFeatureEnabled() — not the client hook
[ ] Client hook used only for post-hydration interactions, not initial render
[ ] Local override via FLAG_* env vars works in development
[ ] Flags created in PostHog dashboard with matching key names
[ ] Test: set FLAG_[KEY]=false in .env.local — confirm feature is hidden
[ ] Test: set FLAG_[KEY]=true in .env.local — confirm feature is shown
[ ] PostHog flag set to 0% rollout first — expand after confirming it works

Comments

Markdown supported