All recipes
Recipe
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. Web and native call useFeatureGate from better-supabase/blocks/entitlements/react.
1.What you build
No screenshot yet. Run the Next.js example (pnpm dev:example) to see it.
2.Add the components
Web: shadcn
npx shadcn add button skeletonNative: React Native
The snippet uses ActivityIndicator, Text, View from react-native, so it needs no styling library. For shadcn-style native components, React Native Reusables serves them from a registry the shadcn CLI reads.
3.Install the glue
Install the block with npx better-supabase sql add entitlements. The glue items are copied into your app, so edit them as you need.
Web
None: the hook calls the block from the client.
Native
None: the hook calls the block from the client.
4.Write the component
Web: React and shadcn
View in the example"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>
);
}
Native: React Native
View in the exampleimport 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>
);
}