Loading...
Files
components/demo.tsx
'use client';

import * as React from 'react';

import { Plate, usePlateEditor } from 'platejs/react';

import { EditorKit } from '@/components/editor/editor-kit';
import { Editor, EditorContainer } from '@/components/ui/editor';

import { DEMO_VALUES } from './values/demo-values';

export default function Demo({ id }: { id: string }) {
  const editor = usePlateEditor({
    plugins: EditorKit,
    value: DEMO_VALUES[id],
  });

  return (
    <Plate editor={editor}>
      <EditorContainer variant="demo">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Features

Media Support

  • File types:
    • Image
    • Video
    • Audio
    • Others (PDF, Word, etc.)
  • Video providers:
    • Local video files
    • YouTube, Vimeo, Dailymotion, Youku, Coub
  • Embed providers:
    • Tweets

Media Features

  • Editable captions
  • Resizable elements

Upload

  • Multiple upload methods:
    • Toolbar button with file picker
    • Drag and drop from file system
    • Paste from clipboard (images)
    • URL embedding for external media
  • Upload experience:
    • Real-time progress tracking
    • Preview during upload
    • Automatically converts the placeholder to the appropriate media element (image, video, audio, file) once the upload or embed is submitted
    • Error handling
    • File size validation
    • Type validation

Kit Usage

Installation

The fastest way to add comprehensive media support is with the MediaKit, which includes pre-configured ImagePlugin, VideoPlugin, AudioPlugin, FilePlugin, MediaEmbedPlugin, PlaceholderPlugin, and CaptionPlugin with their Plate UI components.

'use client';
 
import { CaptionPlugin } from '@platejs/caption/react';
import {
  AudioPlugin,
  FilePlugin,
  ImagePlugin,
  MediaEmbedPlugin,
  PlaceholderPlugin,
  VideoPlugin,
} from '@platejs/media/react';
import { KEYS } from 'platejs';
 
import { AudioElement } from '@/components/ui/media-audio-node';
import { MediaEmbedElement } from '@/components/ui/media-embed-node';
import { FileElement } from '@/components/ui/media-file-node';
import { ImageElement } from '@/components/ui/media-image-node';
import { PlaceholderElement } from '@/components/ui/media-placeholder-node';
import { MediaPreviewDialog } from '@/components/ui/media-preview-dialog';
import { MediaUploadToast } from '@/components/ui/media-upload-toast';
import { VideoElement } from '@/components/ui/media-video-node';
 
export const MediaKit = [
  ImagePlugin.configure({
    options: { disableUploadInsert: true },
    render: { afterEditable: MediaPreviewDialog, node: ImageElement },
  }),
  MediaEmbedPlugin.withComponent(MediaEmbedElement),
  VideoPlugin.withComponent(VideoElement),
  AudioPlugin.withComponent(AudioElement),
  FilePlugin.withComponent(FileElement),
  PlaceholderPlugin.configure({
    options: { disableEmptyPlaceholder: true },
    render: { afterEditable: MediaUploadToast, node: PlaceholderElement },
  }),
  CaptionPlugin.configure({
    options: {
      query: {
        allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
      },
    },
  }),
];

Add Kit

Add the kit to your plugins:

import { createPlateEditor } from 'platejs/react';
import { MediaKit } from '@/components/editor/plugins/media-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...MediaKit,
  ],
});

Add API Routes

npx shadcn@latest add https://platejs.org/r/media-uploadthing-api

Environment Setup

Get your secret key from UploadThing and add it to .env:

.env
UPLOADTHING_TOKEN=xxx

Manual Usage

Installation

pnpm add @platejs/media

Add Plugins

Include the media plugins in your Plate plugins array when creating the editor.

import {
  AudioPlugin,
  FilePlugin,
  ImagePlugin,
  MediaEmbedPlugin,
  PlaceholderPlugin,
  VideoPlugin,
} from '@platejs/media/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ImagePlugin,
    VideoPlugin,
    AudioPlugin,
    FilePlugin,
    MediaEmbedPlugin,
    PlaceholderPlugin,
  ],
});

Configure Plugins

Configure the plugins with custom components and upload settings.

