Skip to main content

Video Plugin

The Video plugin provides video upload, embedding, and playback capabilities with support for popular video providers (YouTube, Vimeo, Loom, Wistia, DailyMotion) and custom video sources.

Installation

npm install @yoopta/video

Usage

import Video from '@yoopta/video';
import { createYooptaEditor } from '@yoopta/editor';

const plugins = [Video];
const editor = createYooptaEditor({ plugins });

Features

  • Video upload with custom handlers
  • URL-based embedding from providers
  • Support for YouTube, Vimeo, Loom, Wistia, DailyMotion
  • Video player settings (controls, loop, muted, autoplay)
  • Poster/thumbnail upload
  • Resizable video with width/height control
  • Alignment support (left, center, right)
  • Object-fit options (contain, cover, fill)
  • Upload progress tracking
  • HTML, Markdown, and Email export

Options

import type { VideoPluginOptions } from '@yoopta/video';

const plugins = [
  Video.extend({
    options: {
      accept: 'video/*',
      upload: {
        endpoint: '/api/upload-video',
        method: 'POST',
        headers: { 'Authorization': 'Bearer token' },
      },
      uploadPoster: {
        endpoint: '/api/upload-poster',
        method: 'POST',
      },
      delete: {
        endpoint: '/api/delete-video',
        method: 'DELETE',
      },
      maxSizes: {
        maxWidth: 1920,
        maxHeight: 1080,
      },
      defaultSettings: {
        controls: true,
        loop: false,
        muted: false,
        autoPlay: false,
      },
      allowedProviders: ['youtube', 'vimeo', 'loom'],
    },
  }),
];

Plugin Options Type

type VideoPluginOptions = {
  upload?: VideoUploadOptions;
  delete?: VideoDeleteOptions;
  uploadPoster?: VideoPosterUploadOptions;
  onError?: (error: VideoUploadError) => void;
  accept?: string;
  maxFileSize?: number;
  maxSizes?: {
    maxWidth?: number | string;
    maxHeight?: number | string;
  } | null;
  defaultSettings?: VideoElementSettings;
  allowedProviders?: VideoProviderTypes[];
};

Element Type

import type { VideoElement, VideoElementProps, VideoProvider } from '@yoopta/video';

type VideoElementSettings = {
  controls?: boolean;
  loop?: boolean;
  muted?: boolean;
  autoPlay?: boolean;
};

type VideoProviderTypes = 
  | 'youtube' 
  | 'vimeo' 
  | 'dailymotion' 
  | 'loom' 
  | 'wistia' 
  | 'custom'
  | string 
  | null;

type VideoProvider = {
  type: VideoProviderTypes;
  id: string;
  url?: string;
};

type VideoElementProps = {
  id?: string | null;
  src?: string | null;
  srcSet?: string | null;
  bgColor?: string | null;
  settings?: VideoElementSettings;
  alignment?: 'left' | 'center' | 'right' | null;
  sizes?: { width: number | string; height: number | string };
  provider?: VideoProvider;
  fit?: 'contain' | 'cover' | 'fill' | null;
  poster?: string | null;
};

type VideoElement = SlateElement<'video', VideoElementProps>;

Default Props

{
  src: null,
  srcSet: null,
  bgColor: null,
  sizes: { width: 0, height: 0 },
  nodeType: 'void',
  fit: null,
  settings: {
    controls: false,
    loop: false,
    muted: false,
    autoPlay: false,
  },
  provider: null,
}

Provider Utilities

import {
  parseVideoUrl,
  buildVideoProvider,
  getEmbedUrl,
  getProvider,
  isValidVideoUrl,
  isProviderUrl,
  getSupportedProviders,
  isProviderSupported,
} from '@yoopta/video';

