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.settings if 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

  1. Create sections/gumlet-video-widget.liquid in your theme (any Online Store 2.0 theme — Dawn, Refresh, or a custom theme — as long as it supports {% schema %} sections).
  2. Paste the full code from Section 3 below into that file.
  3. In the theme editor, add the section to any template (or add it to a JSON template's sections object directly).
  4. 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__track is a flex row with justify-content: center.
  • Each .how-gumlet-video__slide has flex: 1 1 220px and max-width: var(--how-gv-tile-width) (set from the Video width setting, default 380px).
  • 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 proximity turns 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 the aspect_ratio setting → landscape, square, etc.
  • "default": 380 in the tile_width setting → wider or narrower tiles.

5. Adding videos in the theme editor

  1. Open the section in Customize.
  2. Click Add blockVideo.
  3. 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).
  4. 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 in theme.liquid or assets/.
  • 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 (in schema.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.