import {
  AudioPlugin,
  FilePlugin,
  ImagePlugin,
  MediaEmbedPlugin,
  PlaceholderPlugin,
  VideoPlugin,
} from '@platejs/media/react';
import { KEYS } from 'platejs';
import { createPlateEditor } from 'platejs/react';
import { 
  AudioElement, 
  FileElement, 
  ImageElement, 
  MediaEmbedElement, 
  PlaceholderElement, 
  VideoElement 
} from '@/components/ui/media-nodes';
import { MediaUploadToast } from '@/components/ui/media-upload-toast';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ImagePlugin.withComponent(ImageElement),
    VideoPlugin.withComponent(VideoElement),
    AudioPlugin.withComponent(AudioElement),
    FilePlugin.withComponent(FileElement),
    MediaEmbedPlugin.withComponent(MediaEmbedElement),
    PlaceholderPlugin.configure({
      options: { disableEmptyPlaceholder: true },
      render: { afterEditable: MediaUploadToast, node: PlaceholderElement },
    }),
  ],
});
  • withComponent: Assigns custom components to render each media type.
  • options.disableEmptyPlaceholder: Prevents showing placeholder when no file is uploading.
  • render.afterEditable: Renders upload progress toast outside the editor.

Caption Support

To enable media captions, add the Caption Plugin:

import { CaptionPlugin } from '@platejs/caption/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    // ...media plugins,
    CaptionPlugin.configure({
      options: {
        query: {
          allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
        },
      },
    }),
  ],
});

Custom Upload Implementation

For custom upload implementations, create an upload hook that matches this interface:

interface UseUploadFileProps {
  onUploadComplete?: (file: UploadedFile) => void;
  onUploadError?: (error: unknown) => void;
  headers?: Record<string, string>;
  onUploadBegin?: (fileName: string) => void;
  onUploadProgress?: (progress: { progress: number }) => void;
  skipPolling?: boolean;
}
 
interface UploadedFile {
  key: string;    // Unique identifier
  url: string;    // Public URL of the uploaded file
  name: string;   // Original filename
  size: number;   // File size in bytes
  type: string;   // MIME type
}

Example implementation with S3 presigned URLs:

export function useUploadFile({ 
  onUploadComplete, 
  onUploadError, 
  onUploadProgress 
}: UseUploadFileProps = {}) {
  const [uploadedFile, setUploadedFile] = useState<UploadedFile>();
  const [uploadingFile, setUploadingFile] = useState<File>();
  const [progress, setProgress] = useState(0);
  const [isUploading, setIsUploading] = useState(false);
 
  async function uploadFile(file: File) {
    setIsUploading(true);
    setUploadingFile(file);
 
    try {
      // Get presigned URL and final URL from your backend
      const { presignedUrl, fileUrl, fileKey } = await fetch('/api/upload', {
        method: 'POST',
        body: JSON.stringify({
          filename: file.name,
          contentType: file.type,
        }),
      }).then(r => r.json());
 
      // Upload to S3 using presigned URL
      await axios.put(presignedUrl, file, {
        headers: { 'Content-Type': file.type },
        onUploadProgress: (progressEvent) => {
          const progress = (progressEvent.loaded / progressEvent.total) * 100;
          setProgress(progress);
          onUploadProgress?.({ progress });
        },
      });
 
      const uploadedFile = {
        key: fileKey,
        url: fileUrl,
        name: file.name,
        size: file.size,
        type: file.type,
      };
 
      setUploadedFile(uploadedFile);
      onUploadComplete?.(uploadedFile);
      
      return uploadedFile;
    } catch (error) {
      onUploadError?.(error);
      throw error;
    } finally {
      setProgress(0);
      setIsUploading(false);
      setUploadingFile(undefined);
    }
  }
 
  return {
    isUploading,
    progress,
    uploadFile,
    uploadedFile,
    uploadingFile,
  };
}

Then integrate your custom upload hook with the media components:

import { useUploadFile } from '@/hooks/use-upload-file'; // Your custom hook
 
// In your PlaceholderElement component
export function PlaceholderElement({ className, children, element, ...props }) {
  const { uploadFile, isUploading, progress } = useUploadFile({
    onUploadComplete: (uploadedFile) => {
      // Replace placeholder with actual media element
      const { url, type } = uploadedFile;
      
      // Transform placeholder to appropriate media type
      editor.tf.replace.placeholder({
        id: element.id,
        url,
        type: getMediaType(type), // image, video, audio, file
      });
    },
    onUploadError: (error) => {
      console.error('Upload failed:', error);
      // Handle upload error, maybe show toast
    },
  });
 
  // Use uploadFile when files are dropped or selected
  // This integrates with the PlaceholderPlugin's file handling
}

Add Toolbar Button

You can add MediaToolbarButton to your Toolbar to upload and insert media.

Insert Toolbar Button

You can add these items to the Insert Toolbar Button to insert media elements:

{
  icon: <ImageIcon />,
  label: 'Image',
  value: KEYS.img,
}

