Skip to content

Tutorial: Show a Banner Placement

In this tutorial, you’ll render a banner at the top of your app — a targeted upsell or announcement with a call-to-action — controlled entirely from your config. The banner shows only for the users your config targets, and renders nothing for everyone else. Takes about 10 minutes.

A header banner that:

  • Renders a placement in a fixed slot via FixedSurfaceSlot
  • Shows only to a targeted audience (here, Pro users), and nothing otherwise
  • Has a dismissible CTA that opens your checkout flow
  • A React 18+ application with @revturbine/sdk installed
  • A RevTurbineProvider set up (see Quickstart)
  1. Add a banner placement to your config

    This is a minimal but complete ExportedConfig — author the same shape in the studio or with the CLI. The banner is a fixed placement bound to a named slot, targeted at the Pro plan.

    exported_config.json
    {
    "version": "v1",
    "plans": [
    { "id": "plan_pro", "unique_handle": "pro", "name": "Pro", "tier_position": 1, "sort_order": 1 }
    ],
    "entitlements": [],
    "entitlement_rules": [],
    "segments": [],
    "content_ui_paths": [],
    "surface_templates": [
    { "id": "banner_placement", "surface_type": "banner", "fields": [] }
    ],
    "placements": [
    {
    "id": "pl_annual_upsell",
    "name": "Annual billing upsell",
    "category": "fixed",
    "trigger": { "type": "surface_render", "slot_id": "app_header_banner" },
    "payloads": [
    {
    "id": "pl_annual_upsell_p0",
    "target": { "plan_ids": ["plan_pro"], "segment_chips": [] },
    "surfaces": [
    {
    "template_id": "banner_placement",
    "fields": {
    "header": "Save 20% with annual billing",
    "body": "Switch your Pro plan to annual and get two months free.",
    "dismissible": "Yes"
    },
    "ctas": [
    { "label": "Switch to annual", "path": "open_checkout", "config": { "purchase": "pro_annual" } }
    ]
    }
    ],
    "caps": {},
    "status": "active"
    }
    ],
    "order": 0
    }
    ]
    }
  2. Drop the slot into your header

    FixedSurfaceSlot renders the matching placement for the current user — or nothing. The id matches the placement trigger’s slot_id.

    src/components/AppHeader.tsx
    import { FixedSurfaceSlot } from '@revturbine/sdk';
    export function AppHeader() {
    return (
    <header>
    {/* Renders the targeted banner, or nothing */}
    <FixedSurfaceSlot id="app_header_banner" surfaceTemplateIds={['banner_placement']} />
    <nav>{/* ...your nav... */}</nav>
    </header>
    );
    }
  3. Provide the user context that targeting reads

    The placement targets plan_ids: ["plan_pro"], so the banner only renders for Pro users. Pass the user’s plan in the provider; the banner’s CTA dispatches through the UI path resolvers you registered at init.

    src/App.tsx
    <RevTurbineProvider
    options={{
    localRuntime: { exportedConfig },
    user: { id: 'user_123', context: { plan_handle: 'pro' } },
    }}
    >
    <AppHeader />
    </RevTurbineProvider>
  1. FixedSurfaceSlot asks the decision engine which placement wins for app_header_banner.
  2. The engine evaluates the payload’s target against the current user (plan, segments). If nothing matches, the slot renders nothing — your header is unchanged.
  3. The slot renders the surface’s fields (header, body) and CTA; clicking the CTA dispatches through your resolvers (see Quickstart).
  4. Dismissing the banner records the interaction, so any caps (cooldowns, max-per-period) apply on the next render.

Interactive Playground → Banner Slot