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

*.header

*.footer

*.sidebar

Next order /overview

overview.header

overview.footer

overview.sidebar

Subscriptions /subscriptions

subscriptions.header

subscriptions.footer

subscriptions.sidebar

Upcoming orders /schedule

schedule.header

schedule.footer

schedule.sidebar

Previous orders /orders

orders.header

orders.footer

orders.sidebar

Addresses and payment details /customer

customer.header

customer.footer

customer.sidebar

Addresses /shipping

shipping.header

shipping.footer

shipping.sidebar

Payment methods /payment_methods

payment_methods.header

payment_methods.footer

payment_methods.sidebar

Homepage (if enabled)

homepage-header

homepage-footer

homepage-sidebar

Dialog modes and slots

You can also inject content into dialogs on the overview page:

Slot names
  • dialog-header: Renders above the dialog body
  • dialog-footer: Renders below the dialog body and actions
Dialog modes
  • subscription: Subscription management dialog
  • product: Product dialog
  • swap: 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)

<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

<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 use event.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>