'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],
},
},
}),
];
ImageElement
: Renders image elements.VideoElement
: Renders video elements.AudioElement
: Renders audio elements.FileElement
: Renders file elements.MediaEmbedElement
: Renders embedded media.PlaceholderElement
: Renders upload placeholders.MediaUploadToast
: Shows upload progress notifications.MediaPreviewDialog
: Provides media preview functionality.
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
:
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.
- 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
- Default:
false
- Default:
false
Function to upload image to a server. Receives:
Disables file upload on data insertion.
Disables URL embed on data insertion.
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.
- Default:
false
- Default:
false
- Default:
5
- Default:
true
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.
Disable drag and drop file upload functionality.
Maximum number of files that can be uploaded at once, if not specified by uploadConfig
.
Allow multiple files of the same type to be uploaded.
API
api.placeholder.addUploadingFile
Tracks a file that is currently being uploaded.
api.placeholder.getUploadingFile
Gets a file that is currently being uploaded.
api.placeholder.removeUploadingFile
Removes a file from the uploading tracking state after upload completes or fails.
Transforms
tf.insert.media
Inserts media files into the editor with upload placeholders.
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.
tf.insert.mediaEmbed
Inserts a media embed element at the current selection.
Hooks
useResizable
Handles the resizable properties of a media element.
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).
useMediaState
A state hook for a media element.
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.
useFloatingMediaEditButton
Handles the floating media edit button.
useFloatingMediaUrlInput
Handles the URL input field for media elements.
useImage
A hook for image elements.
Utilities
parseMediaUrl
Parses a media URL for plugin-specific handling.
parseVideoUrl
Parses a video URL and extracts the video ID and provider-specific embed URL.
parseTwitterUrl
Parses a Twitter URL and extracts the tweet ID.
parseIframeUrl
Parses the URL of an iframe embed.
isImageUrl
Checks if a URL is a valid image URL.
submitFloatingMedia
Submits a floating media element.
withImageUpload
Enhances the editor instance with image upload functionality.
withImageEmbed
Enhances the editor instance with image-related functionality.
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>;
}
On This Page
FeaturesMedia SupportMedia FeaturesUploadKit UsageInstallationAdd KitAdd API RoutesEnvironment SetupManual UsageInstallationAdd PluginsConfigure PluginsCaption SupportCustom Upload ImplementationAdd Toolbar ButtonInsert Toolbar ButtonPlate PlusPluginsImagePluginVideoPluginAudioPluginFilePluginMediaEmbedPluginPlaceholderPluginAPIapi.placeholder.addUploadingFileapi.placeholder.getUploadingFileapi.placeholder.removeUploadingFileTransformstf.insert.mediatf.insert.imagePlaceholdertf.insert.videoPlaceholdertf.insert.audioPlaceholdertf.insert.filePlaceholdertf.insert.imagetf.insert.mediaEmbedHooksuseResizableuseMediaStateuseMediaToolbarButtonuseFloatingMediaEditButtonuseFloatingMediaUrlInputuseImageUtilitiesparseMediaUrlparseVideoUrlparseTwitterUrlparseIframeUrlisImageUrlsubmitFloatingMediawithImageUploadwithImageEmbedTypesTMediaElementTPlaceholderElementEmbedUrlData