Plate Plus

  • Integration with UploadThing
  • Use slash commands for quick insertion
  • Displays clickable placeholders for various media types (image, video, audio, file)
  • Opens a popover with two tabs when the placeholder is clicked:
    • Upload tab: Allows uploading local files directly
    • Embed tab: Enables pasting embed links for media content
  • Image-specific features:
    • Better loading rendering and image replacement
    • Alignment options
    • Expand/collapse view
    • Download button
  • Video-specific features:
    • Lazy load
    • Alignment options
    • Caption support
    • View original source
  • Floating toolbar appears at the top right of media elements:
    • Alignment dropdown menu
    • Caption button
    • Expand button
    • Download button
  • Beautifully crafted UI

Plugins

ImagePlugin

Plugin for void image elements.

OptionsImagePluginOptions

Collapse all

    Function to upload image to a server. Receives:

    • Data URL (string) from FileReader.readAsDataURL
    • ArrayBuffer from clipboard data Returns:
    • URL string to uploaded image
    • Original data URL/ArrayBuffer if no upload needed
    • Default: Returns original input

    Disables file upload on data insertion.

    • Default: false

    Disables URL embed on data insertion.

    • Default: false

    A function to check whether a text string is a URL.

    A function to transform the URL.

VideoPlugin

Plugin for void video elements. Extends MediaPluginOptions.

AudioPlugin

Plugin for void audio elements. Extends MediaPluginOptions.

FilePlugin

Plugin for void file elements. Extends MediaPluginOptions.

MediaEmbedPlugin

Plugin for void media embed elements. Extends MediaPluginOptions.

PlaceholderPlugin

Plugin for managing media placeholders during upload. Handles file uploads, drag & drop, and clipboard paste events.

Optionsobject

Collapse all

    Configuration for different file types. Default configuration:

    {
      audio: {
        maxFileCount: 1,
        maxFileSize: '8MB',
        mediaType: KEYS.audio,
        minFileCount: 1,
      },
      blob: {
        maxFileCount: 1,
        maxFileSize: '8MB',
        mediaType: KEYS.file,
        minFileCount: 1,
      },
      image: {
        maxFileCount: 3,
        maxFileSize: '4MB',
        mediaType: KEYS.image,
        minFileCount: 1,
      },
      pdf: {
        maxFileCount: 1,
        maxFileSize: '4MB',
        mediaType: KEYS.file,
        minFileCount: 1,
      },
      text: {
        maxFileCount: 1,
        maxFileSize: '64KB',
        mediaType: KEYS.file,
        minFileCount: 1,
      },
      video: {
        maxFileCount: 1,
        maxFileSize: '16MB',
        mediaType: KEYS.video,
        minFileCount: 1,
      },
    }

    Supported file types: 'image' | 'video' | 'audio' | 'pdf' | 'text' | 'blob'

    Disable empty placeholder when no file is uploading.

    • Default: false

    Disable drag and drop file upload functionality.

    • Default: false

    Maximum number of files that can be uploaded at once, if not specified by uploadConfig.

    • Default: 5

    Allow multiple files of the same type to be uploaded.

    • Default: true

API

api.placeholder.addUploadingFile

Tracks a file that is currently being uploaded.

Parameters

Collapse all

    Unique identifier for the placeholder element.

    The file being uploaded.

api.placeholder.getUploadingFile

Gets a file that is currently being uploaded.

Parameters

Collapse all

    Unique identifier for the placeholder element.

Returns

Collapse all

    The uploading file if found, undefined otherwise.

api.placeholder.removeUploadingFile

Removes a file from the uploading tracking state after upload completes or fails.

Parameters

Collapse all

    Unique identifier for the placeholder element to remove.

Transforms

tf.insert.media

Inserts media files into the editor with upload placeholders.

Parameters

Collapse all

    Files to upload. Validates against configured file types and limits.

    Options for the insert nodes transform.

Optionsobject

Collapse all

    Location to insert the media. Defaults to current selection.

    Whether to insert a new block after the media.

    • Default: true

Validates files against configured limits (size, count, type), creates placeholder elements for each file, handles multiple file uploads sequentially, maintains upload history for undo/redo operations, and triggers error handling if validation fails.

Error codes:

enum UploadErrorCode {
  INVALID_FILE_TYPE = 400,
  TOO_MANY_FILES = 402,
  INVALID_FILE_SIZE = 403,
  TOO_LESS_FILES = 405,
  TOO_LARGE = 413,
}

tf.insert.imagePlaceholder

