For merchants who need full control over the HTML layout or want to build a completely custom loyalty page, JeriCommerce provides standalone Liquid section templates that load the loyalty page engine directly from the CDN. This guide is aimed at developers building custom storefront experiences for their merchants.
If the merchant is happy with the default look and just wants to tweak colors and copy, use the default template guide instead.
JeriCommerce provides two ready-made CDN templates. Copy the one that best fits your needs:
templates/visual.liquid) — Full-featured: hero, how-it-works steps, earning rows with SVG icons, tier cards, and loading skeleton. Same look as the theme block.templates/minimal.liquid) — Compact list layout: title + balance bar, earnings as a simple list, tiers as rows. Inline CSS, no external stylesheet needed.You can obtain these from the JeriCommerce admin under Loyalty Page → Get template code, or copy directly from the repository files.
sections/ directory, click Add a new section.jeri-loyalty) and paste the template code.Create a JSON page template that references your section:
// templates/page.loyalty.json
{
"sections": {
"loyalty": {
"type": "jeri-loyalty"
}
},
"order": ["loyalty"]
}Alternatively, add the section to an existing page template via the theme editor: Customize → Add section.
Go to Shopify Admin → Pages → Add page → Select the "loyalty" template.
CDN templates load the same JavaScript that powers the theme app extension block — bundled, minified, and always up to date:
<!-- Always the latest version --> <script src="https://cdn.jericommerce.com/shopify/loyalty-page.js" defer></script> <link rel="stylesheet" href="https://cdn.jericommerce.com/shopify/loyalty-page.css" />
The engine auto-detects the shop domain from the data-shop attribute on the root element, or falls back to window.Shopify.shop. Customer ID is passed via data-shopify-id using Liquid.
The template engine uses two systems to bind API data to your HTML:
1. Variable Interpolation — {varName}
Write {varName} tokens anywhere in your HTML text content. The JS engine walks all text nodes and replaces them with API data. This is the primary way to display data.
<h2>{hero.tierName}</h2>
<p>You have <strong>{hero.balance}</strong> {hero.balanceName}</p>Inside repeating templates (tiers, earnings), variables are scoped to the template data:
<h3>{tier.name}</h3>
<span>{tier.requiredPoints}</span>
<span>{earning.points}</span>Syntax uses single curly braces {key} — no conflict with Liquid ({{ }}), JS template literals (${ }), or CSS.
2. Data Attributes — data-jeri
Used for non-text operations that can't be expressed as text interpolation:
| Attribute | Purpose | Example |
|---|---|---|
| data-jeri="name" on | Defines a repeating row template | |
| data-jeri="name" on container | Receives cloned template rows | |
| data-jeri="key" on | Sets src attribute (hides if empty) | |
| data-jeri="key" on elements | Used by JS for show/hide logic | |
| data-jeri-show-if="value" | Show element only when data matches | data-jeri-show-if="completed" |
| data-jeri-hide-if="value" | Hide element when data matches | data-jeri-hide-if="1" |
| data-jeri-html="key" | Inject raw HTML content | |
| data-jeri-group="name" | Group container (hidden when empty) | data-jeri-group="purchases" |
| data-jeri-action="key" | Action button — binds click handler | data-jeri-action="earning.action" |
| Need | Use |
|---|---|
| Display text/numbers | {varName} in text content |
| Mix variables with custom copy | Earn {earning.points} with {earning.title} |
| Set image src | data-jeri="key" on |
| Show/hide based on value | data-jeri-show-if / data-jeri-hide-if |
| Inject rich HTML content | data-jeri-html="key" |
| Define a repeating row | + |
| Show/hide whole containers | data-jeri="key" (JS controls display) |
earning.*)Used inside the <template data-jeri="earning-row"> template.
| Field | Type | Description |
|---|---|---|
| earning.type | string | Flow type: purchase, social, referral, profile, verify, wallet, scan, link |
| earning.title | string | Human-readable title for the earning rule |
| earning.description | string | Detailed description of how to earn |
| earning.points | string | Points value (e.g., "5 points/EUR", "100 points") |
| earning.iconUrl | string | Icon URL (set via data-jeri on |
| earning.status | string | completed or pending (only set when logged in) |
| earning.action | string | Action type: wallet, verify, follow, referral, or empty |
| earning.actionLabel | string | Button label text for the action |
tier.*)Used inside the <template data-jeri="tiers"> template.
| Field | Type | Description |
|---|---|---|
| tier.name | string | Tier name (e.g., "Gold", "VIP") |
| tier.content | string | Tier description/benefits — may contain HTML, use data-jeri-html |
| tier.image | string | Tier image URL (set via data-jeri on |
| tier.requiredPoints | string | Points needed (e.g., "1,000 points") |
| tier.factor | string | Points multiplier (e.g., "2"). Auto-hidden when "1" |
| tier.isCurrent | string | true or false — whether this is the customer's current tier |
hero.*)Interpolated directly in the hero section HTML (not inside templates).
| Field | Type | Description |
|---|---|---|
| hero.programName | string | Program name from the API |
| hero.balance | string | Customer's current point balance (logged-in only) |
| hero.balanceName | string | Name for points (e.g., "points", "stars") |
| hero.tierName | string | Customer's current tier name (logged-in only) |
| hero.factorValue | string | Points multiplier for current tier (logged-in only) |
These use data-jeri for show/hide logic — JS toggles their display property:
| Selector | Behavior |
|---|---|
| hero.loggedIn | Shown when customer is logged in |
| hero.guest | Shown for guests (hidden for logged-in) |
| hero.factor | Shown when current tier factor > 1 |
| hero.progress | Shown when multiple tiers exist |
| hero.progressFill | JS sets style.width to progress percentage |
| hero.tierLabels | JS builds tier name + points labels programmatically |
| section.howItWorks | Hidden automatically for logged-in customers |
Every custom template must include this root element with the required attributes:
<div
id="jeri-loyalty-page-root"
class="jeri-loyalty-page jeri-loyalty-page--loading"
data-shop="{{ shop.permanent_domain }}"
data-icon-base="https://cdn.jericommerce.com/shopify/"
data-locale="{{ request.locale.iso_code }}"
{% if customer %}data-shopify-id="{{ customer.id }}"{% endif %}
>
<!-- Your custom HTML here -->
</div>| Attribute | Required | Purpose |
|---|---|---|
| id="jeri-loyalty-page-root" | Yes | JS entry point — the engine looks for this ID |
| class="jeri-loyalty-page" | Yes | CSS scoping + CSS variables |
| data-shop | Yes | Shop domain for API calls |
| data-shopify-id | Conditional | Customer ID (only when logged in) |
| data-icon-base | Recommended | URL prefix for earning icons |
| data-locale | Recommended | Store locale for i18n (falls back to "en") |
id="jeri-loyalty-page-root" on the root element — JS needs it to initialize.class="jeri-loyalty-page" on root — CSS variables are defined here.data-shop and data-shopify-id — required for API calls and customer identification.<template data-jeri="X"> needs a matching <div data-jeri="X"> as its sibling.data-jeri on show/hide containers — hero.loggedIn, hero.guest, hero.progress, etc.{varName} freely in any text node — the engine replaces all tokens it recognizes.data-jeri-html="key" for HTML content (not {var}, which would be escaped).
Earning rows can include action buttons for logged-in customers. Four flow types support actions:
| Flow Type | Action | Button Label | Behavior |
|---|---|---|---|
| INSTALL_WALLET_PASS | wallet | Send to my inbox | Sends the loyalty pass to the customer's email |
| VERIFY_ACCOUNT | verify | Send email | Sends a verification email |
| FOLLOW_US | follow | Follow | Opens the social profile in a new tab |
| MEMBER_GET_MEMBER | referral | Copy link | Copies the referral URL to clipboard |
Include the button in your earning-row template:
<button class="my-custom-button"
data-jeri-action="earning.action"
style="display:none">
{earning.actionLabel}
</button>Buttons only appear when the customer is logged in and the flow is not completed (except referral, which is always available). Button states cycle through: default → loading → success/error (reverts after 2 seconds). To remove action buttons entirely, simply omit the <button data-jeri-action="..."> element from your template.
Here is a complete minimal custom template to use as a starting point:
<!-- CDN Scripts -->
<script src="https://cdn.jericommerce.com/shopify/loyalty-page.js" defer></script>
<link rel="stylesheet" href="https://cdn.jericommerce.com/shopify/loyalty-page.css" />
<div
id="jeri-loyalty-page-root"
class="jeri-loyalty-page jeri-loyalty-page--loading"
data-shop="{{ shop.permanent_domain }}"
data-icon-base="https://cdn.jericommerce.com/shopify/"
data-locale="{{ request.locale.iso_code }}"
{% if customer %}data-shopify-id="{{ customer.id }}"{% endif %}
>
<!-- Hero -->
<section>
<h1>{hero.programName}</h1>
<div data-jeri="hero.loggedIn" style="display:none">
<p>{hero.tierName} — {hero.balance} {hero.balanceName}</p>
<a href="#jeri=loyalty/rewards">View my rewards</a>
</div>
<div data-jeri="hero.guest">
<a href="/account/login">Join now</a>
</div>
</section>
<!-- Tiers -->
<template data-jeri="tiers">
<div>
<h3>{tier.name}</h3>
<p>{tier.requiredPoints}</p>
<div data-jeri-html="tier.content"></div>
</div>
</template>
<div data-jeri="tiers"></div>
<!-- Earnings -->
<template data-jeri="earning-row">
<div>
<img data-jeri="earning.iconUrl" alt="" width="32" height="32" />
<strong>{earning.title}</strong>
<span>{earning.points}</span>
<p>{earning.description}</p>
<button data-jeri-action="earning.action" style="display:none">
{earning.actionLabel}
</button>
<span data-jeri="earning.status" data-jeri-show-if="completed">Done</span>
</div>
</template>
<div data-jeri-group="purchases">
<h3>Purchases</h3>
<div data-jeri="earnings-purchases"></div>
</div>
<div data-jeri-group="actions">
<h3>Actions</h3>
<div data-jeri="earnings-actions"></div>
</div>
<!-- Loading skeleton -->
<div class="jeri-loyalty-page__skeleton">
<div class="jeri-loyalty-page__skeleton-hero"></div>
</div>
</div>
Visual template — Best for brands that want a polished, marketing-style loyalty page with hero section, how-it-works steps, earning rows with SVG icons, tier cards, and full loading skeleton.
Minimal template — Best for stores that want a clean, data-first approach with compact list layout, inline CSS, and no external stylesheet dependency beyond the JS engine.
Both templates use the same JS engine and data binding system. The only difference is the HTML structure and visual design. You can start from either one and customize freely.
Earning row icons are SVG files named by earning type: purchase.svg, social.svg, referral.svg, profile.svg, wallet.svg, scan.svg, verify.svg, link.svg.
In CDN mode, the icon base URL is hardcoded to https://cdn.jericommerce.com/shopify/. The icon URL is computed as data-icon-base + type + .svg. If data-icon-base is missing or an icon doesn't exist, the <img> element is hidden automatically.