Product videos are great for engagement, but terrible for conversion if shoppers can’t buy what they just saw.
Let’s say a customer watches your styling video, loves the sunglasses at the 12-second mark, and wants to buy them. But now they have to pause the video, search your catalog, maybe filter by “accessories,” and hope they find the exact pair. That’s too much friction, and it kills purchase intent.
As someone who’s helped dozens of Shopify stores solve this problem, I’ll show you how to turn any product video into an interactive shopping experience using Cloudinary’s shoppable video player and Shopify’s product API.
You’ll learn how to:
- Sync your real-time inventory with video overlays.
- Add clickable product hotspots.
- Automatically link viewers to the right product page based on what they clicked.
By the end of this tutorial, you’ll have a fully working demo that connects your videos to your Shopify store, so your customers can watch, click, and buy without ever leaving the experience.

Combining Cloudinary’s advanced video capabilities with Shopify’s robust e-commerce infrastructure creates a seamless solution to this conversion challenge. Here’s why this pairing works so effectively:
- Real-time inventory synchronization. Your videos automatically reflect product availability, preventing customer disappointment from out-of-stock items.
- Dynamic product information updates. Pricing, descriptions, and product details refresh automatically without manual updates.
- Streamlined purchase journey. Customers can click directly on products within the video and be instantly directed to the relevant product pages, eliminating the need to search through catalogs and maintaining their purchase intent.
- Scalable media management. Cloudinary automatically handles video optimization, delivery, and transformations.
- Developer-friendly integration. Both platforms offer comprehensive APIs that work together smoothly.
Now, let’s walk through the process of building this integration step by step.
You should have:
- Basic knowledge of HTML, CSS, and JavaScript.
- A local development server (Python, Node.js, or VS Code Live Server).
- A free Cloudinary account for video optimization.
- Shopify store or Partner account.
You need environment credentials from your Cloudinary dashboard to use Cloudinary video transformations and create optimized video delivery.
Log in to your Cloudinary dashboard to retrieve environment credentials such as the cloud name, API key, and API secret.

Now let’s walk through the process of setting up your shoppable video infrastructure from scratch.
Start by setting up your development environment with the proper file structure:
# Create project directory
mkdir cloudinary-shoppable-tutorial
cd cloudinary-shoppable-tutorial
Code language: PHP (php)
Next, create the basic file structure:
# Create the basic file structure
touch index.html script.js
Code language: CSS (css)
Then start or set up your local server with the following command:
# Using Live Server extension in VS Code (recommended)
# Just right-click index.html and select "Open with Live Server"
# Option 2: Using Node.js http-server
npm install -g http-server
http-server
Code language: PHP (php)
This structure keeps your code organized and makes it easy to test changes as you build.
Before you can create shoppable videos, you need to gather specific information from your Shopify store. Here’s exactly what you’ll need for each product you want to feature:
Required Shopify data points:
- Product ID. Navigate to your Shopify admin > Products > select a product. The Product ID appears in the URL (e.g., /products/7234567890123).
- Product Handle. This is the URL-friendly version of your product name (e.g., “uv400-sunglasses-black”).
- Variant IDs. For products with different colors or sizes, you’ll need each variant’s ID.
- Store Domain. Your full store URL (e.g., “yourstore.myshopify.com”).
How to find your product information:
// Navigate to: yourstore.myshopify.com/admin/products/PRODUCT_ID.json
// This will show you all product data including:
{
"product": {
"id": 7234567890123,
"title": "UV400 Sunglasses",
"handle": "uv400-sunglasses-black",
"variants": [
{
"id": 41234567890123,
"title": "Black",
"price": "89.99",
"inventory_quantity": 12
}
]
}
}
Code language: JSON / JSON with Comments (json)
You can also use Shopify’s Admin API to fetch this data programmatically if you have API access.
To get your video public IDs and configure metadata for an individual asset, navigate to your Cloudinary dashboard, upload or select any of your existing videos, and then click to copy the video ID, as shown below.

Select any of your existing videos for collection assets, then click to copy the video ID inside the collection, as shown below.

