Skip to main content
Technology & EngineeringFeature Flags Services371 lines

Flagsmith

"Flagsmith: open-source feature flags, remote config, segments, environments, audit logs, self-hosted, REST API"

Quick Summary18 lines
Flagsmith is an open-source feature flag and remote configuration service that can run fully self-hosted or as a managed cloud offering. It gives teams complete control over their feature flag infrastructure with no vendor lock-in. The platform separates concerns into flags (boolean toggles), remote config (key-value pairs attached to flags), segments (user groupings based on traits), and environments (dev/staging/production isolation). Every flag change is audit-logged, and the REST API is a first-class citizen — the dashboard is simply a frontend for the same API your code uses.

## Key Points

1. **Enable local evaluation on the server.** This eliminates per-request latency to the Flagsmith API. The SDK polls for environment changes in the background and evaluates flags locally.
2. **Use environments to mirror your deployment pipeline.** Create dev, staging, and production environments. Each has its own API key and independent flag states.
3. **Store complex configuration as JSON strings in flag values.** Flags are not just booleans — attach a JSON value to parameterize behavior without creating multiple flags.
4. **Use the REST API in CI/CD pipelines.** Automate flag promotion across environments as part of your deployment process rather than manually toggling flags in the dashboard.
5. **Set traits on identity to drive segment membership.** Traits are the input; segments are the rules. Keep traits factual (plan, region, signup date) and let segments encode the business logic.
6. **Review audit logs regularly.** Self-hosted Flagsmith gives you full audit trail access. Integrate audit log queries into your observability stack for change correlation.
7. **Use default flag handlers.** Configure a `defaultFlagHandler` so your application degrades gracefully when a flag is not defined rather than throwing errors.
8. **Pin your self-hosted version.** Flagsmith releases frequently. Pin to a specific Docker tag and upgrade deliberately, testing flag evaluation consistency before and after.
1. **Calling `getIdentityFlags` on every request without local evaluation.** This makes a network call each time. Enable local evaluation or cache identity flags with a short TTL.
2. **Using remote config values without parsing.** Flag values come back as strings. Always parse and validate them — do not assume the value is well-formed JSON or a valid number.
3. **Creating environments for feature branches.** Environments are heavyweight and meant for deployment stages. Use segments or traits to separate branch-specific behavior.
4. **Ignoring the environment document endpoint.** For performance-critical paths, fetch the full environment document once and evaluate locally rather than making per-flag API calls.
skilldb get feature-flags-services-skills/FlagsmithFull skill: 371 lines
Paste into your CLAUDE.md or agent config

Flagsmith

Core Philosophy

Flagsmith is an open-source feature flag and remote configuration service that can run fully self-hosted or as a managed cloud offering. It gives teams complete control over their feature flag infrastructure with no vendor lock-in. The platform separates concerns into flags (boolean toggles), remote config (key-value pairs attached to flags), segments (user groupings based on traits), and environments (dev/staging/production isolation). Every flag change is audit-logged, and the REST API is a first-class citizen — the dashboard is simply a frontend for the same API your code uses.

Setup

Node.js Server SDK

import Flagsmith from "flagsmith-nodejs";

const flagsmith = new Flagsmith({
  environmentKey: process.env.FLAGSMITH_SERVER_KEY!,
  apiUrl: process.env.FLAGSMITH_API_URL ?? "https://edge.api.flagsmith.com/api/v1/",
  enableLocalEvaluation: true,
  environmentRefreshIntervalSeconds: 30,
  defaultFlagHandler: (flagName: string) => {
    // Fallback when a flag is not found
    return { enabled: false, value: null, isDefault: true };
  },
});

await flagsmith.getEnvironmentFlags();

// Evaluate a flag for an identity
const flags = await flagsmith.getIdentityFlags("user-123", {
  plan: "enterprise",
  signup_date: "2024-01-15",
  company: "acme-corp",
});

const newSearchEnabled = flags.isFeatureEnabled("new_search");
const searchConfig = flags.getFeatureValue("new_search");
// searchConfig might be a JSON string: '{"algorithm":"semantic","maxResults":50}'

React Client SDK

// providers/flagsmith-provider.tsx
"use client";

import { FlagsmithProvider } from "flagsmith/react";
import flagsmith from "flagsmith";
import { useEffect, useState } from "react";

interface Props {
  children: React.ReactNode;
  identityKey?: string;
  traits?: Record<string, string | number | boolean>;
}

export function FeatureFlagProvider({ children, identityKey, traits }: Props) {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    flagsmith
      .init({
        environmentID: process.env.NEXT_PUBLIC_FLAGSMITH_ENV_ID!,
        api: process.env.NEXT_PUBLIC_FLAGSMITH_API_URL,
        identity: identityKey,
        traits,
        cacheFlags: true,
        onChange: () => setReady(true),
        enableAnalytics: true,
      })
      .catch(console.error);
  }, [identityKey, traits]);

  if (!ready) return null;

  return <FlagsmithProvider flagsmith={flagsmith}>{children}</FlagsmithProvider>;
}

