Tutorial: Gate a Premium Feature
In this tutorial, you’ll gate a premium feature (video export) behind an entitlement check and show an upgrade modal when access is denied. Takes about 10 minutes.
What You’ll Build
Section titled “What You’ll Build”A video export button that:
- Checks the
data_exportentitlement before allowing access - Shows an upgrade modal when the user doesn’t have the entitlement
- Works instantly in local mode, no API calls needed
Prerequisites
Section titled “Prerequisites”- A React 18+ application with
@revturbine/sdkinstalled - A
RevTurbineProviderset up (see Quickstart)
-
Add entitlement and gate placement to your config
Add to your
exported_config.json:exported_config.json {"version": "v1","plans": [{ "id": "plan_free", "unique_handle": "free", "name": "Free", "tier_position": 0, "sort_order": 0 },{ "id": "plan_pro", "unique_handle": "professional", "name": "Professional", "tier_position": 1, "sort_order": 1 }],"entitlements": [{ "id": "ent_data_export", "unique_handle": "data_export", "name": "Data Export", "type": "feature" }],"entitlement_rules": [{"id": "er_data_export_pro","entitlement_id": "ent_data_export","targets": [{ "kind": "plan", "id": "plan_pro" }],"segment_ids": [],"type_fields": { "kind": "feature", "enabled": true }}],"segments": [],"content_ui_paths": [],"surface_templates": [{ "id": "modal_overlay", "surface_type": "modal", "fields": [] }],"placements": [{"id": "pl_export_gate","name": "Export gate","category": "gated","trigger": { "type": "entitlement_gate", "entitlement_handle": "data_export" },"payloads": [{"id": "pl_export_gate_p0","target": { "plan_ids": ["plan_free"], "segment_chips": [] },"surfaces": [{"template_id": "modal_overlay","fields": {"modal_type": "Blocking","header": "Unlock Data Export","body": "Video export is available on the Professional plan. Upgrade to export your projects in MP4, WebM, and GIF formats."},"ctas": [{ "label": "View Plans", "path": "open_checkout", "config": { "purchase": "professional" } },{ "label": "Maybe later", "path": "dismiss" }]}],"caps": {},"status": "active"}],"order": 0}]} -
Create the gated component
src/components/ExportPanel.tsx import { AccessGateSurfaceSlot } from '@revturbine/sdk';function ExportControls() {return (<div style={{ padding: 16, background: '#e8f5e9', borderRadius: 8 }}><h3>Export Settings</h3><select><option>MP4 (1080p)</option><option>WebM (720p)</option><option>GIF (480p)</option></select><button style={{ marginTop: 8 }}>Start Export</button></div>);}export function ExportPanel() {return (<AccessGateSurfaceSlotid="editor_export_action"surfaceTemplateIds={['modal_overlay']}entitlementHandle="data_export">{/* This only renders when the entitlement is granted */}<ExportControls /></AccessGateSurfaceSlot>);} -
Use it in your page
src/pages/Editor.tsx import { ExportPanel } from '../components/ExportPanel';export default function Editor() {return (<div><h1>Video Editor</h1><div>{/* ... editor UI ... */}</div><h2>Export</h2><ExportPanel /></div>);} -
Test with different users
// Free user → sees upgrade modal<RevTurbineProvideroptions={{// ...user: { id: 'user_1', context: { plan_handle: 'free' } },}}>// Professional user → sees export controls<RevTurbineProvideroptions={{// ...user: { id: 'user_2', context: { plan_handle: 'professional' } },}}>
How It Works
Section titled “How It Works”The AccessGateSurfaceSlot component:
- Checks the
data_exportentitlement against the user’s plan - If granted → renders the children (
ExportControls) - If denied → resolves the gate placement and renders the upgrade modal
The gate check happens instantly in local_only mode using the ExportedConfig.
Alternative: Hook-Based Approach
Section titled “Alternative: Hook-Based Approach”For more control, use the useEntitlement hook directly:
import { useEntitlement, usePlacement } from '@revturbine/sdk';
function ExportPanel() { const { allowed, denied } = useEntitlement({ handle: 'data_export', });
const { visible, content, dismiss, ctaClick } = usePlacement({ surfaceSlot: { id: 'editor_export_action', surfaceTemplateIds: ['modal_overlay'], }, autoLoad: denied, // Only load placement if denied });
if (allowed) return <ExportControls />;
if (visible) { return ( <div className="modal-overlay"> <h2>{content?.header}</h2> <p>{content?.body}</p> <button onClick={() => ctaClick()}>{content?.cta_label}</button> <button onClick={() => dismiss()}>Maybe Later</button> </div> ); }
return <button disabled>Export (Upgrade Required)</button>;}Try It Live
Section titled “Try It Live”Next Steps
Section titled “Next Steps”- Tutorial: Track Usage and Show a Quota Meter — usage-based entitlements
- Entitlements Guide — all entitlement types and patterns