These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Mux?
Mux is an API platform built specifically for video, letting developers create sophisticated live and on-demand video experiences through simple API calls. Instead of managing complex infrastructure, you make API requests while Mux handles the technical processing.
The platform manages processing, storage, and delivery with automatic transcoding into multiple formats and resolutions. This adaptive approach ensures smooth playback regardless of device or connection speed—viewers always get the best quality their current setup can handle.
Mux distributes content through a global CDN, putting videos closer to viewers worldwide to cut latency and speed up loading. You also get real-time analytics showing performance, engagement, and delivery metrics.
For developers building content systems, Mux's integration capabilities provide robust video functionality without requiring you to manage complex infrastructure yourself.
Why Integrate Mux with Strapi
Combining Strapi's headless CMS with Mux's video processing creates a powerful content system that addresses the unique challenges of video-first applications. It brings together intuitive content management with reliable video infrastructure.
Your content team can upload videos directly in Strapi without switching between platforms or managing complex workflows. They can handle videos alongside text, images, and other media in one familiar interface. The Mux Video Uploader plugin transforms video management from a technical challenge into a straightforward editorial process.
This integration is particularly valuable when videos need to connect with custom collection types. Whether you're building a learning system, media library, or product showcase, you can create content models that fit your specific business requirements. Videos become first-class citizens in your content architecture, not awkward add-ons.
The streamlined workflow eliminates friction between creation and delivery. Your team can upload, organize, and publish video content without switching tools or waiting for technical teams to handle processing.
Strapi's role-based access control protects your video content by precisely controlling who can upload videos, edit metadata, and publish. Your valuable video assets remain secure throughout their lifecycle.
As your video library grows, Strapi's flexible architecture adapts while Mux handles the technical complexities of processing, storage, and global delivery.
Strapi Cloud and v5 enhance this integration with advanced features and improved performance, bringing enterprise-scale video management within reach.
To get started with this powerful combination, you can deploy Strapi, explore our demo, or join our community.
Keep in touch with the latest Strapi and Mux updates
How to Integrate Mux with Strapi
Setting up Strapi and Mux requires attention to detail and good security practices. We'll walk through the process step by step, from initial setup to video playback in your frontend.
Prerequisites
Before starting, you'll need:
- Node.js version 16 or higher
- NPM or Yarn as your package manager
- Git for version control
- A free Mux account for their video API services
- A publicly accessible Strapi installation
The public accessibility requirement creates a challenge for local development. You'll need a webhook relay service like SMEE or tools like ngrok to make your local server reachable.
It's important to prioritize security throughout this process. Never commit API keys or sensitive credentials to your repository. Use environment variables to store these values securely. Use a .env
file for local development (add it to your .gitignore
) and proper environment management in production.
Installing the Mux Video Uploader Plugin
Different Strapi versions need different plugin versions, so install the right one for your setup.
For Strapi v4, use the latest plugin version:
npm i strapi-plugin-mux-video-uploader@latest
If you're using Yarn:
yarn add strapi-plugin-mux-video-uploader@latest
For Strapi v3 installations, use version 2.0.0 specifically:
npm i strapi-plugin-mux-video-uploader@2.0.0
Or with Yarn:
yarn add strapi-plugin-mux-video-uploader@2.0.0
Restart your Strapi instance after installation for the plugin to take effect. This step is crucial for Strapi to recognize and initialize the plugin properly.
Creating and Configuring Mux Access Tokens
Proper token management creates secure communication between Strapi and Mux. To get started, log into your Mux account, go to Settings, then API Access Tokens.
Click "Generate new token" with appropriate settings. Choose "Development" for your development environment. Select "Mux Video" as the service and enable both "Read" and "Write" permissions. These permissions allow your Strapi app to both retrieve video information and upload new content.
After generating the token, you'll get both an Access Token ID and a Secret Key. Copy both immediately—the Secret Key won't appear again for security reasons. Store these credentials using environment variables, not hardcoded in your application.
In your Strapi admin panel, go to the Mux Video Uploader plugin settings. Enter your Mux Access Token ID and Secret Key in the fields provided. Save to connect Strapi with your Mux account. Test by uploading a small video file to ensure everything works correctly.
Setting Up Webhooks
Webhooks provide real-time updates about video processing from Mux. When you upload a video, it goes through various processing stages, and webhooks keep Strapi informed without constant polling.
In your Mux dashboard, go to "Webhooks" and create a new webhook. The webhook URL should follow this format: https://your-strapi-domain.com/strapi-plugin-mux-video-uploader/webhooks
. Replace "your-strapi-domain.com" with your actual domain. Select the events you want to track, like upload completion, encoding progress, and error notifications.
For local development, you need special handling since Mux can't reach your local server directly. Set up a webhook relay using a service like SMEE. Create a SMEE channel at smee.io and install the SMEE client:
npm install -g smee-client
Run the SMEE client to forward webhook events to your local Strapi:
smee --url https://smee.io/your-unique-channel --path /strapi-plugin-mux-video-uploader/webhooks --port 1337
Use the SMEE URL as your webhook endpoint in the Mux dashboard instead of your local URL. This creates a tunnel allowing Mux to send webhook events to your local environment.
Creating Content Types for Videos
With the plugin installed and configured, create a content type for managing video content. In your Strapi admin panel, go to Content-Types Builder to create a new collection type. Name it "Video" or something appropriate for your use case.
Add fields that make sense for your video content needs. Include Title for the video name, Description for content details, and Categories or Tags for organization. The key field is the Video field, which uses the Mux Video Uploader field type from the plugin.
Consider adding Featured Image for thumbnails, Release Date for scheduling, and Duration for display. You might also want fields for SEO metadata, content ratings, or other attributes specific to your application.
Save your content type and restart Strapi if prompted. Content editors can now use this type to upload videos directly through the Strapi interface. The Video Uploader field provides an intuitive interface for selecting and uploading videos, with real-time feedback about upload progress and processing.
Implementing Video Playback in Frontend Applications
After setting up your backend, implement video playback in your frontend application. We'll use Next.js as an example, though the principles apply to other React-based frameworks too.
To get started, set up a Next.js project if you don't have one:
npx create-next-app my-video-app
cd my-video-app
Install the Mux React Player component:
npm install @mux/mux-player-react
Create a component to fetch and display videos from your Strapi API:
1import { useState, useEffect } from 'react';
2import MuxPlayer from '@mux/mux-player-react';
3
4export default function VideoGallery() {
5 const [videos, setVideos] = useState([]);
6 const [loading, setLoading] = useState(true);
7
8 useEffect(() => {
9 async function fetchVideos() {
10 try {
11 const response = await fetch('https://your-strapi-api.com/api/videos?populate=*');
12 const data = await response.json();
13 setVideos(data.data);
14 } catch (error) {
15 console.error('Error fetching videos:', error);
16 } finally {
17 setLoading(false);
18 }
19 }
20
21 fetchVideos();
22 }, []);
23
24 if (loading) return <div>Loading videos...</div>;
25
26 return (
27 <div className="video-gallery">
28 <h1>Video Gallery</h1>
29 <div className="video-grid">
30 {videos.map((video) => (
31 <div key={video.id} className="video-card">
32 <h2>{video.attributes.title}</h2>
33 <p>{video.attributes.description}</p>
34 <MuxPlayer
35 streamType="on-demand"
36 playbackId={video.attributes.muxVideo.data.attributes.playbackId}
37 metadata={{
38 video_title: video.attributes.title,
39 video_id: video.id,
40 }}
41 style={{ aspectRatio: '16/9', width: '100%' }}
42 />
43 </div>
44 ))}
45 </div>
46 </div>
47 );
48}
This implementation fetches video data from your Strapi API and renders each video using the Mux Player component. The player handles adaptive streaming automatically, ensuring optimal playback quality based on the viewer's connection and device.
Replace the API URL with your actual Strapi endpoint and adjust the data structure access based on your content type configuration. The integration tutorial provides more examples and customization options for complex implementations.
Keep in touch with the latest Strapi and Mux updates
Project Example: Build a Learning Management System with Strapi and Mux
Let's explore "EduStream," a learning management system that demonstrates the capabilities of integrating Mux with Strapi.
The backend uses Strapi v4 with the Video Uploader plugin handling seamless uploads. Here's how to set up your content structure:
1// api/course/content-types/course/schema.json
2{
3 "kind": "collectionType",
4 "collectionName": "courses",
5 "info": {
6 "singularName": "course",
7 "pluralName": "courses",
8 "displayName": "Course"
9 },
10 "options": {
11 "draftAndPublish": true
12 },
13 "attributes": {
14 "title": {
15 "type": "string",
16 "required": true
17 },
18 "description": {
19 "type": "richtext"
20 },
21 "videos": {
22 "type": "relation",
23 "relation": "oneToMany",
24 "target": "api::video.video"
25 }
26 }
27}
1// api/video/content-types/video/schema.json
2{
3 "kind": "collectionType",
4 "collectionName": "videos",
5 "info": {
6 "singularName": "video",
7 "pluralName": "videos",
8 "displayName": "Video"
9 },
10 "options": {
11 "draftAndPublish": true
12 },
13 "attributes": {
14 "title": {
15 "type": "string",
16 "required": true
17 },
18 "description": {
19 "type": "text"
20 },
21 "duration": {
22 "type": "decimal"
23 },
24 "transcript": {
25 "type": "richtext"
26 },
27 "categories": {
28 "type": "enumeration",
29 "enum": ["beginner", "intermediate", "advanced"]
30 },
31 "asset": {
32 "type": "customField",
33 "customField": "plugin::mux-video-uploader.asset"
34 },
35 "course": {
36 "type": "relation",
37 "relation": "manyToOne",
38 "target": "api::course.course",
39 "inversedBy": "videos"
40 }
41 }
42}
After setting up your content types, follow these steps to implement the integration:
- Install the plugin in your Strapi project:
cd your-strapi-project
npm i strapi-plugin-mux-video-uploader@latest
- Configure environment variables:
1# .env
2MUX_TOKEN_ID=your_token_id
3MUX_TOKEN_SECRET=your_token_secret
4MUX_WEBHOOK_SECRET=your_webhook_secret
- Restart Strapi and verify the plugin appears in your admin panel
The Next.js frontend uses React Player components for intuitive browsing. Here's a sample component for displaying a course video:
1// components/VideoPlayer.js
2import React, { useState } from 'react';
3import ReactPlayer from 'react-player';
4
5const VideoPlayer = ({ video }) => {
6 const [playing, setPlaying] = useState(false);
7 const [playbackRate, setPlaybackRate] = useState(1);
8
9 // Extract playback ID from Mux asset
10 const playbackId = video?.asset?.data?.attributes?.playbackId;
11
12 if (!playbackId) return <div>Video not available</div>;
13
14 const videoUrl = `https://stream.mux.com/${playbackId}.m3u8`;
15
16 return (
17 <div className="video-container">
18 <h2>{video.title}</h2>
19 <div className="player-wrapper">
20 <ReactPlayer
21 url={videoUrl}
22 controls
23 playing={playing}
24 playbackRate={playbackRate}
25 width="100%"
26 height="100%"
27 onPlay={() => setPlaying(true)}
28 onPause={() => setPlaying(false)}
29 />
30 </div>
31 <div className="playback-controls">
32 <button onClick={() => setPlaybackRate(0.75)}>0.75x</button>
33 <button onClick={() => setPlaybackRate(1)}>1x</button>
34 <button onClick={() => setPlaybackRate(1.5)}>1.5x</button>
35 <button onClick={() => setPlaybackRate(2)}>2x</button>
36 </div>
37 {video.transcript && (
38 <div className="transcript">
39 <h3>Transcript</h3>
40 <div dangerouslySetInnerHTML={{ __html: video.transcript }} />
41 </div>
42 )}
43 </div>
44 );
45};
46
47export default VideoPlayer;
To fetch course data from Strapi, implement an API service:
1// services/api.js
2const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1337';
3
4export async function getCourses() {
5 const res = await fetch(`${API_URL}/api/courses?populate=*`);
6 const data = await res.json();
7 return data.data;
8}
9
10export async function getCourse(id) {
11 const res = await fetch(
12 `${API_URL}/api/courses/${id}?populate[videos][populate]=*`
13 );
14 const data = await res.json();
15 return data.data;
16}
17
18export async function getVideo(id) {
19 const res = await fetch(`${API_URL}/api/videos/${id}?populate=*`);
20 const data = await res.json();
21 return data.data;
22}
For the course listing page:
1// pages/courses/index.js
2import { useEffect, useState } from 'react';
3import Link from 'next/link';
4import { getCourses } from '../../services/api';
5
6export default function Courses() {
7 const [courses, setCourses] = useState([]);
8 const [loading, setLoading] = useState(true);
9
10 useEffect(() => {
11 async function loadCourses() {
12 try {
13 const data = await getCourses();
14 setCourses(data);
15 } catch (error) {
16 console.error('Error loading courses:', error);
17 } finally {
18 setLoading(false);
19 }
20 }
21
22 loadCourses();
23 }, []);
24
25 if (loading) return <div>Loading courses...</div>;
26
27 return (
28 <div className="courses-grid">
29 {courses.map(course => (
30 <Link href={`/courses/${course.id}`} key={course.id}>
31 <div className="course-card">
32 <h2>{course.attributes.title}</h2>
33 <p>{course.attributes.description}</p>
34 <span>{course.attributes.videos?.data?.length || 0} videos</span>
35 </div>
36 </Link>
37 ))}
38 </div>
39 );
40}
Security follows best practices with environment-stored API tokens and role-based access limiting premium content to enrolled students. Here's how to implement role-based access:
1// api/course/controllers/course.js
2'use strict';
3
4module.exports = {
5 async find(ctx) {
6 // Check if user is logged in
7 if (!ctx.state.user) {
8 return ctx.unauthorized('You must be logged in');
9 }
10
11 // Get user roles
12 const { id } = ctx.state.user;
13 const user = await strapi.entityService.findOne('plugin::users-permissions.user', id, {
14 populate: ['role', 'enrollments']
15 });
16
17 // Define query based on user role
18 let query = {};
19
20 // If not admin, only show enrolled courses
21 if (user.role.name !== 'Administrator') {
22 const enrolledCourseIds = user.enrollments.map(enrollment => enrollment.course.id);
23 query = {
24 id: { $in: enrolledCourseIds }
25 };
26 }
27
28 // Execute the query with populated videos
29 const courses = await strapi.entityService.findMany('api::course.course', {
30 filters: query,
31 populate: ['videos']
32 });
33
34 return courses;
35 }
36};
Webhooks provide real-time processing updates, informing users when new content becomes available. To handle this on the frontend:
1// utils/notifications.js
2import { toast } from 'react-toastify';
3
4export function setupVideoProcessingListener(userId) {
5 const eventSource = new EventSource(`/api/notifications/sse?userId=${userId}`);
6
7 eventSource.onmessage = (event) => {
8 const data = JSON.parse(event.data);
9
10 if (data.type === 'video_ready') {
11 toast.success(`New video available: ${data.videoTitle}`);
12 }
13 };
14
15 eventSource.onerror = () => {
16 eventSource.close();
17 setTimeout(() => setupVideoProcessingListener(userId), 5000);
18 };
19
20 return () => eventSource.close();
21}
The interface features a responsive grid with auto-generated thumbnails, embedded players supporting speed controls and quality selection, plus a progress-tracking dashboard. The design focuses on clean navigation and smooth playback across devices.
This implementation demonstrates how Strapi's content flexibility combines with Mux's processing power to build scalable applications with excellent user experience and security.
You can explore the full EduStream codebase in our GitHub repository, including backend configuration, frontend components, deployment scripts, and production documentation.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours Monday through Friday at 12:30 pm – 1:30 pm CST.
For more details, visit the Strapi documentation and Mux.