Now you’ll create your first interactive video player. This step transforms a standard video into a dynamic shopping experience where every product becomes clickable. This implementation uses vanilla HTML, CSS, and JavaScript.
Add the following code snippet inside the index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shoppable Video Tutorial</title>
<!-- Cloudinary Video Player CSS - Required for styling -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/cloudinary-video-player@2.3.5/dist/cld-video-player.min.css"
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
</head>
<body>
<div class="container">
<h1>🛍️ Shoppable Video Experience</h1>
<div class="video-section">
<!-- Status indicator to show what's happening -->
<div class="status" id="status">Loading video player...</div>
<!-- Video container where Cloudinary player will be initialized -->
<div class="video-container" id="videoContainer">
<video
id="shoppable-player"
controls
muted
class="cld-video-player"
></video>
</div>
<!-- Product information will be displayed here -->
<div class="product-info" id="productInfo" style="display: none;">
<h3>Featured Products:</h3>
<div class="product-list" id="productList"></div>
</div>
</div>
</div>
<!-- Cloudinary Video Player JavaScript - Required for functionality -->
<script
src="https://cdn.jsdelivr.net/npm/cloudinary-video-player@2.3.5/dist/cld-video-player.min.js"
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
</body>
</html>
Code language: HTML, XML (xml)
Now let’s add some basic styling to make it look professional. While styling is optional (you can use your own styles or skip this step entirely), we’ve prepared some professional CSS styles for users who want to follow along with the complete tutorial. You can find the complete CSS styles in this GitHub gist, simply copy the styles and add them in a <style> section within your <head> tag.
Create a shopify-config.js file to centralize your Shopify and Cloudinary settings. This keeps your sensitive info organized, makes updates easier, and ensures you’re only changing things in one place when your product details or domain change.
Let’s build this file piece by piece:
// shopify-config.js
// Your Cloudinary configuration
const CLOUDINARY_CONFIG = {
cloudName: "your-cloud-name-here", // Replace with your actual Cloudinary cloud name
};
// Your Shopify store configuration
const SHOPIFY_CONFIG = {
storeDomain: "yourstore.myshopify.com", // Replace with your actual store domain
};
Code language: PHP (php)
Next, create a helper function for product URLs. This function automatically generates the correct Shopify product URLs, including variant-specific links. It prevents typos and makes it easy to update URL structures later.
// shopify-config.js
//...
// Function to generate product URLs dynamically
function generateProductUrl(handle, variant = null) {
let url = `https://${SHOPIFY_CONFIG.storeDomain}/products/${handle}`;
if (variant) {
url += `?variant=${variant}`;
}
return url;
}
Code language: JavaScript (javascript)
Now, you can fetch real Shopify data by creating a function that connects to Shopify’s public API to get real-time product information, such as pricing and availability. This ensures your videos always show current information without manual updates.
//...
// Function to fetch real-time product data from Shopify
async function fetchShopifyProductData(productHandle) {
try {
// Using Shopify's public product API (no authentication required)
const response = await fetch(
`https://${SHOPIFY_CONFIG.storeDomain}/products/${productHandle}.js`
);
if (!response.ok) {
throw new Error(`Product not found: ${productHandle}`);
}
const productData = await response.json();
return {
id: productData.id,
title: productData.title,
handle: productData.handle,
price: (productData.price / 100).toFixed(2), // Convert from cents to dollars
comparePrice: productData.compare_at_price
? (productData.compare_at_price / 100).toFixed(2)
: null,
available: productData.available,
variants: productData.variants.map((variant) => ({
id: variant.id,
title: variant.title,
price: (variant.price / 100).toFixed(2),
available: variant.available,
inventory: variant.inventory_quantity,
})),
url: `/products/${productData.handle}`,
};
} catch (error) {
console.error("Error fetching product data:", error);
return null;
}
}
Code language: JavaScript (javascript)
Continue building your configuration by mapping your videos to products. This object connects your Cloudinary videos to specific Shopify products, defining when products appear in the video, where clickable hotspots are placed, and what happens when users click them.
//..
// Map your video IDs to Shopify product information
const VIDEO_PRODUCTS = {
"your-video-public-id": { // Replace with your actual Cloudinary video public ID
shopify_handle: "your-product-handle", // Replace with your Shopify product handle
products: [
{
productId: 1,
productName: "Your Product Name", // Will be updated from Shopify
startTime: 0, // When this product appears in the video (seconds)
endTime: 15, // When this product disappears (seconds)
publicId: "your-product-image", // Cloudinary image for overlay
hotspots: [
{
time: "00:05", // When hotspot appears (MM:SS format)
x: "40%", // Horizontal position on video
y: "30%", // Vertical position on video
tooltipPosition: "right", // Where tooltip appears
},
],
onClick: {
action: "goto", // What happens when clicked
pause: true, // Pause video when clicked
args: {
url: "", // Will be populated dynamically from Shopify
},
},
},
],
},
};
Code language: PHP (php)
Before connecting to your real Shopify data, let’s create a working demo using Cloudinary’s sample videos. Starting with a working demo lets you see immediate results and understand how each piece works before safely updating to your real store data. This prevents frustration and makes debugging much easier.
Replace your configuration with this working demo setup inside the script.js file.
Now, let’s build the core JavaScript that connects everything. You’ll create this step by step, with each function handling a specific aspect of your shoppable video experience. This script orchestrates everything: it initializes the video player, displays product information, handles user interactions, and updates the interface with real-time data.
Update your script.js file and start with the essential variables:
// script.js
// Variables to track current state
let currentPlayer = null;
let currentVideoId = "docs/shoppable_demo"; // Default video to load
Code language: JavaScript (javascript)
Next, create a status update function that provides visual feedback to users about what’s happening (i.e., loading, success, errors).
// script.js
//...
// Function to update status display with different types
function updateStatus(message, type = "info") {
const statusElement = document.getElementById("status");
if (!statusElement) return;
statusElement.textContent = message;
// Remove existing status classes and add styling based on type
statusElement.className = "status";
if (type === "error") {
statusElement.style.background = "#ffebee";
statusElement.style.color = "#c62828";
} else if (type === "success") {
statusElement.style.background = "#e8f5e8";
statusElement.style.color = "#2e7d32";
} else {
statusElement.style.background = "#e3f2fd";
statusElement.style.color = "#1976d2";
}
}
Code language: JavaScript (javascript)
Then, you will build a product display function that shows users what products are featured in the current video, including pricing and availability. This will help users understand what they can shop for before starting the video.
// script.js
//...
// Function to display product information from video data
function displayProducts(videoProducts) {
const productInfo = document.getElementById("productInfo");
const productList = document.getElementById("productList");
// Check if elements exist
if (!productInfo || !productList) {
console.warn("Product display elements not found");
return;
}
// Hide product info if no products
if (!videoProducts || videoProducts.length === 0) {
productInfo.style.display = "none";
return;
}
// Clear existing products and show container
productList.innerHTML = "";
productInfo.style.display = "block";
// Create display for each product
videoProducts.forEach((product) => {
const productData = MOCK_PRODUCTS[product.productId];
if (!productData) return;
const productItem = document.createElement("div");
productItem.style.cssText = `
background: white;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
`;
productItem.innerHTML = `
<div>
<h4 style="margin-bottom: 5px; color: #333;">${productData.title}</h4>
<div style="font-weight: bold; color: #2e7d32;">
${
productData.comparePrice
? `<span style="text-decoration: line-through; color: #999; margin-right: 10px;">$${productData.comparePrice}</span>`
: ""
}
$${productData.price}
</div>
</div>
<div style="padding: 4px 8px; border-radius: 4px; font-size: 0.9rem; ${
productData.available
? "background: #e8f5e8; color: #2e7d32;"
: "background: #ffebee; color: #c62828;"
}">
${
productData.available
? `In stock${
productData.stockLevel
? ` (${productData.stockLevel} left)`
: ""
}`
: "Out of stock"
}
</div>
`;
productList.appendChild(productItem);
});
}
Code language: JavaScript (javascript)
You must create a Cloudinary Video Player, load your video content, and configure all the interactive elements. To do that, update the script.js file with the following code snippet:
//...
// Function to initialize the video player
function initializeVideo() {
try {
updateStatus("Initializing video player...");
// Check if video element exists
const videoElement = document.getElementById("shoppable-player");
if (!videoElement) {
updateStatus("Error: Video element not found", "error");
return;
}
// Check if Cloudinary is loaded
if (typeof cloudinary === "undefined") {
updateStatus("Error: Cloudinary Player SDK not loaded", "error");
return;
}
// Create new Cloudinary player instance
currentPlayer = cloudinary.videoPlayer("shoppable-player", {
cloudName: DEMO_CONFIG.cloudName,
controls: true,
muted: true,
fluid: true, // Responsive sizing
});
// Get video configuration
const videoData = VIDEO_PRODUCTS[currentVideoId];
if (!videoData) {
updateStatus(
`Error: Video configuration for ${currentVideoId} not found.`,
"error"
);
return;
}
// Display product information
displayProducts(videoData.products);
// Configure and load the shoppable video
loadShoppableVideo(videoData);
} catch (error) {
updateStatus(`Error: ${error.message}`, "error");
console.error("Initialization error:", error);
}
}
Code language: JavaScript (javascript)
Next, build the Shoppable Video loading function to set up the interactive elements, dynamic overlays and hotspots, and click behaviors that make your video shoppable.
//...
// Function to configure and load shoppable video
function loadShoppableVideo(videoData) {
// Configure shoppable video if products exist
if (videoData.products && videoData.products.length > 0) {
const shoppableConfig = {
shoppable: {
startState: "openOnPlay", // Show products when video starts
bannerMsg: "Shop this demo", // Call-to-action message
autoClose: 5, // Auto-hide product bar after 5 seconds
products: videoData.products,
},
};
// Load video with shoppable configuration
currentPlayer.source(currentVideoId, {
...shoppableConfig,
transformation: [{ quality: "auto" }],
});
} else {
// Load regular video without shoppable features
currentPlayer.source(currentVideoId, {
transformation: [{ quality: "auto" }],
});
}
// Set up event handlers after loading
setupVideoEventHandlers();
}
Code language: JavaScript (javascript)
Now, add event handlers for user interactions. These event handlers respond to user actions like clicking on products, hovering over hotspots, and video loading events. They provide feedback and track meaningful interactions for analytics.
//...
// Function to set up video event handlers
function setupVideoEventHandlers() {
if (!currentPlayer) return;
// Handle product clicks
currentPlayer.on("productClick", function (event) {
updateStatus(`Clicked on: ${event.productName} at ${event.time}s`, 'success');
// Track the click for analytics
console.log("Product clicked:", {
productId: event.productId,
productName: event.productName,
videoId: currentVideoId,
timestamp: event.time
});
// Optional: Add custom analytics tracking
trackProductClick(event);
});
// Handle product hover events
currentPlayer.on("productHover", function (event) {
console.log("Product hovered:", event);
});
// Handle successful video load
currentPlayer.on("ready", function () {
updateStatus("Demo loaded! Click play to see shoppable features.", 'success');
});
// Handle video errors
currentPlayer.on("error", function (error) {
if (error && (error.code === 4 || error.message?.includes("404"))) {
updateStatus(`Video "${currentVideoId}" not found. Please check the video exists.`, 'error');
} else {
updateStatus("Error loading video. Please try again.", 'error');
console.error("Video player error:", error);
}
});
}
Almost done! Now you need to initialize the video as soon as the page loads by adding the event listener to the script.js file.
//...
// Initialize video player when page loads
document.addEventListener("DOMContentLoaded", function () {
if (typeof cloudinary !== "undefined") {
initializeVideo();
} else {
updateStatus("Error: Cloudinary Player SDK failed to load.", 'error');
}
});
Code language: JavaScript (javascript)
Now, update your HTML to include the necessary scripts. Add these script tags just before the closing </body> tag in your index.html:
<!-- Include the shoppable extension -->
<script src="https://unpkg.com/cloudinary-video-player/shoppable"></script>
<script src="script.js"></script>
Code language: HTML, XML (xml)
Your complete HTML should look like this at the bottom:
<!-- Cloudinary Video Player JavaScript - Required for functionality -->
<script
src="https://cdn.jsdelivr.net/npm/cloudinary-video-player@2.3.5/dist/cld-video-player.min.js"
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<!-- Include the shoppable extension -->
<script src="https://unpkg.com/cloudinary-video-player/shoppable"></script>
<script src="script.js"></script>
</body>
</html>
Code language: HTML, XML (xml)
At this point, you should have a fully functional shoppable video demo! Open your index.html file in a browser (preferably through a local server) and you should see:
- A video player with Cloudinary’s demo video.
- Product information displayed below the video.
- Clickable hotspots when you play the video.
- Status updates showing what’s happening.

