Tutorial: Add an Upgrade Button
In this tutorial, you’ll install the SDK, configure it in local mode, and render an upgrade button that only appears for users on the Starter plan. Takes about 10 minutes.
What You’ll Build
Section titled “What You’ll Build”An upgrade button in your navigation bar that:
- Shows “Upgrade to Pro” for Starter plan users
- Hides automatically for Professional/Enterprise users
- Tracks impressions and CTA clicks
- Navigates to your pricing page on click
Prerequisites
Section titled “Prerequisites”- A React 18+ application (Next.js, Vite, or CRA)
- Node.js 22.13+
-
Install the SDK
Terminal window pnpm add @revturbine/sdk -
Create an Playbook fixture
For local development, you need a config file with plans, entitlements, and placements. Create
src/fixtures/playbook.json:playbook.json {"artifact_type": "playbook","format_version": "1.0.0","plans": [{ "id": "plan_starter", "unique_handle": "starter", "name": "Starter", "tier_position": 0, "sort_order": 0 },{ "id": "plan_pro", "unique_handle": "pro", "name": "Pro", "tier_position": 1, "sort_order": 1 }],"entitlements": [],"entitlement_rules": [],"segments": [],"content_ui_paths": [],"surface_templates": [{ "id": "button", "surface_type": "button", "fields": [] }],"placements": [{"id": "pl_nav_upgrade","name": "Upgrade button","category": "fixed","trigger": { "type": "surface_render", "slot_id": "nav_upgrade" },"payloads": [{"id": "pl_nav_upgrade_p0","target": { "plan_ids": ["plan_starter"], "segment_chips": [] },"surfaces": [{"template_id": "button","fields": {},"ctas": [{ "label": "Upgrade to Pro", "path": "open_checkout", "config": { "purchase": "pro" } }]}],"caps": {},"status": "active"}],"order": 0}]} -
Wrap your app with the provider
src/App.tsx import { RevTurbineProvider, RuntimeMode } from '@revturbine/sdk';import playbook from './fixtures/playbook.json';export default function App() {return (<RevTurbineProvideroptions={{tenantId: 'demo',apiKey: 'local',endpoint: 'http://localhost',mode: 'react',runtimeMode: RuntimeMode.LocalOnly,localRuntime: { playbook },// The button's CTA fires the action you configured; wire CTA// handling to your navigation — see the Quickstart.}}><NavBar /><MainContent /></RevTurbineProvider>);} -
Add the upgrade button slot
src/components/NavBar.tsx import { Slot } from '@revturbine/sdk';export function NavBar() {return (<nav style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '8px 16px' }}><span style={{ fontWeight: 600 }}>MyApp</span><div style={{ flex: 1 }} />{/* This slot renders only when targeting matches */}<Slotid="nav_upgrade"surfaceTemplateIds={['button']}/></nav>);} -
Verify it works
Start your dev server and you should see:
- Starter user → “Upgrade to Pro” button appears
- Professional user → nothing renders (slot is invisible)
To test different users, change the user context in the provider:
<RevTurbineProvideroptions={{...options,// Try: plan_handle: 'pro' — button disappearsuser: { id: 'user_1', plan_handle: 'starter' },}}><YourApp /></RevTurbineProvider>
What Just Happened
Section titled “What Just Happened”- The
RevTurbineProviderinitialized the SDK inlocal_onlymode with your Playbook - The
<Slot>asked the decision engine for a placement at slotnav_upgrade - The engine evaluated the payload target (
plan_ids: ['plan_starter']) against the user context - For Starter users, it resolved the
buttontemplate with the “Upgrade to Pro” CTA - For Pro users, no payload matched — the slot rendered nothing
Local mode is production-ready
Section titled “Local mode is production-ready”local_only is not a demo mode you graduate out of. It evaluates the full Playbook client-side and is a valid production runtime on its own — ship it as-is when a bundled Playbook, client-side decisioning, and localStorage events are enough for you.
Switch to the RevTurbine server runtime when you want what it adds, not because local mode isn’t “real”: live Playbook updates without a redeploy, server-authoritative usage and metering, A/B experiments, and centralized analytics through the ingest pipeline. See Runtime Modes for the full comparison.
To switch, point the provider at your tenant and drop the local runtime:
<RevTurbineProvider options={{ tenantId: 'demo', apiKey: 'local', endpoint: 'http://localhost', tenantId: process.env.NEXT_PUBLIC_RT_TENANT_ID!, apiKey: process.env.NEXT_PUBLIC_RT_API_KEY!, endpoint: process.env.NEXT_PUBLIC_RT_ENDPOINT!, mode: 'react', runtimeMode: RuntimeMode.LocalOnly, localRuntime: { playbook }, runtimeMode: RuntimeMode.Server, }}>Try It Live
Section titled “Try It Live”Next Steps
Section titled “Next Steps”- Tutorial: Gate a Premium Feature — add an entitlement gate
- Component Gallery — interactive demos of all built-in slot components