Skip to main content
Feature gating is how you turn your free users into paying customers. By restricting access to premium features based on plan, you create natural upgrade moments that drive revenue — without frustrating your users.

Feature types

BillingOS supports three types of features:
TypeExampleHow it works
BooleanCustom brandingOn/off — user either has access or doesn’t
Usage quota1,000 API calls/monthCounted — resets each billing period
Numeric limit5 team membersFixed cap — doesn’t reset

Client-side gating

Using the FeatureGate component

The simplest approach — wrap content with <FeatureGate>:
import { FeatureGate } from "@billingos/sdk";

<FeatureGate feature="custom_branding">
  <BrandingEditor />
</FeatureGate>
Content is only rendered if the user has access. Add a fallback for users who don’t:
import { FeatureGate, UpgradePrompt } from "@billingos/sdk";

<FeatureGate
  feature="custom_branding"
  fallback={<UpgradePrompt feature="custom_branding" />}
>
  <BrandingEditor />
</FeatureGate>
Feature gate

Using the useFeature hook

For more control, check access programmatically:
import { useFeature } from "@billingos/sdk";

function ExportButton() {
  const { data } = useFeature("export_pdf");

  return (
    <button
      disabled={!data?.has_access}
      onClick={handleExport}
    >
      Export PDF
      {data?.limit && <span>({data.usage}/{data.limit})</span>}
    </button>
  );
}

Using the useFeatureGate hook

Combines access checking with automatic callbacks:
import { useFeatureGate } from "@billingos/sdk";

function ApiCallButton() {
  const { hasAccess, remaining } = useFeatureGate("api_calls", {
    onAccessDenied: () => toast.error("Upgrade to access this feature"),
    onQuotaExceeded: (usage, limit) =>
      toast.warning(`${usage}/${limit} calls used — upgrade for more`),
  });

  return (
    <button disabled={!hasAccess}>
      Make API Call ({remaining} left)
    </button>
  );
}

Server-side gating

For API routes and server actions, use the Node SDK:
import { BillingOS } from "@billingos/node";

const billing = new BillingOS({ secretKey: process.env.BILLINGOS_SECRET_KEY! });

export async function POST(request: Request) {
  const userId = getAuthenticatedUser(request);

  const entitlement = await billing.checkEntitlement(userId, "api_calls");

  if (!entitlement.has_access) {
    return Response.json({ error: "Upgrade required" }, { status: 403 });
  }

  // Process the request...

  // Track the usage
  await billing.trackUsage({
    customerId: userId,
    featureKey: "api_calls",
    quantity: 1,
  });

  return Response.json({ result: "success" });
}
Always validate entitlements on the server for security-critical features. Client-side gates improve UX but can be bypassed.