Now that your demo works perfectly, you can safely replace the mock data with your actual Shopify store information without breaking the functionality.
Replace the demo configuration in your script.js:
// Replace the DEMO_CONFIG with your actual Cloudinary settings
const DEMO_CONFIG = {
cloudName: "your-actual-cloudinary-name", // Get this from your Cloudinary dashboard
};
// Add your actual Shopify store domain
const SHOPIFY_CONFIG = {
storeDomain: "your-store.myshopify.com", // Your actual Shopify store URL
};
Code language: PHP (php)
Replace the demo video configuration with your actual videos and Shopify product handles. This connects your real content to your real products.
Replace the VIDEO_PRODUCTS configuration with your actual data:
//...
// Update this with your actual video and product information
const VIDEO_PRODUCTS = {
"your-video-public-id": { // Replace with your actual Cloudinary video public ID
shopify_handle: "your-product-handle", // Replace with your Shopify product handle
products: [
{
productId: 1,
productName: "Your Product Name", // Will be updated from Shopify
startTime: 0,
endTime: 15,
publicId: "your-product-image", // Your product image in Cloudinary
hotspots: [
{
time: "00:05",
x: "40%",
y: "30%",
tooltipPosition: "right",
},
],
onClick: {
action: "goto",
pause: true,
args: {
url: "", // Will be populated from Shopify
},
},
},
],
},
};
Code language: PHP (php)
Now let’s integrate the real Shopify data fetching into your working demo. Add this enhanced initialization function. This modification fetches real Shopify data and updates your video configuration with current prices, availability, and product URLs.
//script.js
//...
// Enhanced function to work with real Shopify data
async function initializeVideoWithShopify() {
try {
updateStatus("Initializing video player...");
const videoElement = document.getElementById("shoppable-player");
if (!videoElement) {
updateStatus("Error: Video element not found", 'error');
return;
}
currentPlayer = cloudinary.videoPlayer("shoppable-player", {
cloudName: DEMO_CONFIG.cloudName, // Use your actual cloud name
controls: true,
muted: true,
fluid: true,
});
const videoData = VIDEO_PRODUCTS[currentVideoId];
if (!videoData) {
updateStatus(`Error: Video configuration not found.`, 'error');
return;
}
// Fetch real Shopify product data if handle is provided
if (videoData.shopify_handle && typeof fetchShopifyProductData !== 'undefined') {
updateStatus("Fetching current product data from Shopify...");
try {
const shopifyData = await fetchShopifyProductData(videoData.shopify_handle);
if (shopifyData) {
// Update video products with real Shopify data
updateProductsWithShopifyData(videoData, shopifyData);
}
} catch (error) {
console.warn("Could not fetch Shopify data, using static data:", error);
updateStatus("Using cached product data", 'info');
}
}
// Display product information and load video
displayProducts(videoData.products);
loadShoppableVideo(videoData);
} catch (error) {
updateStatus(`Error: ${error.message}`, 'error');
console.error("Initialization error:", error);
}
}
Code language: JavaScript (javascript)
Next, update products with real Shopify data:
//...
// Function to update products with real Shopify data
function updateProductsWithShopifyData(videoData, shopifyData) {
videoData.products.forEach((product, index) => {
const variant = shopifyData.variants && shopifyData.variants[index]
? shopifyData.variants[index]
: shopifyData;
product.productName = variant.title ? `${shopifyData.title} - ${variant.title}` : shopifyData.title;
product.price = variant.price || shopifyData.price;
product.comparePrice = variant.comparePrice || shopifyData.comparePrice;
product.available = variant.available !== undefined ? variant.available : shopifyData.available;
product.stockLevel = variant.inventory;
// Generate the correct product URL using the helper function
const productUrl = generateProductUrl(shopifyData.handle, variant.id);
product.onClick.args.url = productUrl;
if (product.hotspots) {
product.hotspots.forEach(hotspot => {
hotspot.clickUrl = productUrl;
});
}
});
}
Code language: JavaScript (javascript)
To connect your demo to your actual store, update these four configuration values, replace your:
- Cloudinary cloud name. Update
DEMO_CONFIG.cloudName
with your actual Cloudinary cloud name. - Shopify domain. Update
SHOPIFY_CONFIG.storeDomain
with your actual store domain. - Video public ID. Update the key in
VIDEO_PRODUCTS
with your actual video public ID. - Product handle. Update
shopify_handle
with your actual Shopify product handle.
You’ve created a shoppable video experience connecting Cloudinary videos with your Shopify products! Your customers can now click directly on products in your videos and be redirected to your store for immediate purchase.
To complement your API integration and streamline your video production workflows, consider using Cloudinary Studio or MediaFlows for automated video processing and management tasks.
Sign up on Cloudinary to connect product videos with real-time Shopify inventory and add interactive shopping experiences.