flowglad-feature-gating
Implements access control and paywalls to gate premium features based on user subscription status.
Install
mkdir -p .claude/skills/flowglad-feature-gating && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/7257" && unzip -o skill.zip -d .claude/skills/flowglad-feature-gating && rm skill.zipInstalls to .claude/skills/flowglad-feature-gating
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Implement feature access checks using Flowglad to gate premium features, create paywalls, and restrict functionality based on subscription status. Use this skill when adding paid-only features or checking user entitlements.Key capabilities
- →Implement client-side feature access checks
- →Protect API routes with server-side entitlement verification
- →Handle asynchronous loading of billing data
- →Redirect unprivileged users to upgrade paths
How it works
Uses a centralized billing hook to verify entitlements against feature slugs, managing asynchronous state transitions to prevent UI flickers.
Inputs & outputs
When to use flowglad-feature-gating
- →Implementing premium feature paywalls
- →Restricting access based on subscription
- →Managing feature entitlements
- →Redirecting non-paying users to upgrades
About this skill
Feature Gating
Abstract
Implement feature access checks using Flowglad's checkFeatureAccess method to gate premium features, create paywalls, and restrict functionality based on subscription status.
Table of Contents
- Loading State Handling — CRITICAL
- Server-Side Gating — HIGH
- Feature Identification — MEDIUM
- Component Wrapper Patterns — MEDIUM
- Redirect to Upgrade Patterns — MEDIUM
1. Loading State Handling
Impact: CRITICAL
The billing hook loads asynchronously. While loading, checkFeatureAccess is null (not a function). If you try to call it before loading completes, you'll get a runtime error or incorrect behavior. This causes premium users to see upgrade prompts or paywalls incorrectly.
Note: The
flowglad()factory function used in server-side examples must be set up in your project (typically at@/lib/flowglad). See the setup skill for configuration instructions.
1.1 Wait for Billing to Load
Impact: CRITICAL (prevents flash of incorrect content)
Users with active subscriptions will see upgrade prompts flash briefly if you don't wait for billing to load before checking access.
Incorrect: checks access before billing loads
function PremiumFeature() {
const { checkFeatureAccess } = useBilling()
// BUG: checkFeatureAccess is null while loading!
// This will throw: "checkFeatureAccess is not a function"
if (!checkFeatureAccess('premium-feature')) {
return <UpgradePrompt />
}
return <PremiumContent />
}
This crashes because checkFeatureAccess is null until billing data loads, not a callable function.
Correct: check both loaded and checkFeatureAccess
function PremiumFeature() {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded || !checkFeatureAccess) {
return <LoadingSkeleton />
}
if (!checkFeatureAccess('premium-feature')) {
return <UpgradePrompt />
}
return <PremiumContent />
}
Always check both loaded and checkFeatureAccess before calling the function to ensure billing data is available.
1.2 Skeleton Loading Patterns
Impact: CRITICAL (prevents layout shift)
Show appropriate loading states that match the expected content dimensions to prevent layout shift.
Incorrect: shows nothing or spinner
function Dashboard() {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded) {
return null // Content disappears!
}
return <DashboardContent />
}
Correct: show skeleton matching content layout
function Dashboard() {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded || !checkFeatureAccess) {
return (
<div className="space-y-4">
<div className="h-8 w-48 bg-gray-200 animate-pulse rounded" />
<div className="h-64 bg-gray-200 animate-pulse rounded" />
</div>
)
}
return <DashboardContent />
}
2. Server-Side Gating
Impact: HIGH
Client-side feature checks are for UI purposes only. Any sensitive operation or data access must verify subscription status server-side. Users can bypass client-side checks by modifying frontend code or using browser developer tools.
2.1 Verify Access on Server
Impact: HIGH (security requirement)
Never trust client-side access checks for operations that cost money, access sensitive data, or perform privileged actions.
Incorrect: trusts client-side check for sensitive operation
// API route
export async function POST(req: Request) {
// Client could bypass this by modifying frontend code
const { hasAccess } = await req.json()
if (!hasAccess) {
return Response.json({ error: 'No access' }, { status: 403 })
}
return performSensitiveOperation()
}
Correct: verify server-side
// API route
import { flowglad } from '@/lib/flowglad'
import { auth } from '@/lib/auth'
export async function POST(req: Request) {
const session = await auth()
if (!session?.user?.id) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const billing = await flowglad(session.user.id).getBilling()
if (!billing.checkFeatureAccess('api-access')) {
return Response.json({ error: 'Upgrade required' }, { status: 403 })
}
return performSensitiveOperation()
}
2.2 API Route Protection
Impact: HIGH (prevents unauthorized access)
Create a reusable pattern for protecting multiple API routes with feature checks.
Incorrect: duplicates check logic everywhere
// routes/generate.ts
export async function POST(req: Request) {
const session = await auth()
const billing = await flowglad(session.user.id).getBilling()
if (!billing.checkFeatureAccess('ai-generation')) {
return Response.json({ error: 'Upgrade required' }, { status: 403 })
}
// ... generation logic
}
// routes/export.ts
export async function POST(req: Request) {
const session = await auth()
const billing = await flowglad(session.user.id).getBilling()
if (!billing.checkFeatureAccess('export')) {
return Response.json({ error: 'Upgrade required' }, { status: 403 })
}
// ... export logic
}
Correct: create reusable middleware/helper
// lib/requireFeature.ts
import { flowglad } from '@/lib/flowglad'
import { auth } from '@/lib/auth'
export async function requireFeature(featureSlug: string) {
const session = await auth()
if (!session?.user?.id) {
return { error: 'Unauthorized', status: 401 }
}
const billing = await flowglad(session.user.id).getBilling()
if (!billing.checkFeatureAccess(featureSlug)) {
return { error: 'Upgrade required', status: 403 }
}
return { userId: session.user.id, billing }
}
// routes/generate.ts
export async function POST(req: Request) {
const result = await requireFeature('ai-generation')
if ('error' in result) {
return Response.json({ error: result.error }, { status: result.status })
}
const { userId, billing } = result
// ... generation logic
}
3. Feature Identification
Impact: MEDIUM
How you reference features affects code maintainability and environment portability.
3.1 Use Slugs Not IDs
Impact: MEDIUM (environment portability)
Feature IDs are auto-generated and differ between development, staging, and production environments. Slugs are stable identifiers you control.
Incorrect: hardcoding Flowglad IDs
// IDs change between environments!
if (billing.checkFeatureAccess('feat_abc123xyz')) {
// Works in dev, breaks in production
}
Correct: use slugs
// Slugs are stable across environments
if (billing.checkFeatureAccess('advanced-analytics')) {
// Works everywhere
}
Define feature slugs in your Flowglad dashboard and reference them consistently in code.
4. Component Wrapper Patterns
Impact: MEDIUM
Reusable patterns for gating components reduce boilerplate and ensure consistent behavior.
4.1 Feature Gate Component
Impact: MEDIUM (reduces boilerplate)
Create a declarative component for gating content.
Incorrect: repeats gate logic in every component
function AnalyticsDashboard() {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded || !checkFeatureAccess) return <Skeleton />
if (!checkFeatureAccess('analytics')) return <UpgradePrompt feature="analytics" />
return <Analytics />
}
function ExportButton() {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded || !checkFeatureAccess) return <Skeleton />
if (!checkFeatureAccess('export')) return <UpgradePrompt feature="export" />
return <ExportUI />
}
Correct: create reusable FeatureGate component
// components/FeatureGate.tsx
import { useBilling } from '@flowglad/nextjs'
import { ReactNode } from 'react'
interface FeatureGateProps {
feature: string
children: ReactNode
fallback?: ReactNode
loading?: ReactNode
}
export function FeatureGate({
feature,
children,
fallback = <UpgradePrompt />,
loading = <Skeleton />,
}: FeatureGateProps) {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded || !checkFeatureAccess) {
return <>{loading}</>
}
if (!checkFeatureAccess(feature)) {
return <>{fallback}</>
}
return <>{children}</>
}
// Usage
function AnalyticsDashboard() {
return (
<FeatureGate feature="analytics">
<Analytics />
</FeatureGate>
)
}
function ExportButton() {
return (
<FeatureGate feature="export" fallback={<LockedExportButton />}>
<ExportUI />
</FeatureGate>
)
}
4.2 Higher-Order Component Pattern
Impact: MEDIUM (alternative pattern for class components or full-page gates)
Use HOC pattern when you need to gate entire pages or components.
Incorrect: duplicates page-level checks
// pages/analytics.tsx
export default function AnalyticsPage() {
const { loaded, checkFeatureAccess } = useBilling()
if (!loaded || !checkFeatureAccess) return <PageSkeleton />
if (!checkFeatureAccess('analytics')) {
// Using redirect() in a client
---
*Content truncated.*
When not to use it
- →Implementing granular RBAC user roles
- →Caching dynamic content locally
Prerequisites
Limitations
- →Requires server-side verification for security
- →Billing hooks can be delayed during initial load
How it compares
It combines loading state management and entitlement checks specifically tailored for SaaS monetization flows.
Compared to similar skills
flowglad-feature-gating side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| flowglad-feature-gating (this skill) | 1 | 6mo | No flags | Intermediate |
| supabase-developer | 95 | 7mo | Review | Intermediate |
| supabase-mcp-integration | 13 | 8mo | Review | Advanced |
| shopify-development | 12 | 6mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by flowglad
View all by flowglad →You might also like
supabase-developer
daffy0208
Build full-stack applications with Supabase (PostgreSQL, Auth, Storage, Real-time, Edge Functions). Use when implementing authentication, database design with RLS, file storage, real-time features, or serverless functions.
supabase-mcp-integration
manutej
Comprehensive Supabase integration covering authentication, database operations, realtime subscriptions, storage, and MCP server patterns for building production-ready backends with PostgreSQL, Auth, and real-time capabilities
shopify-development
davila7
Build Shopify apps, extensions, themes using GraphQL Admin API, Shopify CLI, Polaris UI, and Liquid. TRIGGER: "shopify", "shopify app", "checkout extension", "admin extension", "POS extension", "shopify theme", "liquid template", "polaris", "shopify graphql", "shopify webhook", "shopify billing", "app subscription", "metafields", "shopify functions"
better-auth-best-practices
novuhq
Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.
nextjs-supabase-auth
davila7
Expert integration of Supabase Auth with Next.js App Router Use when: supabase auth next, authentication next.js, login supabase, auth middleware, protected route.
cloudbase-guidelines
TencentCloudBase
Essential CloudBase (TCB, Tencent CloudBase, 云开发, 微信云开发) development guidelines. MUST read when working with CloudBase projects, developing web apps, mini programs, or backend services using CloudBase platform.