Self-Hosted Docker Compose

// scripts/verify-flagsmith.ts — health check for self-hosted instance
async function verifyFlagsmithHealth(baseUrl: string): Promise<boolean> {
  const healthEndpoint = `${baseUrl}/health`;
  const apiEndpoint = `${baseUrl}/api/v1/environment-document/`;

  try {
    const healthRes = await fetch(healthEndpoint);
    if (!healthRes.ok) {
      console.error("Flagsmith health check failed:", healthRes.status);
      return false;
    }

    const apiRes = await fetch(apiEndpoint, {
      headers: {
        "X-Environment-Key": process.env.FLAGSMITH_SERVER_KEY!,
      },
    });

    if (!apiRes.ok) {
      console.error("Flagsmith API check failed:", apiRes.status);
      return false;
    }

    const envDoc = await apiRes.json();
    console.log(`Flagsmith ready: ${envDoc.feature_states.length} flags loaded`);
    return true;
  } catch (err) {
    console.error("Flagsmith not reachable:", err);
    return false;
  }
}

Key Techniques

Local Evaluation Mode

// Local evaluation downloads the full environment config
// and evaluates flags in-process — no network call per evaluation
const flagsmith = new Flagsmith({
  environmentKey: process.env.FLAGSMITH_SERVER_KEY!,
  enableLocalEvaluation: true,
  environmentRefreshIntervalSeconds: 15,
});

// After init, evaluations are purely local
async function evaluateForUser(userId: string, traits: Record<string, string>) {
  const flags = await flagsmith.getIdentityFlags(userId, traits);

  return {
    showBetaFeatures: flags.isFeatureEnabled("beta_features"),
    apiRateLimit: Number(flags.getFeatureValue("api_rate_limit")) || 1000,
    searchAlgorithm: flags.getFeatureValue("search_algorithm") || "keyword",
  };
}

Segments and Trait-Based Targeting

// Traits are properties of an identity used for segment matching
// Segments are defined in the Flagsmith dashboard or via API

async function identifyUser(
  userId: string,
  userAttributes: {
    plan: string;
    region: string;
    createdAt: string;
    employeeCount: number;
  }
) {
  const flags = await flagsmith.getIdentityFlags(userId, {
    plan: userAttributes.plan,
    region: userAttributes.region,
    created_at: userAttributes.createdAt,
    employee_count: userAttributes.employeeCount,
  });

  // Segments in dashboard might define:
  // "enterprise_users" -> trait "plan" equals "enterprise"
  // "large_orgs" -> trait "employee_count" greater than 100
  // "eu_users" -> trait "region" in ["eu-west-1", "eu-central-1"]

  return {
    showAdvancedAnalytics: flags.isFeatureEnabled("advanced_analytics"),
    exportFormat: flags.getFeatureValue("export_format") || "csv",
    maxTeamMembers: Number(flags.getFeatureValue("max_team_members")) || 5,
  };
}

REST API for Programmatic Flag Management

// Manage flags programmatically via the REST API
// Useful for CI/CD pipelines, scripts, and custom dashboards

const FLAGSMITH_API = process.env.FLAGSMITH_API_URL!;
const FLAGSMITH_TOKEN = process.env.FLAGSMITH_ADMIN_TOKEN!;

interface FlagState {
  id: number;
  feature: { id: number; name: string };
  enabled: boolean;
  feature_state_value: string | null;
}

async function listFlags(environmentKey: string): Promise<FlagState[]> {
  const res = await fetch(`${FLAGSMITH_API}/environments/${environmentKey}/featurestates/`, {
    headers: {
      Authorization: `Token ${FLAGSMITH_TOKEN}`,
      "Content-Type": "application/json",
    },
  });
  const data = await res.json();
  return data.results;
}

