How to work with video in BCMS

By Momčilo Popov•July 3, 2025•3 min read
Almost every website needs video. Maybe it’s a product demo. Maybe it’s an interview or a loop in the background. Video helps explain things better. So you need a way to manage and show it properly.
Here’s how to do that in BCMS.
Create a video widget
In BCMS, video is just content block. You create a widget for it like any other block.
A basic video widget should have:
A media field (
file) for uploaded videosA boolean (
autoplay) to control playbackA string field (
youtube_url) if you want to support YouTube embeds too

The generated TypeScript type will look like this:
export interface VideoWidget { file?: PropMediaDataParsed; autoplay?: boolean; youtube_url?: string; }
Write a component to render it
Once you have the widget, make a React (Next.js) component to show the video.
This one checks if a video was uploaded. If not, it checks if there’s a YouTube URL.
'use client'; import React, { FC, useEffect, useState } from 'react'; import type { VideoWidget as VideoWidgetType } from '@bcms-types/ts'; import BCMSMedia from '../bcms-media'; import { bcmsMediaClient } from '@root/media-client'; interface Props { data: VideoWidgetType; } const VideoWidget: FC<Props> = ({ data }) => { const [youtubeVideoId, setYoutubeVideoId] = useState(''); useEffect(() => { if (!data.youtube_url) return; const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; const match = data.youtube_url.match(regExp); setYoutubeVideoId(match && match[7].length === 11 ? match[7] : ''); }, [data.youtube_url]); return ( <div> {data.file ? ( <BCMSMedia video={{ src: bcmsMediaClient.cmsOrigin + bcmsMediaClient.media.toUri(data.file._id, data.file.name), autoplay: data.autoplay, }} /> ) : youtubeVideoId ? ( <iframe src={`https://www.youtube.com/embed/${youtubeVideoId}`} allow="autoplay; encrypted-media" allowFullScreen className="aspect-video w-full" title="YouTube video" /> ) : null} </div> ); }; export default VideoWidget;
What ss bcmsMediaClient and why it's separate
In most of my BCMS setups, you’ll see two client instances:
client.ts- used for authenticated actions like fetching contentmedia-client.ts- used only for accessing public media
I keep bcmsMediaClient separate because it's safe to expose in the browser. It only includes public keys and is used for loading images and videos from the BCMS CDN. Nothing sensitive is in there. But still, no need to use it with the content API key.
Here’s what it looks like:
// media-client.ts import { Client } from '@thebcms/client'; export const bcmsMediaClient = new Client( process.env.NEXT_PUBLIC_BCMS_ORG_ID!, process.env.NEXT_PUBLIC_BCMS_INSTANCE_ID!, { id: process.env.NEXT_PUBLIC_BCMS_MEDIA_API_KEY_ID!, secret: process.env.NEXT_PUBLIC_BCMS_MEDIA_API_KEY_SECRET!, }, { injectSvg: true, }, );
You use it to:
Generate media URLs:
bcmsMediaClient.media.toUri(...)Pass config to
< BCMSImage />or< BCMSContentManager />Avoid exposing full API access in the browser
If you only use one client for everything, you might leak sensitive keys in the frontend. That’s why I always create bcmsMediaClient separately.
Reuse the rendering logic
To keep things clean, you can use a small helper component like BCMSMedia to render both images and videos. Here’s the version for video only:
'use client'; import React, { FC } from 'react'; interface Props { video?: { src: string; autoplay?: boolean; }; className?: string; } const BCMSMedia: FC<Props> = ({ video, className = '' }) => { if (!video) return null; return ( <video controls={!video.autoplay} autoPlay={video.autoplay} playsInline={video.autoplay} muted={video.autoplay} loop={video.autoplay} className={`w-full object-contain ${className}`} > <source src={video.src} type="video/mp4" /> Your browser does not support the video element. </video> ); }; export default BCMSMedia;
How BCMS stores videos
When you upload a video in the dashboard, BCMS stores it on our CDN. You get a public URL. That’s what you use in the < video > tag.
You don’t need to worry about hosting or storage.
But BCMS doesn’t compress or resize videos. It gives you the original file. So make sure your videos are optimized before uploading.
Right now, uploading is only possible through the dashboard. API upload is coming soon, but only in the Pro version.
Using.... Cloudinary instead?
If you need more control - like resizing, transcoding, adaptive streaming - you can use Cloudinary.
Store the Cloudinary URL in your widget. Then render it however you want. BCMS doesn’t care where the video is hosted. You control that part.
Render it with BCMSContentManager
If you're using BCMSContentManager to render dynamic content, just pass the VideoWidget like this:
import { BCMSContentManager } from '@thebcms/components-react'; import VideoWidget from '@/components/widgets/video'; export default function BlogContent({ content }) { return ( <BCMSContentManager items={content} clientConfig={bcmsMediaClient.getConfig()} widgetComponents={{ video: VideoWidget, }} /> ); }
Summary
You can upload
.mp4files to BCMS and display them on your siteYou can also embed YouTube videos
Videos are served from the CDN in their original format
No file upload API yet, but it’s on the roadmap
You’re free to use Cloudinary or any other service
BCMS just gives you the data - you decide how to render it
If you want more control, build your widget and component logic around your needs. That’s the point of BCMS - it stays out of your way.
....
Content
Ready to build?
Start structuring content with BCMS and scale as your project grows. No credit card needed.
Continue reading

Connect BCMS MCP to ChatGPT
How to connect BCMS MCP with ChatGPT

Build a Weather App using Next.js, TypeScript, BCMS, and Tailwind CSS
Build a responsive weather app with Next.js, TypeScript & Tailwind. Fetch data via API, display forecasts & alerts. Master modern web dev with BCMS.

Tailwind CSS Tutorial: Start to Finish
A hands-on Tailwind CSS tutorial: setup, utilities, responsive design, and components.