DOPP API: Theme Interop Tutorial
Introduction:
This tutorial demonstrates how to dynamically update prices and display discounts in custom product grids and containers using the DOPP API’s theme interop features.
We highly recommend downloading the "Public API TypeScript Definition" mentioned in the "DOPP API Reference" article. You can feed this into ChatGPT/Copilot/Cursor etc. for assistance when writing your integration code.
Use Case/Context:
Let's say you have a wishlist page, where customers can view a list of items they have personally curated. Our app does not support wishlists out of the box, as there is too much potential variation.
The DOPP API’s themeInterop API makes it possible for you to easily display discount strikethrough prices in your wishlist.
Assumptions:
Before you start, make sure:
- You understand basic HTML, JavaScript, and Shopify’s Liquid syntax.
- The "Discounts Embed" app embed is enabled.
- You can modify Shopify theme files.
- Your products are displayed within a product grid element, with the ID wishlist-root.
- You have a wishlist_items Liquid variable, of type product_list.
Step 1: Converting Your Products to DOPPPublicApiProduct
First, prepare your products in the DOPPPublicApiProduct format using this Liquid snippet:
window.wishlistItems = [
{% for dopp_product in wishlist_items %}
{%- liquid
assign current_variant = dopp_product.selected_or_first_available_variant
assign target = dopp_product.selected_or_first_available_variant
assign compare_at_price = target.compare_at_price
assign price = target.price
-%}
{
// Required fields
productId: {{ dopp_product.id | json }},
regularPriceInCents: {{ price | json }},
variantId: {{ current_variant.id | json }},
// Optional fields, but if your discount depends on any of these, they
// must be included
collectionIds: {{ dopp_product.collections | map: "id" | json }},
compareAtPriceInCents: {{ compare_at_price | json }},
handle: {{ dopp_product.handle | json }},
tags: {{ dopp_product.tags | json }},
url: {{ dopp_product.url | json }},
vendor: {{ dopp_product.vendor | json }},
variants: [
{% for variant in dopp_product.variants %}
{
id: {{ variant.id | json }},
title: {{ variant.title | json }},
priceInCents: {{ variant.price | json }},
compareAtPriceInCents: {{ variant.compare_at_price | json }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}{% unless forloop.last %},{% endunless %}
{% endfor %}
];
Step 2: Waiting for the API to Initialize
You may have multiple apps and scripts on your site. This makes it a challenge to predict exactly when the "Discounts Embed" will load, and therefore, it's hard to know when window.RegiosDOPP.api.v0 will be available. Use the following snippet to reliably wait to run your integration code until the API is initialized:
javascript
const updateWishlistPrices = (api) => {
console.log("DOPP API initialized", { api });
// TODO: Add your code here...
};
if (window.RegiosDOPP?.api?.v0) {
updateWishlistPrices(window.RegiosDOPP.api.v0);
} else {
window.addEventListener("regios-dopp:api-initialized", (e) => {
updateWishlistPrices(e.detail.api);
});
}
Step 3: Using Theme Interop API to Detect Product Grid Cells
Use themeInterop.findProductGridCells to detect product elements within #wishlist-root and correlate them with DOPPPublicApiProduct data. This function is ideal for product grids like wishlists or collections.
const gridCells = await api.themeInterop.findProductGridCells(document.querySelector('#wishlist-root'), wishlistItems, 'COLLECTION_PAGE');
If this doesn't work, you might be trying to detect elements in a product grid that our app doesn't understand the structure of. If so, use findProductGridCellsByUrl instead, which will find cells by searching for links to a given product's PDP.
// Assuming that each product grid cell in your grid has the class name `wishlist-card`
const gridCells = await api.themeInterop.findProductGridCellsByUrl(document.querySelector('#wishlist-root'), wishlistItems, [".wishlist-card"]);
Step 4: Updating On-Page Prices Using Theme Interop API
Apply the themeInterop.updateOnPagePrices method to dynamically update displayed prices. While this method is used here following findProductGridCells, it can independently update prices for any product displayed within a given container, such as a single featured product.
for (const cell of gridCells) {
await api.themeInterop.updateOnPagePrices(cell.element, 'COLLECTION_PAGE', cell.product);
}
Putting It All Together
Here’s how to integrate these steps into your Shopify theme to ensure dynamic price updates:
window.wishlistItems = [
{% for dopp_product in wishlist_items %}
{%- liquid
assign current_variant = dopp_product.selected_or_first_available_variant
assign target = dopp_product.selected_or_first_available_variant
assign compare_at_price = target.compare_at_price
assign price = target.price
-%}
{
// Required fields
productId: {{ dopp_product.id | json }},
regularPriceInCents: {{ price | json }},
variantId: {{ current_variant.id | json }},
// Optional fields, but if your discount depends on any of these, they
// must be included
collectionIds: {{ dopp_product.collections | map: "id" | json }},
compareAtPriceInCents: {{ compare_at_price | json }},
handle: {{ dopp_product.handle | json }},
tags: {{ dopp_product.tags | json }},
url: {{ dopp_product.url | json }},
vendor: {{ dopp_product.vendor | json }},
variants: [
{% for variant in dopp_product.variants %}
{
id: {{ variant.id | json }},
title: {{ variant.title | json }},
priceInCents: {{ variant.price | json }},
compareAtPriceInCents: {{ variant.compare_at_price | json }},
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}{% unless forloop.last %},{% endunless %}
{% endfor %}
];
/**
* Initializes the process to update prices on the wishlist page.
* This function handles both immediate initialization and setting up
* event listeners for when the API becomes available.
*/
function initWishlistPrices() {
/**
* Updates prices on the wishlist page using the DOPP API.
* @param {object} api - The DOPP API interface provided by the window object.
*/
const updateWishlistPrices = async (api) => {
const wishlistRoot = document.querySelector('#wishlist-root');
const gridCells = await api.themeInterop.findProductGridCells(wishlistRoot, window.wishlistItems, 'COLLECTION_PAGE');
for (const cell of gridCells) {
await api.themeInterop.updateOnPagePrices(cell.element, 'COLLECTION_PAGE', cell.product);
}
}
if (window.RegiosDOPP?.api?.v0) {
updateWishlistPrices(window.RegiosDOPP.api.v0);
} else {
window.addEventListener("regios-dopp:api-initialized", (e) => {
updateWishlistPrices(e.detail.api);
});
}
}
// Checks if the document has been fully parsed and initializes the wishlist prices,
// otherwise waits for the DOM to be fully loaded.
if (document.readyState !== "loading") {
initWishlistPrices();
} else {
document.addEventListener("DOMContentLoaded", initWishlistPrices);
}
Your Feedback Matters
As a solo founder, your experience is crucial to me. Could you spare a moment to share your thoughts? Please leave a review on the Shopify App Store. Thank you for helping me improve!
Updated on: 03/01/2025
Thank you!