Embed Videos On Shopify
Gumlet Video Widget — Shopify Section
A drop-in Liquid section that embeds one or more Gumlet videos using
Gumlet's hosted embed player. No React, no build step, no external dependencies. Works in any
Shopify Online Store 2.0 theme (sections/ + JSON templates, or {% section %} in a .liquid
template).
- Merchant pastes a Gumlet Video/Asset ID per video block — no manual URL building.
- Multiple videos are added as theme editor blocks ("Add block" → "Video"), each with its own Asset ID field.
- Layout is a centered, growing row: one video sits centered at a fixed tile width, and each added block grows the row outward from the center. Once tiles no longer fit the viewport, the row becomes horizontally scrollable — no arrows/dots needed.
- All playback settings (autoplay, loop, muted, native controls, aspect ratio, accent color, cover image) are section-level and apply to every video uniformly.
-
Defaults to Reel mode (9:16 portrait) — change it in
schema.settingsif your theme wants landscape by default.
1. How Gumlet embedding works
Gumlet serves videos through a hosted iframe player:
https://play.gumlet.io/embed/{asset_id}
{asset_id} is the Video/Asset ID shown in the Gumlet dashboard under Video Library. You
customize playback with query string parameters appended to that URL:
| Param | Type | Effect |
|---|---|---|
autoplay |
boolean | Autoplay on load. Browsers force this to also be muted. |
loop |
boolean | Loop playback. |
muted |
boolean | Start muted. |
disable_player_controls |
boolean | Hides Gumlet's native player chrome (seek bar, play button, etc.). |
preload |
boolean | Preloads the video so there's no play-button delay. |
player_color |
hex (URL-encoded #) |
Accent color for the native player UI. |
Example:
https://play.gumlet.io/embed/abcd1234?autoplay=true&loop=true&muted=true&disable_player_controls=true&preload=true&player_color=%23FF6600
Recommended iframe attributes:
<iframe
src="https://play.gumlet.io/embed/abcd1234?autoplay=true&loop=true&muted=true"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
allowfullscreen
loading="lazy"
></iframe>
That's the entire integration surface this section uses — everything is driven by the URL, so
there's no external script to load and no postMessage handshake to maintain. If you later need
to toggle mute/pause programmatically after load (not covered here), Gumlet exposes a
player.js library over postMessage — see
Gumlet's docs for that.
2. Installing the section
-
Create
sections/gumlet-video-widget.liquidin your theme (any Online Store 2.0 theme — Dawn, Refresh, or a custom theme — as long as it supports{% schema %}sections). - Paste the full code from Section 3 below into that file.
-
In the theme editor, add the section to any template (or add it to a JSON template's
sectionsobject directly). - Click Add block → Video, paste a Gumlet Asset ID. Repeat Add block for more videos.
No npm install, no asset bundling — it's a single self-contained .liquid file using Shopify's
inline {% stylesheet %} and {% javascript %} tags.
3. Full section code
Save as sections/gumlet-video-widget.liquid:
{% liquid
assign autoplay = section.settings.autoplay
assign loop_video = section.settings.loop
assign muted = section.settings.muted
assign disable_controls = true
if section.settings.show_native_controls
assign disable_controls = false
endif
assign embed_params = '?autoplay=' | append: autoplay
assign embed_params = embed_params | append: '&loop=' | append: loop_video
assign embed_params = embed_params | append: '&muted=' | append: muted
assign embed_params = embed_params | append: '&disable_player_controls=' | append: disable_controls
assign embed_params = embed_params | append: '&preload=true'
if section.settings.player_color != blank
assign color_hex = section.settings.player_color | remove: '#'
assign embed_params = embed_params | append: '&player_color=%23' | append: color_hex
endif
case section.settings.aspect_ratio
when '9:16'
assign aspect = '9/16'
when '1:1'
assign aspect = '1/1'
when '4:3'
assign aspect = '4/3'
when '21:9'
assign aspect = '21/9'
else
assign aspect = '16/9'
endcase
assign blocks = section.blocks
assign block_count = blocks.size
assign has_multiple = false
if block_count > 1
assign has_multiple = true
endif
%}
<section
class="how-gumlet-video{% unless section.settings.full_width %} how-gumlet-video--boxed{% endunless %}{% if has_multiple %} how-gumlet-video--multi{% endif %}"
id="how-gumlet-video-{{ section.id }}"
style="--how-gv-maxwidth: {{ section.settings.max_width }}px;"
>
<div class="how-gumlet-video__container">
{% if section.settings.heading != blank or section.settings.description != blank %}
<div class="how-gumlet-video__header">
{% if section.settings.heading != blank %}
<h2 class="how-gumlet-video__heading">{{ section.settings.heading }}</h2>
{% endif %}
{% if section.settings.description != blank %}
<p class="how-gumlet-video__description">{{ section.settings.description }}</p>
{% endif %}
</div>
{% endif %}
{% if block_count == 0 %}
<div class="how-gumlet-video__media" style="--how-gv-aspect: {{ aspect }};">
<div class="how-gumlet-video__placeholder">
{{ 'image' | placeholder_svg_tag }}
<p>Click "Add block" and choose "Video" to add a Gumlet video</p>
</div>
</div>
{% else %}
<div class="how-gumlet-video__row" data-how-gumlet-row>
<div class="how-gumlet-video__track" data-how-gumlet-track style="--how-gv-tile-width: {{ section.settings.tile_width }}px;">
{% for block in blocks %}
{% liquid
assign video_id = block.settings.gumlet_video_id
assign video_title = section.settings.video_title
if video_title == blank
assign video_title = section.settings.heading
endif
if video_title == blank
assign video_title = 'Video'
endif
if has_multiple
assign video_title = video_title | append: ' ' | append: forloop.index
endif
assign embed_src = 'https://play.gumlet.io/embed/' | append: video_id | append: embed_params
%}
<div class="how-gumlet-video__slide" data-how-gumlet-slide {{ block.shopify_attributes }}>
<div class="how-gumlet-video__media" style="--how-gv-aspect: {{ aspect }};" data-how-gumlet-media>
{% if video_id != blank %}
<iframe
id="how-gumlet-iframe-{{ section.id }}-{{ forloop.index }}"
class="how-gumlet-video__iframe"
data-how-gumlet-iframe
src="{{ embed_src }}"
title="{{ video_title | escape }}"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
allowfullscreen
loading="lazy"
></iframe>
{% else %}
<div class="how-gumlet-video__placeholder">
{{ 'image' | placeholder_svg_tag }}
<p>Add a Gumlet Video ID in this block's settings</p>
</div>
{% endif %}
{% if section.settings.cover_image != blank %}
<button type="button" class="how-gumlet-video__cover" data-how-gumlet-cover aria-label="Play video">
<img
src="{{ section.settings.cover_image | image_url: width: 1600 }}"
width="{{ section.settings.cover_image.width }}"
height="{{ section.settings.cover_image.height }}"
alt=""
loading="lazy"
>
<span class="how-gumlet-video__play-icon" aria-hidden="true">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
</span>
</button>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</section>
{% stylesheet %}
.how-gumlet-video {
width: 100%;
padding: 60px 0;
background: #fff;
box-sizing: border-box;
}
.how-gumlet-video--boxed {
padding: 60px 20px;
}
.how-gumlet-video * {
box-sizing: border-box;
}
.how-gumlet-video__container {
width: 100%;
margin: 0 auto;
}
.how-gumlet-video--boxed .how-gumlet-video__container {
max-width: var(--how-gv-maxwidth, 1200px);
}
.how-gumlet-video__header {
text-align: center;
margin-bottom: 32px;
padding: 0 20px;
}
.how-gumlet-video__heading {
margin: 0;
font-size: clamp(26px, 3.5vw, 40px);
font-weight: 600;
line-height: 1.2;
color: #111;
}
.how-gumlet-video__description {
margin: 12px auto 0;
max-width: 640px;
font-size: 15px;
line-height: 1.5;
color: #555;
}
.how-gumlet-video__row {
position: relative;
}
.how-gumlet-video__track {
display: flex;
justify-content: center;
gap: 4px;
overflow-x: auto;
scroll-snap-type: x proximity;
scrollbar-width: none;
-ms-overflow-style: none;
}
.how-gumlet-video__track::-webkit-scrollbar {
display: none;
}
.how-gumlet-video__slide {
flex: 1 1 220px;
max-width: var(--how-gv-tile-width, 380px);
min-width: 180px;
scroll-snap-align: center;
}
.how-gumlet-video__media {
position: relative;
width: 100%;
aspect-ratio: var(--how-gv-aspect, 16/9);
background: #000;
overflow: hidden;
}
.how-gumlet-video--boxed .how-gumlet-video__media {
border-radius: 8px;
}
.how-gumlet-video__iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
display: block;
}
.how-gumlet-video__placeholder {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
background: #f4f4f4;
color: #888;
font-size: 13px;
text-align: center;
padding: 20px;
}
.how-gumlet-video__placeholder svg {
width: 25%;
opacity: 0.4;
}
.how-gumlet-video__cover {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
padding: 0;
margin: 0;
cursor: pointer;
background: none;
opacity: 1;
visibility: visible;
transition: opacity 0.35s ease;
}
.how-gumlet-video__cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.how-gumlet-video__cover.is-hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.how-gumlet-video__play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 64px;
height: 64px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.92);
color: #111;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s ease;
}
.how-gumlet-video__cover:hover .how-gumlet-video__play-icon {
transform: translate(-50%, -50%) scale(1.08);
}
@media screen and (max-width: 749px) {
.how-gumlet-video {
padding: 40px 0;
}
.how-gumlet-video--boxed {
padding: 40px 16px;
}
.how-gumlet-video__header {
margin-bottom: 24px;
}
.how-gumlet-video__slide {
flex-basis: 68%;
max-width: 320px;
}
}
{% endstylesheet %}
{% javascript %}
(function () {
function initCovers(root) {
root.querySelectorAll('[data-how-gumlet-cover]').forEach(function (cover) {
cover.addEventListener('click', function () {
cover.classList.add('is-hidden');
});
});
}
function initSection(root) {
if (root.dataset.howGumletInit === 'true') return;
root.dataset.howGumletInit = 'true';
initCovers(root);
}
function initAll() {
document.querySelectorAll('.how-gumlet-video').forEach(initSection);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAll);
} else {
initAll();
}
document.addEventListener('shopify:section:load', function (event) {
var section = event.target.querySelector('.how-gumlet-video');
if (section) initSection(section);
});
})();
{% endjavascript %}
{% schema %}
{
"name": "Gumlet Video Widget",
"tag": "section",
"class": "how-gumlet-video-section",
"settings": [
{
"type": "header",
"content": "Content"
},
{
"type": "text",
"id": "heading",
"label": "Heading"
},
{
"type": "textarea",
"id": "description",
"label": "Description"
},
{
"type": "text",
"id": "video_title",
"label": "Video title (for accessibility)",
"info": "Applied to every video. Falls back to the heading if left blank."
},
{
"type": "image_picker",
"id": "cover_image",
"label": "Cover image",
"info": "Optional. Shown as a poster over every video until clicked."
},
{
"type": "header",
"content": "Playback settings"
},
{
"type": "select",
"id": "aspect_ratio",
"label": "Aspect ratio",
"options": [
{ "value": "9:16", "label": "Reel mode / Portrait (9:16)" },
{ "value": "16:9", "label": "Landscape (16:9)" },
{ "value": "1:1", "label": "Square (1:1)" },
{ "value": "4:3", "label": "Classic (4:3)" },
{ "value": "21:9", "label": "Cinematic (21:9)" }
],
"default": "9:16"
},
{
"type": "checkbox",
"id": "autoplay",
"label": "Autoplay",
"default": true
},
{
"type": "checkbox",
"id": "loop",
"label": "Loop video",
"default": true
},
{
"type": "checkbox",
"id": "muted",
"label": "Start muted",
"default": true,
"info": "Browsers require video to start muted for autoplay to work."
},
{
"type": "checkbox",
"id": "show_native_controls",
"label": "Show player controls",
"default": false
},
{
"type": "header",
"content": "Style"
},
{
"type": "range",
"id": "tile_width",
"min": 200,
"max": 600,
"step": 20,
"unit": "px",
"label": "Video width",
"default": 380,
"info": "Each video starts centered at this width. Adding more videos grows the row outward from the center; it scrolls horizontally once they no longer fit."
},
{
"type": "color",
"id": "player_color",
"label": "Player accent color"
},
{
"type": "checkbox",
"id": "full_width",
"label": "Full width",
"default": true
},
{
"type": "range",
"id": "max_width",
"min": 600,
"max": 1600,
"step": 20,
"unit": "px",
"label": "Max width",
"default": 1200,
"info": "Only applies when Full width is off."
}
],
"blocks": [
{
"type": "video",
"name": "Video",
"settings": [
{
"type": "text",
"id": "gumlet_video_id",
"label": "Gumlet Video/Asset ID",
"info": "Found in your Gumlet dashboard under Video Library → Asset ID. Paste only the ID, not the full URL."
}
]
}
],
"presets": [
{
"name": "Gumlet Video Widget",
"blocks": [
{
"type": "video"
}
]
}
]
}
{% endschema %}
4. How the layout works
.how-gumlet-video__trackis a flex row withjustify-content: center.-
Each
.how-gumlet-video__slidehasflex: 1 1 220pxandmax-width: var(--how-gv-tile-width)(set from the Video width setting, default380px). - 1 video → it grows up to the tile-width cap, and the leftover space on the flex line splits evenly on both sides — so it sits centered in the middle of the section.
- 2+ videos → each block adds another capped-width tile to the row; the whole row stays centered as it grows outward, edge to edge with a 4px gap.
-
Once total tile width exceeds the viewport, tiles shrink down to
min-width: 180px; past that floor,overflow-x: auto+scroll-snap-type: x proximityturns the row into a native horizontal swipe/scroll — no JS carousel controller needed.
To change the default video look, edit two things:
"default": "9:16"in theaspect_ratiosetting → landscape, square, etc."default": 380in thetile_widthsetting → wider or narrower tiles.
5. Adding videos in the theme editor
- Open the section in Customize.
- Click Add block → Video.
- Paste the Gumlet Video/Asset ID (from Gumlet dashboard → Video Library → the video's Asset ID — not the full embed URL, just the ID string).
- Repeat "Add block" for each additional video. Reorder/duplicate/delete blocks like any other Shopify block.
6. Notes for reuse in a different theme
-
The section is fully self-contained — inline CSS (
{% stylesheet %}) and JS ({% javascript %}), so there's nothing else to wire up intheme.liquidorassets/. - No external scripts are loaded at all — the only network request per video is the Gumlet iframe itself.
-
All class names are prefixed
how-gumlet-video__…(BEM-style) to avoid collisions with any theme's existing CSS. -
Class name
how-gumlet-video-section(inschema.class) is only a hook for section-specific page-level CSS if you ever need it — safe to remove. - If your theme already loads Google Fonts / a base font stack, this section inherits it (no font declarations here) — this is a deliberate choice so it matches the surrounding theme.

