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.
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
Plugins
ImagePlugin
Plugin for void image elements.
uploadImage optional (dataUrl: string | ArrayBuffer) => Promise<string | ArrayBuffer> | string | ArrayBuffer
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
- Data URL (string) from
disableUploadInsert optional boolean
Disables file upload on data insertion.
- Default:
false
- Default:
disableEmbedInsert optional boolean
Disables URL embed on data insertion.
- Default:
false
- Default:
isUrl optional function
A function to check whether a text string is a URL.
transformUrl optional function
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.
uploadConfig optional Partial<Record<AllowedFileType, MediaItemConfig>>
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'
disableEmptyPlaceholder optional boolean
Disable empty placeholder when no file is uploading.
- Default:
false
- Default:
disableFileDrop optional boolean
Disable drag and drop file upload functionality.
- Default:
false
- Default:
maxFileCount optional number
Maximum number of files that can be uploaded at once, if not specified by
uploadConfig
.- Default:
5
- Default:
multiple optional boolean
Allow multiple files of the same type to be uploaded.
- Default:
true
- Default:
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.
align 'left' | 'center' | 'right'
The alignment of the content within the resizable element.
minWidth ResizeLength
The minimum width that the resizable element can be adjusted to.
maxWidth ResizeLength
The maximum width that the resizable element can be adjusted to.
setNodeWidth (width: number | string) => void
Function to set the width of the node when resizing.
setWidth (width: number | string) => void
Function to set the width of the resizable element directly.
width Property.Width<string | number> | undefined
The current width of the resizable element (percentage, 'auto', or pixels).
useMediaState
A state hook for a media element.
align string
The alignment of the media element.
focus boolean
Whether the media element is currently focused.
selected boolean
Whether the media element is currently selected.
readOnly boolean
Whether the editor is in read-only mode.
embed EmbedUrlData
The parsed embed data of the media element.
isTweet boolean
Whether the media element is a tweet.
isVideo boolean
Whether the media element is a video.
isYoutube boolean
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 PlusPluginsImagePlugin
VideoPlugin
AudioPlugin
FilePlugin
MediaEmbedPlugin
PlaceholderPlugin
APIapi.placeholder.addUploadingFile
api.placeholder.getUploadingFile
api.placeholder.removeUploadingFile
Transformstf.insert.media
tf.insert.imagePlaceholder
tf.insert.videoPlaceholder
tf.insert.audioPlaceholder
tf.insert.filePlaceholder
tf.insert.image
tf.insert.mediaEmbed
HooksuseResizable
useMediaState
useMediaToolbarButton
useFloatingMediaEditButton
useFloatingMediaUrlInput
useImage
UtilitiesparseMediaUrl
parseVideoUrl
parseTwitterUrl
parseIframeUrl
isImageUrl
submitFloatingMedia
withImageUpload
withImageEmbed
TypesTMediaElement
TPlaceholderElement
EmbedUrlData