# Feature gate

> Content shown only when the active organization's plan includes a feature, read from the session's features claim and updated when the session refreshes.

Source: https://bettersupabase.com/docs/registry/ui-recipes/feature-gate

{/* Generated by scripts/build-recipes.ts from the example apps. Edit the examples or the script, then run `pnpm recipes`. */}

Both sides call `useFeatureGate` from `better-supabase/blocks/entitlements/react`; the [entitlements block](https://bettersupabase.com/blocks/entitlements) must be installed (`npx better-supabase add entitlements`). Its page on bettersupabase.com is [/recipes/feature-gate](https://bettersupabase.com/recipes/feature-gate).

Web, with shadcn components:

```bash
npx shadcn add button skeleton
```

```tsx title="apps/examples/nextjs/src/features/billing/components/feature-gate.tsx"
"use client";

import type { ReactNode } from "react";

import { useFeatureGate } from "better-supabase/blocks/entitlements/react";
import { LockIcon } from "lucide-react";
import { useExtracted } from "next-intl";

import type { Entitlement } from "@/lib/claims";

import { buttonVariants } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "@/i18n/navigation";

/**
 * Renders `children` only when the active organization's plan includes
 * `feature`, read from the session's `features` claim; otherwise a link to the
 * plans. UX only: RLS and the route behind it check the feature too.
 */
export function FeatureGate({
  feature,
  children,
}: {
  feature: Entitlement;
  children: ReactNode;
}) {
  const t = useExtracted("billing");
  const { allowed } = useFeatureGate(feature);

  if (allowed === undefined) {
    return <Skeleton className="h-8 w-28" aria-busy="true" />;
  }

  if (allowed) {
    return children;
  }

  return (
    <Link
      href="/settings/billing"
      className={buttonVariants({ variant: "outline", size: "sm" })}
      data-testid={`upgrade-${feature}`}
    >
      <LockIcon />
      {t("Upgrade to unlock")}
    </Link>
  );
}
```

React Native, with `ActivityIndicator`, `Text`, `View` from `react-native`:

```tsx title="apps/examples/expo-powersync/src/components/feature-gate.tsx"
import type { ReactNode } from "react";

import { useFeatureGate } from "better-supabase/blocks/entitlements/react";
import { ActivityIndicator, Text, View } from "react-native";

/**
 * Renders `children` only when the active organization's plan includes
 * `feature`, read from the session's `features` claim. UX only: RLS checks the
 * feature too.
 */
export function FeatureGate({
  feature,
  children,
}: {
  readonly feature: string;
  readonly children: ReactNode;
}) {
  const { allowed } = useFeatureGate(feature);

  if (allowed === undefined) {
    return <ActivityIndicator />;
  }

  if (allowed) {
    return children;
  }

  return (
    <View style={{ padding: 12, borderWidth: 1, borderRadius: 12 }}>
      <Text style={{ fontWeight: "600" }}>Not in your plan</Text>
      <Text style={{ color: "#737373" }}>
        Upgrade to a plan with {feature} to use this.
      </Text>
    </View>
  );
}
```