Headless Personalization
Code-driven personalization with Ours Privacy. Use window.ours_experiments.getVisitorContext() to access geo, UTM, device, and visitor status, and render targeted content in your own application code.
Headless personalization is the code-driven personalization pattern for Ours Privacy. Your application reads visitor signals (geo, UTM, device, visitor status, and the properties your personalization rules have accumulated) from window.ours_experiments.getVisitorContext() and renders targeted content directly in your own JavaScript.
Use this when you want to make rendering decisions from visitor signals. For the always-on rules concept, see Content Personalization. For experiment assignment APIs (which variant the visitor was assigned to), see the JavaScript SDK.
Quick Start
const ctx = window.ours_experiments.getVisitorContext();
// Geo-targeted content
if (ctx.geo.country_region === 'TX') {
showTexasComplianceBanner();
}
// Campaign-specific messaging
if (ctx.utm.source === 'meta' && ctx.visitor_status === 'new') {
showFacebookWelcomeOffer();
}
// Device-specific layout
if (ctx.device.type === 'mobile') {
showMobileOptimizedHero();
}Browser Signals
Signals available immediately on page load.
UTM Parameters
ctx.utm;
// {
// source: 'meta',
// medium: 'paid',
// campaign: 'spring-2026',
// content: 'hero-cta',
// term: 'privacy software'
// }All values are string | null. Parsed from window.location.search on each page load.
Initial UTM Parameters
ctx.initial_utm;
// {
// source: 'meta',
// medium: 'paid',
// campaign: 'spring-2026',
// content: null,
// term: null
// }The visitor's UTM values from their first tracked arrival for the current experiment cookie, persisted in the experiment cookie. Set once on the first visit that carries any UTM parameter for that tracked visitor, then reused on later pageviews for the same visitor ID.
- Returns
nullif the visitor has never arrived via a URL with UTM parameters. - Same shape as
ctx.utmwhen present. - Useful for targeting or analytics based on the visitor's original acquisition channel, regardless of their current page.
// Show content based on original acquisition channel
if (ctx.initial_utm?.source === 'meta' && ctx.utm.source === null) {
showReturningFacebookVisitorOffer();
}Query Parameters
ctx.query_params;
// { utm_source: 'meta', ref: 'partner-123', promo: 'SAVE20' }All current URL query parameters as a flat key-value object.
Visitor Status
ctx.visitor_status; // 'new' | 'returning'Based on whether the current visitor ID has already been seen by the experiment runtime for this browser's experiment cookie.
Referrer
ctx.referrer; // 'https://google.com/search?q=...' | nullDevice & Browser
ctx.device;
// {
// type: 'mobile', // 'desktop' | 'mobile' | 'tablet'
// os: 'iOS',
// browser: 'Safari',
// screen_width: 390,
// screen_height: 844,
// language: 'en-US'
// }Time
ctx.time;
// {
// day_of_week: 2, // 0 (Sunday) – 6 (Saturday)
// hour: 14, // 0-23, visitor's local time
// timezone: 'America/Chicago'
// }Geo and Device Signals
Geographic and device signals are resolved at the edge and available alongside the browser signals above.
Geolocation
ctx.geo;
// {
// country: 'US',
// country_region: 'TX',
// city: 'Austin',
// time_zone: 'America/Chicago'
// }Device Type
ctx.geo.is_mobile_viewer; // 'true' | 'false'
ctx.geo.is_desktop_viewer; // 'true' | 'false'
ctx.geo.is_tablet_viewer; // 'true' | 'false'Edge-resolved device flags that complement the client-side ctx.device object.
Accumulated Personalization Properties
ctx.properties;
// {
// visited_pricing: true,
// last_campaign: 'spring-2026',
// pageview_count: 7
// }The visitor traits your personalization property rules have accumulated, keyed by property key. Every value is a single scalar (string, number, or boolean), or null when the captured field was itself empty. Property rules never store nested objects or raw event payloads, so there is nothing to walk into.
Guard every read. A visitor who has not matched any property rule yet has an empty bag, and the bag itself is absent until the runtime has resolved the visitor:
// Boolean trait: compare explicitly instead of relying on truthiness, so an
// absent bag renders the default experience rather than throwing.
if (ctx?.properties?.visited_pricing === true) {
showPricingFollowUp();
}
// Scalar trait
if (ctx?.properties?.last_campaign === 'spring-2026') {
showSpringCampaignHero();
}A value accumulated during the current page view reaches the browser on the visitor's next full page load. If your rendering decision has to react within the same page view, read the event you are already sending instead of waiting on the property.
Data Safety
Accumulated property values are delivered to the browser and can be queried with a visitor ID. Use only values that are safe for browser delivery. Do not use secrets, credentials, protected health information, or other confidential data in personalization rules.
Targeting Rules on Visitor Signals
Everything above is also available to the visitor targeting on a personalization rule or experiment, so you can gate a rule on a signal without writing any code. Name the signal as the rule key:
| Rule key | Reads |
|---|---|
geo.country, geo.region, geo.city | Edge-resolved location |
utm.source, utm.medium, utm.campaign, utm.content, utm.term | UTM parameters on the current URL |
initial_utm.source, initial_utm.medium, initial_utm.campaign, initial_utm.content, initial_utm.term | The visitor's original acquisition UTMs |
device.type, device.os, device.browser, device.language, device.screen_width, device.screen_height | Client-side device and browser |
time.hour, time.day_of_week, time.timezone | The visitor's local time |
query_params.<name> | A single query parameter on the current URL |
visitor_status | new or returning |
referrer | The document referrer |
| Any other bare key | An accumulated personalization property with that key |
A string rule value matches case-insensitively. An array of values matches if the signal equals any of them. Multiple keys on one rule must all match.
Accumulated property keys are matched as whole keys, so a rule key of visited_pricing reads the property named visited_pricing. A key that names no signal above and no property never matches.
Event: visitor:ready
Subscribe to the visitor context becoming available after window.ours_experiments.init(...) runs:
window.ours_experiments.on('visitor:ready', (ctx) => {
// ctx contains the current visitor context
if (ctx.geo.country_region === 'CA') {
showCaliforniaDisclosure();
}
});If you are attaching listeners after the runtime has already loaded, call window.ours_experiments.getVisitorContext() directly.
Use Cases
Geographic compliance messaging: Show state-specific disclaimers or pricing based on ctx.geo.country_region.
Campaign-matched landing pages: Adjust hero copy to match the ad the visitor clicked, using ctx.utm.source and ctx.utm.campaign.
Returning visitor recognition: Show a loyalty message or skip the intro for returning visitors using ctx.visitor_status.
Analytics enrichment: Push visitor geo, UTM, and device data into your analytics tool or GTM dataLayer.
Conditional rendering: Show or hide page sections based on any combination of visitor signals: device type, location, behavior history.
Next Steps
- Content Personalization: Always-on visitor-context and behavior-targeting rules
- Headless Experiments: Run an A/B test where your application code renders the variant
- JavaScript SDK: Experiment assignments and event subscriptions
- Installation: Set up the experiment runtime on your site
How is this guide?