Inserts a placeholder that converts to an image element when completed.

tf.insert.videoPlaceholder

Inserts a placeholder that converts to a video element when completed.

tf.insert.audioPlaceholder

Inserts a placeholder that converts to an audio element when completed.

tf.insert.filePlaceholder

Inserts a placeholder that converts to a file element when completed.

tf.insert.image

Inserts an image element into the editor.

Parameters

Collapse all

    The URL or ArrayBuffer of the image.

    Additional options for inserting the image element.

OptionsInsertImageOptions

Collapse all

    If true, the image will be inserted in the next block.

tf.insert.mediaEmbed

Inserts a media embed element at the current selection.

OptionsInsertMediaEmbedOptions

Collapse all

    The URL of the media embed.

    • Default: ''

    The key of the media embed element.

    • Default: KEYS.mediaEmbed

    Additional options for inserting nodes.

Hooks

useResizable

Handles the resizable properties of a media element.

State

Collapse all

    The alignment of the content within the resizable element.

    The minimum width that the resizable element can be adjusted to.

    The maximum width that the resizable element can be adjusted to.

    Function to set the width of the node when resizing.

    Function to set the width of the resizable element directly.

    The current width of the resizable element (percentage, 'auto', or pixels).

Returnsobject

Collapse all

    React reference to the outermost wrapper div.

    CSS styles for the wrapper div.

    CSS styles for the resizable element.

    Callback function called when the element is resized.

useMediaState

A state hook for a media element.

Parameters

Collapse all

    Array of URL parsers to parse the media element URL.

    • EmbedUrlParser: (url: string) => EmbedUrlData | undefined

Returnsobject

Collapse all

    The alignment of the media element.

    Whether the media element is currently focused.

    Whether the media element is currently selected.

    Whether the editor is in read-only mode.

    The parsed embed data of the media element.

    Whether the media element is a tweet.

    Whether the media element is a video.

    Whether the media element is a YouTube video.

useMediaToolbarButton

A behavior hook for a media toolbar button.

Parameters

Collapse all

    The type of media node to insert.

Returnsobject

Collapse all

    Callback function that inserts the media node and focuses the editor.

useFloatingMediaEditButton

Handles the floating media edit button.

Returnsobject

Collapse all

    Callback function to handle the button click.

useFloatingMediaUrlInput

Handles the URL input field for media elements.

Props

Collapse all

    The default value for the URL input field.

Returnsobject

Collapse all

    Callback function to handle input changes.

    Whether the URL input field should be focused on mount.

    The default value for the URL input field.

useImage

A hook for image elements.

Returnsobject

Collapse all

    The URL of the media element.

    The caption string for the image.

    Whether the image is draggable.

Utilities

parseMediaUrl

Parses a media URL for plugin-specific handling.

Parameters

Collapse all

    The key of the media plugin.

    The URL of the media to be parsed.

parseVideoUrl

Parses a video URL and extracts the video ID and provider-specific embed URL.

Parameters

Collapse all

    The video URL to parse.

ReturnsEmbedUrlData | undefined

    An object containing the video ID and provider if parsing is successful, undefined if URL is invalid or unsupported.

parseTwitterUrl

Parses a Twitter URL and extracts the tweet ID.

Parameters

Collapse all

    The Twitter URL.

Returns

Collapse all

    An object containing the tweet ID and provider if the parsing is successful. Returns undefined if the URL is not valid or does not match any supported video providers.

parseIframeUrl

Parses the URL of an iframe embed.

Parameters

Collapse all

    The URL or embed code of the iframe.

isImageUrl

Checks if a URL is a valid image URL.

Parameters

Collapse all

    The URL to check.

Returnsboolean

    Whether the URL is a valid image URL.

submitFloatingMedia

Submits a floating media element.

Parameters

Collapse all

    The floating media element to be submitted.

    The key of the media plugin.

withImageUpload

Enhances the editor instance with image upload functionality.

Parameters

Collapse all

    The plate plugin.

withImageEmbed

Enhances the editor instance with image-related functionality.

Parameters

Collapse all

    The plate plugin.

Types

TMediaElement

export interface TMediaElement extends TElement {
  url: string;
  id?: string;
  align?: 'center' | 'left' | 'right';
  isUpload?: boolean;
  name?: string;
  placeholderId?: string;
}

TPlaceholderElement

export interface TPlaceholderElement extends TElement {
  mediaType: string;
}

EmbedUrlData

export interface EmbedUrlData {
  url?: string;
  provider?: string;
  id?: string;
  component?: React.FC<EmbedUrlData>;
}