Theme developer guide
This guide covers how Context integrates with your Shopify theme, the loading strategies available, the events it fires, and best practices for working with dynamic components and inline scripts.
How Context Works
Context personalizes storefront content by replacing Shopify sections with targeted variations at page load. The process works in three phases:
- Initialization -- A lightweight script in the document
<head>captures page context (template, customer, cart, product data) and prepares the page for personalization. - Evaluation -- Context determines which recipe matches the current visitor based on your configured targeting rules.
- Rendering -- Matched variations are fetched and swapped into the page, replacing the default sections. Shopify section lifecycle events are fired so that theme scripts can re-initialize.
During phases 1 and 2, page sections are hidden to prevent a flash of default content. Once rendering completes, sections are revealed progressively and a ready event is dispatched.
Loading Strategies
Context needs to coordinate with your theme's JavaScript. Since many theme scripts initialize on the DOMContentLoaded event, Context provides three strategies for managing this interaction. You can configure the strategy in the Context app block settings under Loading strategy.
Redispatch (Default)
Setting value: "Redispatch (default)"Context allows the browser's native DOMContentLoaded to fire as normal. Once personalization is complete, Context dispatches a new DOMContentLoaded event so that theme scripts re-initialize against the updated DOM.
When to use: This is the best starting point for most themes. It works well when your theme scripts bind to DOMContentLoaded and can tolerate the event firing a second time.
Trade-off: Scripts that run side effects on DOMContentLoaded (such as appending elements or starting animations) may execute twice -- once on the original event and once on the redispatched event.
Intercept and Queue
Setting value: "Intercept and queue"An inline script runs very early in the page load and intercepts all DOMContentLoaded listeners registered on document and window. These listeners are queued and held until Context finishes rendering. At that point, the queued callbacks are executed in order against the personalized DOM.
While intercepting, document.readyState returns 'loading' so that scripts checking readiness behave correctly.
When to use: Use this strategy when you need to guarantee that theme scripts only see the personalized DOM. This prevents double-initialization and is the safest option for avoiding layout shift caused by scripts running before personalization.
Trade-off: Scripts that bypass addEventListener (for example, using inline onload attributes or polling document.readyState directly) will not be intercepted. Some third-party scripts may not be compatible with this approach.
Disabled
Setting value: "Disabled"Context takes no action regarding DOMContentLoaded. The browser fires it natively and Context does not redispatch or intercept.
When to use: Use this when your theme handles initialization independently of DOMContentLoaded, or when you have custom integration logic that listens for the relevantbits:context:ready event directly.
Additional Loading Options
These settings are available in the Context app block and affect how content is loaded and displayed.
Show content before Context is loaded
Setting: "Show content before Context is loaded" (default: off)By default, Context hides page sections while personalization is in progress to prevent a flash of default content. Enable this option if your personalized content is below the fold or if you prefer to show default content immediately and swap it when ready.
When this setting is off, sections receive a visibility: hidden style via the rb-context-loading CSS class on the <html> element. This class is removed once Context finishes.
Enable real-time updates
Setting: "Enable real-time updates" (default: off)When enabled, Context monitors for cart changes and time-based triggers after the initial page load. If a visitor's cart changes or a time boundary is crossed, Context re-evaluates the targeting rules and re-renders sections if a different variation now matches.
Cart changes are detected automatically by monitoring Shopify's /cart API calls and the theme:cart:change event. Time boundaries are checked on a regular interval.
Rule caching
Setting: "Rule caching" (range: 1--24 hours, default: 24)Controls how long a visitor's targeting results are cached in the browser before Context re-evaluates them on the next page load. Lower values mean more frequent re-evaluation but slightly more network overhead.
Combine matching recipes
Setting: "Combine matching recipes" (default: on)When enabled, all matching recipes apply to a visitor in priority order. Each section can only display one variation -- the first matching recipe that targets that section wins. When disabled, only the single highest-priority matching recipe applies.
Events
Context fires custom DOM events at key points in its lifecycle. Use these to coordinate your theme scripts with personalization.
relevantbits:context:loaded
Fired on window when the Context script has finished loading and the window.relevantbits API is available. At this point, personalization has not started yet.
window.addEventListener('relevantbits:context:loaded', function () {
// The Context API is now available
// Personalization has not yet been applied
});Typical use: Register configuration overrides or custom logic before Context begins rendering.
relevantbits:context:ready
Fired on window when Context has finished rendering all personalized sections and the page is fully ready. This is the primary event for theme developers to hook into.
window.addEventListener('relevantbits:context:ready', function () {
// All personalized sections are now in the DOM
// Safe to query personalized elements, start animations, etc.
});Typical use: Initialize sliders, carousels, lazy-loaded images, or any JavaScript that depends on the final personalized DOM. This event fires regardless of which loading strategy is active.
shopify:section:load
Fired on each newly rendered section element when Context replaces a section with a personalized variation. This is the standard Shopify section lifecycle event.
document.addEventListener('shopify:section:load', function (event) {
var section = event.target;
var sectionId = event.detail.sectionId;
// Initialize scripts for this specific section
});Typical use: Shopify themes already handle this event for the theme editor. Context fires it so that section-specific initialization code runs for personalized content too.
shopify:section:unload
Fired on a section element just before Context replaces it with a new variation. This is the standard Shopify section cleanup event.
document.addEventListener('shopify:section:unload', function (event) {
var section = event.target;
var sectionId = event.detail.sectionId;
// Clean up event listeners, timers, etc.
});Typical use: Destroy carousels, disconnect observers, or clean up any resources tied to the outgoing section.
DOMContentLoaded (redispatched)
When using the Redispatch loading strategy, Context fires a new DOMContentLoaded event on document after personalization completes. This allows theme scripts bound to this event to re-initialize.
Note: This is a synthetic event. The browser's original DOMContentLoaded will have already fired before this one.
CSS Classes
Context adds CSS classes to the <html> element and to personalized sections. You can use these for custom styling.
Class | Applied to | When |
rb-context-loading | <html>, individual sections | While personalization is in progress. Removed per-section as each finishes, and from <html> once all sections begin rendering. |
rb-context-ready | <html> | After personalization is complete and all events have fired. |
rb-context-section | Personalized section elements | Added to sections that have been replaced with a Context variation. |
Example: Styling personalized sections
/* Add a transition to personalized sections */
.rb-context-section {
animation: fadeIn 0.3s ease-in;
}
/* Hide a specific element until Context is ready */
.rb-context-loading .my-hero-banner {
opacity: 0;
}
.rb-context-ready .my-hero-banner {
opacity: 1;
transition: opacity 0.3s;
}Data Attributes
Personalized sections include data attributes for debugging and custom scripting.
Attribute | Description |
data-ctx-recipe-id | The ID of the recipe that matched for this section. |
data-ctx-recipe-name | The name of the matching recipe. |
data-ctx-variation-id | The ID of the variation that was rendered. |
data-ctx-variation-name | The name of the variation that was rendered. |
Example: Reading variation data
document.addEventListener('shopify:section:load', function (event) {
var section = event.target;
var variationName = section.getAttribute('data-ctx-variation-name');
if (variationName) {
console.log('Context rendered variation:', variationName);
}
});Analytics Events
Context automatically tracks engagement analytics when visitors interact with personalized content. These events are recorded for reporting in the Context dashboard.
Event | Trigger | Description |
session | Page load with a matching recipe | Recorded once per session (approximately every 24 hours per visitor). |
view | Personalized section enters the viewport | Recorded when a visitor scrolls a personalized section into view. Uses IntersectionObserver. |
click | Click inside a personalized section | Recorded on the first click within a personalized section. |
checkout | Order completed | Recorded via the web pixel on the post-purchase page, with recipe attribution. |
Third-party analytics forwarding
If the visitor has consented to tracking (via Shopify's Consent API), Context also forwards events to:
- Klaviyo -- Pushed as
Context session,Context view, andContext clickevents to the Klaviyo tracking queue. - Google Analytics -- Sent as
context_session,context_view, andcontext_clickevents viagtag()if available on the page.
These events include the recipe name, variation name, section ID, and page URL, allowing you to build segments and reports in your analytics tools.
Inline Scripts
Overview
By default, Context loads external scripts and stylesheets referenced in your variation sections but does not execute inline <script> blocks. This is the safest behavior for most themes.
If your variations contain sections that rely on inline scripts (for example, a section with an embedded widget bootstrap script), you can enable the Run inline scripts experimental setting.
How it works
When Run inline scripts is enabled:
- Context scans the variation HTML for
<script>tags without asrcattribute. - Data scripts are skipped --
<script>tags with types likeapplication/json,application/ld+json,text/template,text/html, ortext/x-templateare never executed. - Executable inline scripts are injected into the page and run in document order.
- Any external scripts that the inline code dynamically creates (for example, a bootstrap snippet that appends a
<script src="...">to the body) are tracked by Context so they can be cleaned up if the section is later replaced.
When Run inline scripts is disabled (the default):
- Inline
<script>blocks inside variations are not executed. - External
<script src="...">and<link rel="stylesheet">tags are still loaded normally. - Scripts that were already on the page from the original section remain untouched.
External script and stylesheet handling
Regardless of the inline scripts setting, Context handles external resources from variation HTML as follows:
- External scripts (
<script src="...">) are appended todocument.bodyonce. Context tracks loaded script URLs to avoid duplicates across section replacements. - External stylesheets (
<link rel="stylesheet">) are appended todocument.headonce. Duplicate detection prevents the same stylesheet from being loaded twice. - When a section is replaced with a new variation, previously loaded scripts and stylesheets from the old variation are cleaned up before the new ones are loaded.
Best Practices for Inline Scripts
1. Prefer external scripts over inline scripts.
Move initialization logic into external .js files referenced via <script src="...">. External scripts are loaded reliably regardless of the inline scripts setting and benefit from browser caching.
<!-- Preferred: External script -->
<script src="{{ 'my-section.js' | asset_url }}" defer></script>
<!-- Avoid: Inline script that may not execute -->
<script>
initializeMySection();
</script>2. Use shopify:section:load for section initialization.
Instead of relying on inline scripts, bind your initialization logic to the shopify:section:load event. This works with both the Shopify theme editor and Context replacements.
document.addEventListener('shopify:section:load', function (event) {
var container = event.target;
// Initialize components within this section
var slider = container.querySelector('.my-slider');
if (slider) {
new SliderComponent(slider);
}
});3. Use relevantbits:context:ready for page-level initialization.
If you need to run logic after all personalization is complete, listen for the ready event rather than placing inline scripts in your sections.
window.addEventListener('relevantbits:context:ready', function () {
// All sections are personalized, safe to initialize page-level features
lazyLoadImages();
initializeScrollAnimations();
});4. Clean up resources on shopify:section:unload.
If your section scripts create timers, observers, or global event listeners, clean them up when the section is unloaded to prevent memory leaks and double-initialization.
document.addEventListener('shopify:section:unload', function (event) {
var container = event.target;
var slider = container.querySelector('.my-slider');
if (slider && slider._sliderInstance) {
slider._sliderInstance.destroy();
}
});5. Guard against double-initialization.
When using the Redispatch loading strategy, DOMContentLoaded fires twice. Protect your initialization code with a guard variable.
(function () {
var initialized = false;
document.addEventListener('DOMContentLoaded', function () {
if (initialized) return;
initialized = true;
// One-time initialization here
});
})();6. Keep inline scripts idempotent when the setting is enabled.
If you must use inline scripts with the Run inline scripts setting enabled, ensure they are safe to run multiple times. Context may re-execute them if a section is replaced during real-time re-evaluation.
<script>
// Guard against re-initialization
if (!window.myWidgetLoaded) {
window.myWidgetLoaded = true;
loadWidget();
}
</script>Preview Mode
Context includes a preview mode for testing recipes before they go live. Append ?recipe={recipeId} to any storefront URL to preview a specific recipe.
In preview mode:
- A banner appears at the top of the page showing which recipe is being previewed.
- Analytics events (session, view, click) are not recorded.
- Real-time re-evaluation is disabled.
- If Combine matching recipes is enabled, you can preview multiple recipes by passing comma-separated IDs:
?recipe=id1,id2.
The window.relevantbits API
Context exposes a global window.relevantbits object with the following properties and methods available after the relevantbits:context:loaded event.
Property / Method | Description |
window.relevantbits.enabled | Boolean indicating whether Context has been initialized on this page. |
window.relevantbits.ready | Boolean indicating whether personalization is complete (only present in intercept mode). |
window.relevantbits.template | The current Shopify template name (e.g., 'index', 'product', 'collection'). |
window.relevantbits.setOptions(options) | Override Context settings at runtime. Overrides persist in localStorage until cleared. See Runtime option overrides. |
window.relevantbits.getContext() | Returns the stored context data including the matched recipe and visitor state. |
window.relevantbits.setContext(data) | Updates the stored context data. |
Runtime option overrides
You can override Context settings at runtime using setOptions. This is useful for testing different configurations without modifying the theme.
window.relevantbits.setOptions({
debug: true, // Enable console logging
enable_real_time: true, // Force real-time updates on
});Overrides are stored in localStorage and persist across page loads. To clear them:
window.relevantbits.setOptions({});Debug mode
Enable debug mode to see detailed logging in the browser console. This logs every step of the Context lifecycle including recipe matching, section rendering, script loading, and event recording.
window.relevantbits.setOptions({ debug: true });Or enable it in the Context app block settings under Enable debug mode.
Integration Patterns
Pattern: Dynamic component initialization
If your theme has components (sliders, accordions, tabs) that need JavaScript initialization, use the section lifecycle events:
Pattern: Conditional logic based on personalization
window.addEventListener('relevantbits:context:ready', function () {
// Check if a specific section was personalized
var hero = document.querySelector(
'.shopify-section[data-ctx-variation-name]',
);
if (hero) {
console.log('Hero was personalized with:', hero.dataset.ctxVariationName);
}
});Pattern: Waiting for Context when it may not be installed
(function () {
var timeout = setTimeout(function () {
// Context did not load within 2 seconds, proceed with defaults
initPage();
}, 2000);
window.addEventListener('relevantbits:context:ready', function () {
clearTimeout(timeout);
initPage();
});
function initPage() {
// Your initialization logic
}
})();Troubleshooting
Sections flash default content before personalization
- Ensure Show content before Context is loaded is disabled (the default).
- Check that the Context app block is placed in your theme's
<head>section. - If using a custom theme, verify that the
rb-context-loadingCSS class is not being overridden.
Theme scripts don't initialize after personalization
- Try switching the loading strategy. Start with Redispatch, then try Intercept and queue if scripts run too early.
- Ensure your scripts handle the
shopify:section:loadevent for section-specific initialization. - Add a
relevantbits:context:readylistener for page-level initialization.
Sliders, carousels, or tabs break after personalization
- Verify that your component scripts listen for
shopify:section:loadand re-initialize when the event fires. - Add cleanup logic on
shopify:section:unloadto destroy existing instances before the section is replaced.
Third-party widgets don't appear in personalized sections
- Enable Run inline scripts if the widget relies on an inline bootstrap script.
- If the widget loads via an external script, ensure the
<script src="...">tag is included in your variation section's Liquid template.
Double-initialization of scripts
- If using Redispatch, guard your
DOMContentLoadedlisteners against running twice (see best practices above). - Switch to Intercept and queue to prevent the original
DOMContentLoadedlisteners from firing before personalization.
On this page
- Theme developer guide
- How Context Works
- Loading Strategies
- Redispatch (Default)
- Intercept and Queue
- Disabled
- Additional Loading Options
- Show content before Context is loaded
- Enable real-time updates
- Rule caching
- Combine matching recipes
- Events
- relevantbits:context:loaded
- relevantbits:context:ready
- shopify:section:load
- shopify:section:unload
- DOMContentLoaded (redispatched)
- CSS Classes
- Example: Styling personalized sections
- Data Attributes
- Example: Reading variation data
- Analytics Events
- Third-party analytics forwarding
- Inline Scripts
- Overview
- How it works
- External script and stylesheet handling
- Best Practices for Inline Scripts
- Preview Mode
- The window.relevantbits API
- Runtime option overrides
- Debug mode
- Integration Patterns
- Pattern: Dynamic component initialization
- Pattern: Conditional logic based on personalization
- Pattern: Waiting for Context when it may not be installed
- Troubleshooting
- Sections flash default content before personalization
- Theme scripts don't initialize after personalization
- Sliders, carousels, or tabs break after personalization
- Third-party widgets don't appear in personalized sections
- Double-initialization of scripts