Blog
How to add a custom video player to your Webflow site

How to add a custom video player to your Webflow site

Share this article

Why use a custom video player instead of Webflow’s native video element?

Full control over video behavior → Customize controls, autoplay, looping, and fullscreen options.
GDPR compliance → Unlike YouTube, self-hosted videos do not use tracking cookies, meaning no consent is required.
Better branding → No third-party branding or suggested videos after playback.
Optimized performance → Self-hosted MP4/WebM videos load faster and do not rely on external services.

Step 1: convert and host your video

Before adding a video player, make sure your video is in MP4 and WebM formats for maximum compatibility.

Convert your video

Upload your video to Cloudinary

  1. Create a free account on Cloudinary.
  2. Go to Dashboard → Assets and upload your MP4 and WebM files.
  3. Copy the direct video URL from Cloudinary.

Step 2: add the custom video player code

Now that your video is ready, you need to insert it into Webflow using custom HTML, CSS, and JavaScript.

Insert this code in an embed element in Webflow:

<video class="video" width="100%" height="100%">
    <source src="https://res.cloudinary.com/your-account/video.mp4" type="video/mp4">
    <source src="https://res.cloudinary.com/your-account/video.webm" type="video/webm">
</video>

📌 Replace the URLs with the links from your Cloudinary Asset Manager.

Step 3: add custom video player controls

Now, we’ll add JavaScript to control video playback, pause, fullscreen, and progress tracking.

Insert this JavaScript in Webflow’s custom code section:

<script>
  document.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll("video").forEach(video => {
      const container = document.createElement("div");
      container.classList.add("video-container");
      video.parentNode.insertBefore(container, video);
      container.appendChild(video);

      const controls = document.createElement("div");
      controls.classList.add("video-controls");

      const playPauseBtn = document.createElement("button");
      playPauseBtn.classList.add("video-button");
      playPauseBtn.innerHTML = `
        <svg id="pauseIcon" width="24" height="24" fill="white">
          <path d="M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2m6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2"></path>
        </svg>
        <svg id="playIcon" width="24" height="24" fill="white" style="display: none;">
          <path d="M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18a1 1 0 0 0 0-1.69L9.54 5.98A.998.998 0 0 0 8 6.82"></path>
        </svg>
      `;
      controls.appendChild(playPauseBtn);

      const progressContainer = document.createElement("div");
      progressContainer.classList.add("video-progress-container");

      const progressBar = document.createElement("div");
      progressBar.classList.add("video-progress-bar");
      progressContainer.appendChild(progressBar);
      controls.appendChild(progressContainer);

      const fullscreenBtn = document.createElement("button");
      fullscreenBtn.classList.add("video-button");
      fullscreenBtn.innerHTML = `
        <svg width="24" height="24" fill="white">
          <path d="M4 4h6v2H6v4H4V4m10 0h6v6h-2V6h-4V4m-6 10v4h4v2H4v-6h2m16 0v6h-6v-2h4v-4h2"></path>
        </svg>
      `;
      controls.appendChild(fullscreenBtn);
      container.appendChild(controls);

      playPauseBtn.addEventListener("click", () => {
        if (video.paused) {
          video.play();
          playPauseBtn.querySelector("#playIcon").style.display = "none";
          playPauseBtn.querySelector("#pauseIcon").style.display = "block";
        } else {
          video.pause();
          playPauseBtn.querySelector("#playIcon").style.display = "block";
          playPauseBtn.querySelector("#pauseIcon").style.display = "none";
        }
      });

      fullscreenBtn.addEventListener("click", () => {
        if (video.requestFullscreen) {
          video.requestFullscreen();
        } 
      });

      video.addEventListener("timeupdate", () => {
        const percent = (video.currentTime / video.duration) * 100;
        progressBar.style.width = `${percent}%`;
      });

      progressContainer.addEventListener("click", (e) => {
        const rect = progressContainer.getBoundingClientRect();
        const offsetX = e.clientX - rect.left;
        video.currentTime = (offsetX / rect.width) * video.duration;
      });
    });
  });
</script>

{{blog_article_ad}}

Step 4: style the video player

To make controls visible and responsive, add this CSS in Webflow’s Custom Code → Head Section:

<style>
.video-container {
  position: relative;
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
.video-controls {
  position: absolute;
  bottom: 0;
  width: 100%;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  padding: 8px;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}
.video-container:hover .video-controls {
  opacity: 1;
}
.video-button {
  background: none;
  border: none;
  cursor: pointer;
}
.video-progress-container {
  flex: 1;
  height: 6px;
  background: rgba(255, 255, 255, 0.3);
  cursor: pointer;
  position: relative;
}
.video-progress-bar {
  width: 0%;
  height: 100%;
  background: white;
}
</style>


Alternative: autoplay with Finsweet

If you want your video to autoplay and loop without controls, use Finsweet’s Auto Video Attributes (Finsweet Auto Video). This solution removes controls but ensures seamless video playback.

Conclusion

By implementing a custom video player, you ensure GDPR compliance, improve branding, and gain full control over video behavior. If you want autoplay without controls, consider using Finsweet Auto Video Attributes. Now, your Webflow site can feature fully customized, self-hosted videos without relying on YouTube! 🚀

Webflow’s native Video element is great for embedding content from YouTube and Vimeo, but it lacks flexibility for customization and self-hosted videos. By following this guide, you’ll have a fully customized video player that fits your brand and ensures full control over the user experience.

YouTube videos come with tracking cookies, which require user consent under GDPR regulations. if a visitor does not accept cookies, the youtube video will not display. self-hosted videos provide full control over branding, improve loading speeds, and remove third-party ads.
You can use Finsweet’s Auto Video Attributes (Finsweet auto video) to enable autoplay and looping without user interaction. simply add the attribute fs-video="auto" to your video in Webflow.
Cloudinary is a great option. you can create a free account, upload mp4 and webm versions, and get a direct video URL to use in your Webflow embed.
You can customize the video container, play/pause button, progress bar, and fullscreen toggle using css. the guide above includes a fully customizable css snippet to modify the look of your video player and align it with your brand design.
none
none
Our cooking skills are questionable. Our web skills? Not at all. Let’s talk!
Unique & tailor-made website
Full control, zero dependency
Fast, smooth, SEO-optimized
Book a meeting
bebranded realisations image

Let's grow your website - and the rest

Our team is here to understand your needs & work with you to create your digital experience.