async function toggleFlag(
  flagStateId: number,
  enabled: boolean,
  value?: string
): Promise<void> {
  await fetch(`${FLAGSMITH_API}/features/featurestates/${flagStateId}/`, {
    method: "PATCH",
    headers: {
      Authorization: `Token ${FLAGSMITH_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      enabled,
      ...(value !== undefined && { feature_state_value: value }),
    }),
  });
}

// CI/CD: enable a flag in staging after deploy
async function enableFlagInStaging(flagName: string) {
  const flags = await listFlags(process.env.FLAGSMITH_STAGING_ENV_KEY!);
  const target = flags.find((f) => f.feature.name === flagName);

  if (target) {
    await toggleFlag(target.id, true);
    console.log(`Enabled "${flagName}" in staging`);
  }
}

Multi-Environment Flag Promotion

// Promote flag configuration from staging to production
async function promoteFlag(flagName: string) {
  const stagingFlags = await listFlags(process.env.FLAGSMITH_STAGING_ENV_KEY!);
  const stagingFlag = stagingFlags.find((f) => f.feature.name === flagName);

  if (!stagingFlag) {
    throw new Error(`Flag "${flagName}" not found in staging`);
  }

  const prodFlags = await listFlags(process.env.FLAGSMITH_PROD_ENV_KEY!);
  const prodFlag = prodFlags.find((f) => f.feature.name === flagName);

  if (!prodFlag) {
    throw new Error(`Flag "${flagName}" not found in production`);
  }

  await toggleFlag(prodFlag.id, stagingFlag.enabled, stagingFlag.feature_state_value ?? undefined);

  console.log(
    `Promoted "${flagName}" to production: enabled=${stagingFlag.enabled}, value=${stagingFlag.feature_state_value}`
  );
}

Audit Log Consumption

// Fetch audit logs for compliance and debugging
interface AuditLogEntry {
  id: number;
  created_date: string;
  log: string;
  author: { email: string } | null;
  environment: { name: string } | null;
  related_object_type: string;
}

async function getRecentAuditLogs(
  projectId: string,
  limit: number = 50
): Promise<AuditLogEntry[]> {
  const res = await fetch(
    `${FLAGSMITH_API}/projects/${projectId}/audit/?page_size=${limit}`,
    {
      headers: {
        Authorization: `Token ${FLAGSMITH_TOKEN}`,
      },
    }
  );
  const data = await res.json();
  return data.results;
}

async function printFlagChangeHistory(projectId: string, flagName: string) {
  const logs = await getRecentAuditLogs(projectId, 100);
  const flagLogs = logs.filter((entry) => entry.log.includes(flagName));

  for (const entry of flagLogs) {
    console.log(
      `[${entry.created_date}] ${entry.author?.email ?? "system"} — ${entry.log} (${entry.environment?.name ?? "global"})`
    );
  }
}

React Hooks for Flag Consumption

"use client";

import { useFlags, useFlagsmith } from "flagsmith/react";

function PricingPage() {
  const flags = useFlags(["new_pricing_table", "pricing_config"]);
  const flagsmith = useFlagsmith();

  const showNewPricing = flags.new_pricing_table?.enabled ?? false;
  const pricingConfig = JSON.parse(
    flags.pricing_config?.value?.toString() || '{"plans":[]}'
  );

  function handlePlanSelect(planId: string) {
    // Track analytics event
    flagsmith.setTrait("selected_plan", planId);
  }

  if (showNewPricing) {
    return (
      <NewPricingTable
        plans={pricingConfig.plans}
        onSelect={handlePlanSelect}
      />
    );
  }

  return <LegacyPricingPage />;
}

Best Practices

  1. Enable local evaluation on the server. This eliminates per-request latency to the Flagsmith API. The SDK polls for environment changes in the background and evaluates flags locally.

  2. Use environments to mirror your deployment pipeline. Create dev, staging, and production environments. Each has its own API key and independent flag states.

  3. Store complex configuration as JSON strings in flag values. Flags are not just booleans — attach a JSON value to parameterize behavior without creating multiple flags.

  4. Use the REST API in CI/CD pipelines. Automate flag promotion across environments as part of your deployment process rather than manually toggling flags in the dashboard.

  5. Set traits on identity to drive segment membership. Traits are the input; segments are the rules. Keep traits factual (plan, region, signup date) and let segments encode the business logic.

  6. Review audit logs regularly. Self-hosted Flagsmith gives you full audit trail access. Integrate audit log queries into your observability stack for change correlation.

  7. Use default flag handlers. Configure a defaultFlagHandler so your application degrades gracefully when a flag is not defined rather than throwing errors.

  8. Pin your self-hosted version. Flagsmith releases frequently. Pin to a specific Docker tag and upgrade deliberately, testing flag evaluation consistency before and after.

Anti-Patterns

  1. Calling getIdentityFlags on every request without local evaluation. This makes a network call each time. Enable local evaluation or cache identity flags with a short TTL.

  2. Using remote config values without parsing. Flag values come back as strings. Always parse and validate them — do not assume the value is well-formed JSON or a valid number.

  3. Creating environments for feature branches. Environments are heavyweight and meant for deployment stages. Use segments or traits to separate branch-specific behavior.

  4. Ignoring the environment document endpoint. For performance-critical paths, fetch the full environment document once and evaluate locally rather than making per-flag API calls.

  5. Storing secrets in flag values. Flag values are sent to client SDKs. Never put API keys, tokens, or passwords in remote config values. Use server-side-only flags for sensitive configuration.

  6. Skipping health checks on self-hosted instances. If Flagsmith is down and you do not have local evaluation enabled, all flags fall back to defaults. Monitor the /health endpoint and alert on failures.

  7. Mixing identity keys across services. If your API uses user IDs and your frontend uses session IDs as identity keys, the same person gets inconsistent flag values. Standardize on a single identity key format.

Install this skill directly: skilldb add feature-flags-services-skills

Get CLI access →