Use Your Theme’s Product Card with Releva #
What This Does #
Releva picks which products to recommend. Your Shopify theme controls how they look.
You write standard Shopify Liquid — tags, prices, metafields, third-party app integrations all work out of the box because Shopify renders the HTML server-side.
Setup #
1. Create a Section File #
- Go to Shopify Admin > Online Store > Themes.
- Click “…” > Edit code.
- In the Sections folder, click “Add a new file”.
- Name it (e.g.)
releva-recs.liquid. - Paste one of the examples from the end of this guide.
- Click Save.
2. Configure the Releva Block #
- Go to Online Store > Themes > Customize.
- Add or find the Releva Product Block.
- Enter the Product Block Token from your Releva dashboard.
- Check “Use my theme’s product card”.
- In Product Card Section ID, enter the section filename without
.liquid(e.g.releva-recs). - Save.
That’s it. Releva handles everything else — writing the product list, fetching the section, and tracking clicks.
What Every Section Must Contain #
Three things to remember:
- First line — replace
YOUR-SECTION-ID-HEREwith your section filename. Forreleva-recs.liquid, use_rlv_h_releva-recs. {% if product.title != blank %}guard — skips products that are unpublished or hidden from the current market.{% schema %}tag — required by Shopify on every section file.
{% assign rlv_handles = cart.attributes['_rlv_h_YOUR-SECTION-ID-HERE'] | default: '' | split: ',' %}
{% for handle in rlv_handles %}
{% assign product = all_products[handle] %}
{% if product.title != blank %}
<!-- Your product card markup goes here -->
<a href="{{ product.url }}">{{ product.title }} — {{ product.price | money }}</a>
{% endif %}
{% endfor %}
{% schema %}
{ "name": "Releva Recommendations", "settings": [] }
{% endschema %}
Inside the loop, product is the full Shopify product object — render it however you want.
Available Product Fields #
| Liquid | Description |
|---|---|
{{ product.title }} | Product title |
{{ product.price \\| money }} | Current price, formatted |
{{ product.compare_at_price \\| money }} | Compare-at price |
{{ product.featured_image \\| image_url: width: 300 }} | Product image |
{{ product.url }} | Product URL |
{{ product.tags }} | Product tags (for badges, promos) |
{{ product.vendor }} | Product vendor |
{{ product.type }} | Product type |
{{ product.variants }} | All variants |
{{ product.metafields }} | Custom metafields |
{{ product.available }} | Availability |
{{ product.description }} | Product description |
Multiple Blocks on One Page #
Each block needs its own section file. Two blocks pointing to the same file will collide.
If two blocks should look identical, create two section files and have both render a shared snippet:
{% assign rlv_handles = cart.attributes['_rlv_h_YOUR-SECTION-ID-HERE'] | default: '' | split: ',' %}
{% for handle in rlv_handles %}
{% assign product = all_products[handle] %}
{% if product.title != blank %}
{% render 'my-shared-card', product: product %}
{% endif %}
{% endfor %}
{% schema %}{ "name": "Releva Trending", "settings": [] }{% endschema %}
Good to Know #
- 20-product cap — Shopify limits
all_products[handle]to 20 lookups per page. Releva respects this automatically. - Click tracking is automatic — any
<a href="{{ product.url }}">link in your section gets tracking applied. No extra markup needed. - Theme editor preview — the block will appear empty in the theme editor. Test on the live storefront.
- Markets / multi-language — prices and translations match the market the visitor is browsing.
Troubleshooting #
| Symptom | Fix |
|---|---|
| Block is empty on the storefront | Check that the cart attribute key in your section matches the filename. For releva-recs.liquid, use _rlv_h_releva-recs. |
| Empty wrapper, no cards | The {% assign %} line is missing or the attribute key is wrong. |
| Some cards missing | Those products are unpublished or hidden from the current market — this is expected. |
| No checkbox or section ID field in settings | The Releva app needs to be updated. |
| Cards show but clicks aren’t tracked | Make sure your card uses <a href="{{ product.url }}">. |
Section Examples #
Carousel / slider #
A Swiper.js carousel with arrow buttons and responsive breakpoints.
{% assign rlv_handles = cart.attributes['_rlv_h_YOUR-SECTION-ID-HERE'] | default: '' | split: ',' %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<style>
.releva-slider-wrap {
position: relative;
max-width: 1240px;
margin: 0 auto;
padding: 20px 50px;
}
.releva-slider-wrap .swiper { overflow: hidden; }
.releva-slider-wrap .swiper-slide { height: auto; display: flex; }
.releva-slider-wrap .releva-card { width: 100%; text-align: center; }
.releva-slider-wrap .releva-card a { display: block; text-decoration: none; color: inherit; }
.releva-slider-wrap .releva-card .img-wrap { position: relative; overflow: hidden; }
.releva-slider-wrap .releva-card img { width: 100%; height: auto; display: block; }
.releva-slider-wrap .releva-card h3 { font-size: 14px; margin: 10px 0 4px; font-weight: 600; }
.releva-slider-wrap .releva-card .price { font-weight: bold; }
.releva-slider-wrap .releva-card .price s { color: #999; font-weight: normal; margin-right: 6px; }
.releva-slider-wrap .sale-badge {
position: absolute;
top: 8px;
left: 8px;
background: #e53935;
color: #fff;
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
border-radius: 4px;
z-index: 2;
letter-spacing: 0.5px;
}
.releva-slider-wrap .swiper-button-prev,
.releva-slider-wrap .swiper-button-next {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
cursor: pointer;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
}
.releva-slider-wrap .swiper-button-prev { left: 5px; }
.releva-slider-wrap .swiper-button-next { right: 5px; }
.releva-slider-wrap .swiper-button-prev::after,
.releva-slider-wrap .swiper-button-next::after {
content: '';
display: block;
width: 10px;
height: 10px;
border-top: 2px solid #222;
border-right: 2px solid #222;
}
.releva-slider-wrap .swiper-button-prev::after {
transform: rotate(-135deg);
margin-left: 4px;
}
.releva-slider-wrap .swiper-button-next::after {
transform: rotate(45deg);
margin-right: 4px;
}
.releva-slider-wrap .swiper-button-disabled {
opacity: 0.35;
cursor: default;
}
</style>
<div class="releva-slider-wrap">
<div class="swiper releva-swiper">
<div class="swiper-wrapper">
{% for handle in rlv_handles %}
{% assign product = all_products[handle] %}
{% if product.title != blank %}
<div class="swiper-slide">
<div class="releva-card">
<a href="{{ product.url }}">
<div class="img-wrap">
{% if product.compare_at_price > product.price %}
{% assign discount = product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price %}
<span class="sale-badge">-{{ discount }}%</span>
{% endif %}
{% if product.featured_image %}
{% assign img = product.featured_image %}
<img
src="{{ img | image_url: width: 400 }}"
alt="{{ product.title | escape }}"
loading="lazy"
width="400"
height="{{ 400 | divided_by: img.aspect_ratio | round }}"
>
{% endif %}
</div>
<h3>{{ product.title }}</h3>
<div class="price">
{% if product.compare_at_price > product.price %}
<s>{{ product.compare_at_price | money }}</s>
{% endif %}
{{ product.price | money }}
</div>
</a>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js" defer></script>
<script>
(function () {
function initRelevaSwipers() {
if (typeof Swiper === 'undefined') { setTimeout(initRelevaSwipers, 100); return; }
document.querySelectorAll('.releva-slider-wrap').forEach(function (wrap) {
var el = wrap.querySelector('.releva-swiper');
if (!el || el.dataset.rlvSwiperInit) return;
el.dataset.rlvSwiperInit = '1';
new Swiper(el, {
slidesPerView: 2,
spaceBetween: 15,
navigation: {
prevEl: wrap.querySelector('.swiper-button-prev'),
nextEl: wrap.querySelector('.swiper-button-next')
},
breakpoints: {
640: { slidesPerView: 3 },
1024: { slidesPerView: 4 },
1200: { slidesPerView: 5 }
}
});
});
}
window.addEventListener('releva:recommendations', initRelevaSwipers);
if (document.readyState !== 'loading') initRelevaSwipers();
else document.addEventListener('DOMContentLoaded', initRelevaSwipers);
})();
</script>
{% schema %}
{ "name": "Releva Recommendations", "settings": [] }
{% endschema %}
To avoid theme-check warnings, you can host Swiper locally: download swiper-bundle.min.css and swiper-bundle.min.js from the CDN, upload to your theme’s Assets folder, then replace the CDN URLs with {{ 'swiper-bundle.min.css' | asset_url | stylesheet_tag }} and <script src="{{ 'swiper-bundle.min.js' | asset_url }}" defer></script>.
Reusing your existing theme card #
If your theme already has a product card snippet (e.g. snippets/product-card.liquid), just wrap it in the loop:
{% assign rlv_handles = cart.attributes['_rlv_h_YOUR-SECTION-ID-HERE'] | default: '' | split: ',' %}
<div class="my-grid">
{% for handle in rlv_handles %}
{% assign product = all_products[handle] %}
{% if product.title != blank %}
{% render 'product-card', product: product %}
{% endif %}
{% endfor %}
</div>
{% schema %}
{ "name": "Releva Recommendations", "settings": [] }
{% endschema %}