Placements
Placements are the SDK’s core output — they’re the decisions about what content to render in which slot, for which user, at what moment. This guide covers placement configuration, resolution, lifecycle, and chaining.
How Placements Work
Section titled “How Placements Work”- You define slots in your UI — named positions where placements can appear
- The SDK evaluates targeting rules against the current user context
- If a rule matches, the SDK resolves a placement decision with content, surface template, and CTA path
- The slot component renders the decision using a built-in or custom template
Slot Configuration
Section titled “Slot Configuration”Every slot needs an id and optionally a list of accepted surfaceTemplateIds:
<Slot id="dashboard_banner" surfaceTemplateIds={['banner_placement', 'banner_announcement']}/>| Field | Type | Description |
|---|---|---|
id | string | Unique slot identifier (matches server-side placement rules) |
surfaceTemplateIds | string[] | Templates this slot accepts (filters available placements) |
Slot Types
Section titled “Slot Types”Fixed Slots
Section titled “Fixed Slots”Always-visible inline placements — buttons, cards, meters:
Fixed slots render nothing if no placement matches — your layout stays intact.
Access Gate Slots
Section titled “Access Gate Slots”Wrap premium content and show an upgrade prompt when access is denied:
If the entitlement is granted, children render normally. If denied, the gate renders an upgrade placement in their place.
That denial UI is itself a placement, not hardcoded markup: the decision engine picks the winning placement for the gate (scoped by surfaceTemplateIds) and renders its content — modal or banner, headline, offer, and CTA. So what a denied user sees is controlled by your monetization config and can be changed, targeted, or A/B-tested in the studio without a code deploy. deniedFallback is shown only when no placement matches.
Message Slots
Section titled “Message Slots”Page-level overlays triggered by targeting rules — toasts, modals, banners:
Message slots render nothing until a targeting rule triggers. Place them at the top level of your layout.
Placement Output
Section titled “Placement Output”When a placement resolves, the decision contains:
interface PlacementOutput { output_id: string; category: string; surface: { type: RevTurbineComponentType; template?: string; slot_id?: string; }; content: Record<string, unknown>; // toast duration is in seconds cta_path?: Record<string, unknown>; promotion?: Record<string, unknown>; rule_id: string; decision_id: string; config_version: string; present_upsell: boolean;}Placement Lifecycle
Section titled “Placement Lifecycle”Impression → Interaction → Outcome
Section titled “Impression → Interaction → Outcome”┌─────────┐ ┌──────────┐ ┌──────────┐│ Resolve │ ──► │ Visible │ ──► │ Interact │└─────────┘ └──────────┘ └──────────┘ │ │ │ ┌─────┴─────┐ │ │ dismiss │ │ │ snooze │ │ │ ctaClick │ │ │ ctaComplete│ │ └───────────┘ │ ▼ (auto-tracked impression)Interaction Methods
Section titled “Interaction Methods”const { dismiss, snooze, ctaClick, ctaComplete } = usePlacement({ placement: { name: 'hero_banner' } });
// Dismiss — suppresses for default cooldownawait dismiss();
// Dismiss with custom cooldown (30 minutes)await dismiss(30 * 60 * 1000);
// Snooze — temporarily suppress (1 hour)await snooze(3600);
// Record CTA clickawait ctaClick();
// Record CTA completion (e.g., checkout finished)await ctaComplete();Each interaction is automatically tracked as a treatment interaction event.
Cap Policies
Section titled “Cap Policies”Placements can author cap policies that limit how often they appear:
const capPolicy = { caps: { max_per_period: { count: 3, period: 'session' }, cooldown_days: 1, },};Authoring a cap in the Playbook enables client-side cap enforcement for that placement. The decision API returns visible: false with a cap_exceeded reason when capped; the convenience getPlacement() API returns null.
Placement Chaining
Section titled “Placement Chaining”A CTA can point to another placement via cta_path.placement_handle:
import { parseUiPath } from '@revturbine/sdk';
// First placement decides: show upgrade bannerconst banner = await sdk.getPlacement({ slotId: 'dashboard_banner' });// banner.cta_path = { type: 'open_rt_placement', placement_handle: 'checkout_offer' }
// On CTA click, resolve the follow-up placementconst path = banner?.cta_path ? parseUiPath(banner.cta_path) : undefined;const followUp = path?.placement_handle ? await sdk.getPlacement({ placementHandle: path.placement_handle }) : null;UI Path Resolvers
Section titled “UI Path Resolvers”Map CTA actions to application navigation:
<RevTurbineProvider options={{ // ... uiPathResolvers: { open_pricing: () => router.push('/pricing'), upgrade: (uiPath) => { stripe.redirectToCheckout({ priceId: uiPath.plan_handle }); }, start_trial: () => router.push('/trial/start'), }, }}> <YourApp /></RevTurbineProvider>When a user clicks a CTA, the SDK calls the matching resolver.
Using the Hook
Section titled “Using the Hook”For custom rendering, use usePlacement directly:
const { visible, content, decision, isLoading, error, refresh, dismiss, ctaClick,} = usePlacement({ surfaceSlot: { id: 'dashboard_banner', surfaceTemplateIds: ['banner_placement'], }, ttlMs: 300_000, // Cache for 5 minutes autoLoad: true, // Load on mount});Live examples
Section titled “Live examples”Every built-in slot type, running live in local_only mode — edit the code and switch demo users to see the engine respond.
Fixed slots
Section titled “Fixed slots”Upgrade Button — an always-visible CTA in a nav slot.
Plans & Pricing Card — an inline pricing/plans card.
Quota Meter — a usage meter with progress bar and counter.
Annual Nudge Banner — an annual-plan nudge banner.
Global slots
Section titled “Global slots”Usage Warning Banner — a “running low” usage banner.
Usage Exhausted Modal — a limit-reached modal.
Trial Welcome Toast — a trial-welcome toast.
Next Steps
Section titled “Next Steps”- Component Gallery — interactive demos of every built-in slot component
- Custom Slot Types — register your own slot components
- Events & Analytics — track impressions and interactions
- Interactive Playground — try all slot types live