Feature Gating
Corral provides a full set of gating primitives for UI components, pages, API routes, and server functions. All gates read from the same session โ no extra API calls needed.
gates.tsx includes AuthGate, PlanGate, FeatureGate, BlurGate, PageGate, and SkeletonPage.
All gates support admin bypass by default.
FeatureGate
The primary gating component. Wraps content and shows or hides it based on whether the current user has access to a named feature.
Basic usage
import { FeatureGate } from '@corral/next' // or @corral/react
// Gate mode comes from corral.yaml (ai_analysis has gate: blur)
<FeatureGate feature="ai_analysis">
<AIAnalysisPanel />
</FeatureGate>
Override gate mode inline
// Override the YAML gate mode for this instance
<FeatureGate feature="ai_analysis" gate="block">
<AIAnalysisPanel />
</FeatureGate>
// Custom fallback instead of the auto-generated upgrade prompt
<FeatureGate
feature="ai_analysis"
fallback={<UpgradeCard feature="ai_analysis" />}
>
<AIAnalysisPanel />
</FeatureGate>
Props
| Prop | Type | Description |
|---|---|---|
feature | string | Feature name from corral.yaml |
gate | 'blur'|'skeleton'|'block'|'hide'|'disabled' | Override YAML gate mode |
fallback | ReactNode | Custom content to show when gated |
session | CorralSession | Pass session explicitly (Server Components) |
upgradeUrl | string | Override the upgrade CTA URL |
PlanGate
Gate by plan level rather than by feature name. Useful for generic upgrade prompts or plan-specific UI elements.
import { PlanGate } from '@corral/next'
// Show for pro and above
<PlanGate plan="pro">
<RealtimeFeed />
</PlanGate>
// Show ONLY for free users (inverse gate)
<PlanGate plan="free" exact>
<FreeTrialBanner />
</PlanGate>
// Show for enterprise only
<PlanGate plan="enterprise" exact fallback={<ContactSalesCard />}>
<AirGapConfig />
</PlanGate>
AuthGate
Show content only to authenticated (or unauthenticated) users. The simplest gate โ no plan or feature check involved.
import { AuthGate } from '@corral/next'
// Only for signed-in users
<AuthGate fallback={<SignInButton />}>
<UserMenu />
</AuthGate>
// Only for signed-OUT users (show login prompt)
<AuthGate require="unauthenticated">
<HeroCTA />
</AuthGate>
// Role check
<AuthGate role="admin">
<AdminToolbar />
</AuthGate>
PageGate
Wraps an entire page. On the server, checks session and either renders the page or returns a gate response (redirect, blurred preview, or block). Use this in Next.js layouts or page components.
import { PageGate } from '@corral/next'
export default async function AdvancedReportsPage() {
return (
<PageGate
feature="ai_analysis"
gate="blur"
upgradeUrl="/pricing"
previewLines={8} // show first 8 lines blurred
>
<AdvancedReportsContent />
</PageGate>
)
}
PageGate gate modes in detail
| Mode | Server behavior | Client behavior |
|---|---|---|
redirect |
Server-side redirect to /login or custom URL |
N/A |
block |
Renders a full-page upgrade prompt instead of content | CSS-only blur overlay with upgrade CTA |
blur |
Renders page with partial content, rest blurred | CSS blur filter + overlay on blurred section |
skeleton |
Renders page shell with skeleton loaders | Animated skeleton with "upgrade to see data" hint |
useFeatureGate
React hook for client-side conditional logic. Returns the gate state for a feature based on the current session.
import { useFeatureGate } from '@corral/react'
function ExportButton() {
const { allowed, loading, plan } = useFeatureGate('bulk_export')
if (loading) return <Skeleton width=120 />
if (!allowed) {
return (
<button disabled title={`Upgrade to Enterprise to export bulk data`>
Export All ๐
</button>
)
}
return <button onClick={handleBulkExport}>Export All</button>
}
// useSession for full session object
import { useSession } from '@corral/react'
function PlanBadge() {
const { session } = useSession()
return <span>Plan: {session?.plan.id ?? 'anonymous'}</span>
}
Return value
| Field | Type | Description |
|---|---|---|
allowed | boolean | Whether the current user can access this feature |
loading | boolean | Session still loading |
reason | 'unauthenticated'|'wrong_plan'|'meter_exceeded'|'ok' | Why access was denied |
plan | string | The minimum plan required |
upgradeUrl | string | Generated upgrade URL for this feature |
Server-Side Gating
Use requireFeature() or hasFeature() in server
components, server actions, and API route handlers.
requireFeature โ throws on failure
import { requireFeature, getSession } from '@/lib/corral'
// In a server action:
'use server'
export async function runAnalysis(input: AnalysisInput) {
await requireFeature('ai_analysis')
// throws CorralGateError if not allowed
// the error is caught by Next.js error boundaries
return doAnalysis(input)
}
hasFeature โ check without throwing
import { getSession } from '@/lib/corral'
export async function GET(req: Request) {
const session = await getSession()
if (!session) {
return Response.json({ error: 'unauthenticated' }, { status: 401 })
}
const canUseAI = session.features.includes('ai_analysis')
// or: corral.hasFeature(session, 'ai_analysis')
return Response.json({
data: canUseAI ? await getFullData() : await getBasicData(),
upgraded: canUseAI,
})
}
API Route Gating
Use the withFeature wrapper for clean API route gating.
Returns 403 with a structured error if the user doesn't have access.
import { withFeature } from '@corral/next'
export const POST = withFeature('ai_analysis', async (req, { session }) => {
// Only reached if user has ai_analysis feature
const body = await req.json()
// Track usage meter
await trackUsage(session.user.id, 'api_calls', 1)
const result = await runAIAnalysis(body)
return Response.json(result)
})
// Error response when gated:
// { "error": "feature_not_available",
// "feature": "ai_analysis",
// "required_plan": "pro",
// "upgrade_url": "/pricing" }
Middleware Gating
Gate entire route groups in Next.js middleware without any React involved. Runs at the edge before the page loads.
import { corralMiddleware } from '@corral/next'
export function middleware(req) {
return corralMiddleware(req, {
// Must be authenticated
protectedRoutes: ['/dashboard/:path*'],
// Must have specific plan
planRoutes: {
'/reports/advanced/:path*': 'pro',
},
// Must have specific feature
featureRoutes: {
'/api/analysis/:path*': 'ai_analysis',
},
// Must have role
roleRoutes: {
'/admin/:path*': 'admin',
},
loginUrl: '/login',
upgradeUrl: '/pricing',
})
}
All 15 Gating Patterns
Every combination of subject, scope, and behavior you'll need:
<AuthGate>gate: disabled<PlanGate plan="pro"><PlanGate plan="free" exact><AuthGate role="admin"><PageGate>withFeature('x', handler)checkLimit(userId, 'meter')session.plan.status === 'trialing'useFeatureGate() hook for custom conditional logic in components.Combining gates
Gates compose naturally. Use multiple gates in sequence for complex access rules:
{/* Must be logged in AND on pro+ AND not have exceeded meter */}
<AuthGate fallback={<LoginPrompt />}>
<FeatureGate feature="ai_analysis">
<MeterGate meter="api_calls" fallback={<LimitReachedBanner />}>
<AIAnalysisPanel />
</MeterGate>
</FeatureGate>
</AuthGate>