// Parse video URL to extract provider info
const parsed = parseVideoUrl('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
// { provider: 'youtube', id: 'dQw4w9WgXcQ', originalUrl: '...', embedUrl: '...', isValid: true }

// Check if URL is from a supported provider
if (isValidVideoUrl(url)) {
  const provider = getProvider(url); // 'youtube', 'vimeo', etc.
}

// Get list of supported providers
const providers = getSupportedProviders();
// ['youtube', 'vimeo', 'dailymotion', 'loom', 'wistia']

Hooks

useVideoUpload

import { useVideoUpload } from '@yoopta/video';
import type { UseVideoUploadReturn } from '@yoopta/video';

function VideoUploader({ editor, blockId }) {
  const { upload, loading, progress, error, cancel, reset } = useVideoUpload(
    editor,
    blockId
  );

  const handleUpload = async (file: File) => {
    try {
      const result = await upload(file);
      console.log('Uploaded:', result.url);
    } catch (err) {
      console.error('Upload failed:', err);
    }
  };

  return (
    <div>
      {loading && <p>Uploading: {progress?.percentage}%</p>}
      {error && <p>Error: {error.message}</p>}
      <input type="file" accept="video/*" onChange={(e) => handleUpload(e.target.files[0])} />
    </div>
  );
}

useVideoDelete

import { useVideoDelete } from '@yoopta/video';

const { deleteVideo, loading, error } = useVideoDelete(editor, blockId);

await deleteVideo(videoElement);

useVideoPosterUpload

import { useVideoPosterUpload } from '@yoopta/video';

const { uploadPoster, loading, progress } = useVideoPosterUpload(editor, blockId);

const posterUrl = await uploadPoster(thumbnailFile);

useVideoDimensions

import { useVideoDimensions } from '@yoopta/video';

const { width, height, duration, loading } = useVideoDimensions(videoUrl);

useVideoPreview

import { useVideoPreview } from '@yoopta/video';
import type { VideoUploadPreview } from '@yoopta/video';

const preview: VideoUploadPreview = useVideoPreview(videoFile);
// { url: string, width?: number, height?: number, duration?: number }

Commands

import { VideoCommands } from '@yoopta/video';

// Insert video from URL
editor.commands.Video.insertVideo({
  src: 'https://example.com/video.mp4',
  sizes: { width: 1280, height: 720 },
  settings: { controls: true, loop: false },
});

// Insert video from provider URL
editor.commands.Video.insertVideoFromProvider({
  url: 'https://www.youtube.com/watch?v=VIDEO_ID',
});

// Update video settings
editor.commands.Video.updateSettings(blockId, {
  controls: true,
  autoPlay: true,
  muted: true,
});

Parsers

HTML

Deserialize: Converts <video> tags to video blocks Serialize: Outputs video in flex container for alignment
<div style="
  margin-left: 0px; 
  display: flex; 
  width: 100%; 
  justify-content: center;
">
  <video 
    data-meta-align="center" 
    data-meta-depth="0" 
    src="https://example.com/video.mp4" 
    width="1280" 
    height="720" 
    controls="true" 
    loop="false" 
    muted="false" 
    autoplay="false" 
    style="margin: 0 auto;" 
    objectFit="cover"
  />
</div>

Markdown

![https://example.com/video.mp4](https://example.com/video.mp4)

Email

Shows poster image with play button overlay linking to video:
<table style="width: 100%;">
  <tbody style="width: 100%;">
    <tr>
      <td style="display: flex; justify-content: center; padding: 1rem 0;">
        <a href="https://example.com/video.mp4" target="_blank">
          <img src="poster.jpg" width="1280" height="720" alt="Poster" />
          <!-- Play button SVG overlay -->
        </a>
      </td>
    </tr>
  </tbody>
</table>

Examples

Basic Video Embed

import { Blocks } from '@yoopta/editor';
import { generateId } from '@yoopta/editor';

Blocks.insertBlock(editor, {
  type: 'Video',
  value: [
    {
      id: generateId(),
      type: 'video',
      props: {
        src: 'https://example.com/video.mp4',
        poster: 'https://example.com/poster.jpg',
        fit: 'cover',
        sizes: { width: 1280, height: 720 },
        settings: {
          controls: true,
          loop: false,
          muted: false,
          autoPlay: false,
        },
      },
      children: [{ text: '' }],
    },
  ],
});

YouTube Embed

import { parseVideoUrl } from '@yoopta/video';

const youtubeUrl = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
const parsed = parseVideoUrl(youtubeUrl);

Blocks.insertBlock(editor, {
  type: 'Video',
  value: [
    {
      id: generateId(),
      type: 'video',
      props: {
        provider: {
          type: parsed.provider,
          id: parsed.id,
          url: parsed.originalUrl,
        },
        sizes: { width: 853, height: 480 },
      },
      children: [{ text: '' }],
    },
  ],
});

Custom Upload Handler

import type { VideoUploadFn } from '@yoopta/video';

const customVideoUpload: VideoUploadFn = async (file, onProgress) => {
  const formData = new FormData();
  formData.append('video', file);

  const xhr = new XMLHttpRequest();
  
  xhr.upload.addEventListener('progress', (e) => {
    if (onProgress && e.lengthComputable) {
      onProgress({
        loaded: e.loaded,
        total: e.total,
        percentage: Math.round((e.loaded / e.total) * 100),
      });
    }
  });

  const response = await new Promise((resolve, reject) => {
    xhr.onload = () => resolve(JSON.parse(xhr.responseText));
    xhr.onerror = reject;
    xhr.open('POST', '/api/upload-video');
    xhr.send(formData);
  });

  return {
    id: response.id,
    src: response.url,
    poster: response.thumbnail,
    sizes: {
      width: response.width,
      height: response.height,
    },
  };
};

const plugins = [
  Video.extend({
    options: { upload: customVideoUpload },
  }),
];

Supported Providers

  • YouTube: https://www.youtube.com/watch?v=VIDEO_ID
  • Vimeo: https://vimeo.com/VIDEO_ID
  • Loom: https://www.loom.com/share/VIDEO_ID
  • Wistia: https://username.wistia.com/medias/VIDEO_ID
  • DailyMotion: https://www.dailymotion.com/video/VIDEO_ID

Build docs developers (and LLMs) love