Feature Gating

Define plans and check feature access with one line.

Feature gating lets you map billing products to application features, then check access with a simple API.

Define your plans

import { createEnhancedClient } from "@fuime/billing-sdk";
import { stripe } from "@fuime/billing-sdk/stripe";

const billing = createEnhancedClient({
  provider: stripe({ apiKey: process.env.STRIPE_KEY }),
  plans: {
    // Stripe product IDs
    prod_free: {
      features: ["basic"],
    },
    prod_pro: {
      features: ["basic", "api", "export", "analytics"],
    },
    prod_enterprise: {
      features: "*", // All features
    },
  },
});

Check feature access

// Simple boolean check
if (await billing.hasFeature(userId, "api")) {
  // User has API access
}

// Check multiple features
const features = await billing.getFeatures(userId);
// ["basic", "api", "export", "analytics"]

// Check if user has any active subscription
const entitlement = await billing.getEntitlement(userId);
if (entitlement?.active) {
  // User is subscribed
}

Wildcard features

Use "*" for plans that should have access to everything:

plans: {
  prod_enterprise: {
    features: "*", // Access to all features, current and future
  },
}

When checking features:

await billing.hasFeature(enterpriseUser, "anything"); // true
await billing.hasFeature(enterpriseUser, "new-feature"); // true

Feature flags vs. feature gating

Feature FlagsFeature Gating
Control rolloutControl access by subscription
Toggle on/off for everyoneDifferent access per plan
A/B testingMonetization

You can combine both:

// Check feature flag AND billing
if (featureFlags.isEnabled("new-dashboard") &&
    await billing.hasFeature(userId, "dashboard")) {
  // Show new dashboard to paying users in the rollout
}

Middleware pattern

Protect routes with middleware:

middleware.ts
import { billing } from "@/lib/billing";
import { NextResponse } from "next/server";

export async function middleware(request: Request) {
  const userId = getUserFromSession(request);

  if (request.nextUrl.pathname.startsWith("/api/pro")) {
    if (!(await billing.hasFeature(userId, "api"))) {
      return NextResponse.json(
        { error: "Pro subscription required" },
        { status: 403 }
      );
    }
  }

  return NextResponse.next();
}

React component pattern

async function FeatureGate({
  feature,
  userId,
  children,
  fallback
}: {
  feature: string;
  userId: string;
  children: React.ReactNode;
  fallback?: React.ReactNode;
}) {
  const hasAccess = await billing.hasFeature(userId, feature);

  if (!hasAccess) {
    return fallback ?? null;
  }

  return <>{children}</>;
}

// Usage
<FeatureGate feature="export" userId={user.id} fallback={<UpgradePrompt />}>
  <ExportButton />
</FeatureGate>