Adding custom content to Affinity
Extend the portal offering with custom extensions placed strategically throughout the portal.
Use content extensions to personalize Affinity with custom content. Custom content includes:
- Announcements
- Brand or educational content
- Product offers and merchandising
- Third-party app widgets
- Navigation
- Interactive custom components
This guide explains how you can add custom content to Affinity if you are on the Recharge Pro plan, or a Custom plan.
Platform:
- Shopify Checkout Integration
- Migrated Shopify Checkout Integration
Slot locations
You can place custom content globally across all pages, within specific layout regions, or inside dialog modes. See content extensions in the Recharge Help Center for information on the type of content that merchants can share through content extensions.
Note:Extending the customer portal with custom content is only available to merchants on the Recharge Pro or Custom plan.
Layout regions
Page/Section | Header | Footer | Sidebar |
---|---|---|---|
All pages |
|
|
|
Next order
|
|
|
|
Subscriptions
|
|
|
|
Upcoming orders
|
|
|
|
Previous orders
|
|
|
|
Addresses and payment details
|
|
|
|
Addresses
|
|
|
|
Payment methods
|
|
|
|
Homepage (if enabled) |
|
|
|
Dialog modes and slots
You can also inject content into dialogs on the overview page:
Slot names
dialog-header
: Renders above the dialog bodydialog-footer
: Renders below the dialog body and actions
Dialog modes
subscription
: Subscription management dialogproduct
: Product dialogswap
: Swap dialog
Scoping patterns
<mode>,<slot>
>subscription.dialog-header
<route>, <mode>, <slot>
>overview.subscription.dialog-footer
<route>, <slot>
> layout region (for example,overview.header
)*.dialog-header
> all dialogs (Recharge recommends using this sparingly)
Declare slot content (HTML)
Declare content with an inert script tag. The HTML inside is injected into the slot at runtime.
Single-target example (header on /overview
)
/overview
)<script type="text/html" data-recharge-slot="overview.header">
<p class="announcement">Remember to update your preferences!</p>
</script>
Multi-target example (global header + subscription dialog header)
<script type="text/html" data-recharge-slot="*.header,overview.footer, subscription.dialog-header">
<div class="global-promo"><strong>Spring Sale:</strong> Code SPRING15</div>
</script>
Rules for multi-target declarations:
- Commas separate targets
- Whitespace is ignored around targets
- Each target is matched independently
Dialog-only examples
Generic dialog header (all modes)
<script type="text/html" data-recharge-slot="dialog-header">
<div class="dialog-banner"><h4>Need help? Chat with us.</h4></div>
</script>
Subscription dialog footer only on /schedule
/schedule
<script type="text/html" data-recharge-slot="schedule.subscription.dialog-footer">
<div class="subscription-help"><small>Applies only to this subscription.</small></div>
</script>
Product dialog header and footer
<script type="text/html" data-recharge-slot="product.dialog-header">
<div class="product-upsell"><strong>Tip:</strong> Add accessories.</div>
</script>
<script type="text/html" data-recharge-slot="product.dialog-footer">
<div class="product-dialog-footer-note"><em>Pricing updates after confirmation.</em></div>
</script>
Combined multi-target
<script type="text/html" data-recharge-slot="*.header, subscription.dialog-footer">
<div class="multi-slot-block">Universal header + subscription dialog footer note.</div>
</script>
Advanced content extensions
By default, slot declarations support static HTML. To add functionality, place a placeholder element in the slot and initialize it with JavaScript once the slot is mounted. This lets you embed widgets or apps that stay in sync with the portal.
Lifecycle events you can use:
Recharge::slot::mounted { name, pathname }
Recharge::slot::unmounted
Recharge::slot::render
Recharge::slot::remove
Example: Initalize a subscription dialog widget
<script type="text/html" data-recharge-slot="subscription.dialog-header">
<div id="announcement-dialog-widget-root"></div>
</script>
<script>
document.addEventListener("Recharge::slot::mounted", (event) => {
const { name } = event.detail;
if (name === "dialog-header") {
const root = document.getElementById("announcement-dialog-widget-root");
if (root) {
// Initialize framework widget here
}
}
});
</script>
Note: The slot target already encodes the mode (for example,
subscription.dialog-header
), so checking the slot name is usually enough. If you need additional scoping, inspect the DOM or useevent.detail.pathname
.
Interactive extensions run independently from the portal, but you’ll need to keep them in sync. Use lifecycle events (mounted
, unmounted
, render
, remove
) to handle updates and ensure your extension responds whenever the customer portal changes state.
Matching logic
On the /overview
page in subscription mode, a slot named dialog-header will match any script whose data-recharge-slot contains one of:
dialog-header
*.dialog-header
overview.dialog-header
subscription.dialog-header
overview.subscription.dialog-header
If multiple scripts match the same slot, all are concatenated in DOM order.
Best practices
Follow these best practices to keep your custom content lightweight, maintainable, and consistent across the portal:
- Keep injected HTML minimal; move heavy logic to JavaScript after mounted
- Namespace CSS classes and IDs
- Avoid inline JavaScript inside the text/html block
- Consolidate multi-target declarations to reduce duplication
Troubleshooting
If your custom content isn’t appearing as expected, try the following troubleshooting steps:
- Confirm your plan includes slot permissions
- Check that your script tags exist early in the DOM
- Check target strings for typos or incorrect scoping
- Verify that the following slot component appeared:
data-testid="slot-<name>"
- Debug by logging lifecycle events:
document.addEventListener('Recharge::slot::mounted', e => console.log(e.detail));
FAQ
How do I target both the global header and a specific dialog footer simultaneously?
Use data-recharge-slot="*.header, subscription.dialog-footer"
.
What happens if multiple scripts target the same slot?
All matching scripts are combined in the DOM in the order they appear.
How can I update slot content dynamically?
Update the content inside the injected DOM element while the slot container itself stays the same.
The example below shows a combined layout and dialog targets plus a simple mounted event logge
<script type="text/html" data-recharge-slot="*.header, subscription.dialog-footer">
<section class="global-announcement">
<p><strong>New!</strong> Centralized subscription management.</p>
</section>
</script>
<script type="text/html" data-recharge-slot="product.dialog-header">
<div class="product-dialog-tip">Configure options below.</div>
</script>
<script type="text/html" data-recharge-slot="overview.header">
<div class="overview-banner">
<h4>Your next order summary</h4>
</div>
</script>
<script>
document.addEventListener("Recharge::slot::mounted", ({ detail }) => {
if (detail.name === "dialog-header") {
console.log("Dialog header slot mounted from", detail.pathname);
}
});
</script>
Updated 15 days ago