āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/udecode/plate/(plugins)/(elements)/media ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
title: Media docs:
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 the kit to your plugins:
import { createPlateEditor } from 'platejs/react';
import { MediaKit } from '@/components/editor/plugins/media-kit';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
...MediaKit,
],
});
Get your secret key from UploadThing and add it to .env:
UPLOADTHING_TOKEN=xxx
</Steps>
npm install @platejs/media
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 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.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],
},
},
}),
],
});
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
}
You can add MediaToolbarButton to your Toolbar to upload and insert media.
You can add these items to the Insert Toolbar Button to insert media elements:
{
icon: <ImageIcon />,
label: 'Image',
value: KEYS.img,
}
</Steps>
ImagePluginPlugin for void image elements.
<API name="ImagePlugin"> <APIOptions type="ImagePluginOptions"> <APIItem name="uploadImage" type="(dataUrl: string | ArrayBuffer) => Promise<string | ArrayBuffer> | string | ArrayBuffer" optional> 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 </APIItem> <APIItem name="disableUploadInsert" type="boolean" optional> Disables file upload on data insertion. - **Default:** `false` </APIItem> <APIItem name="disableEmbedInsert" type="boolean" optional> Disables URL embed on data insertion. - **Default:** `false` </APIItem> <APIItem name="isUrl" type="function" optional> A function to check whether a text string is a URL. </APIItem> <APIItem name="transformUrl" type="function" optional> A function to transform the URL. </APIItem> </APIOptions> </API>VideoPluginPlugin for void video elements. Extends MediaPluginOptions.
AudioPluginPlugin for void audio elements. Extends MediaPluginOptions.
FilePluginPlugin for void file elements. Extends MediaPluginOptions.
MediaEmbedPluginPlugin for void media embed elements. Extends MediaPluginOptions.
PlaceholderPluginPlugin for managing media placeholders during upload. Handles file uploads, drag & drop, and clipboard paste events.
<API name="PlaceholderPlugin"> <APIOptions type="object"> <APIItem name="uploadConfig" type="Partial<Record<AllowedFileType, MediaItemConfig>>" optional> Configuration for different file types. Default configuration: ```ts { 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'` <APISubList> <APISubListItem parent="uploadConfig" name="mediaType" type="MediaKeys"> The media plugin keys that this config is for: `'audio' | 'file' | 'image' | 'video'` </APISubListItem> <APISubListItem parent="uploadConfig" name="maxFileCount" type="number" optional> The maximum number of files of this type that can be uploaded. </APISubListItem> <APISubListItem parent="uploadConfig" name="maxFileSize" type="FileSize" optional> The maximum file size for a file of this type. Format: `${1|2|4|8|16|32|64|128|256|512|1024}${B|KB|MB|GB}` </APISubListItem> <APISubListItem parent="uploadConfig" name="minFileCount" type="number" optional> The minimum number of files of this type that must be uploaded. </APISubListItem> </APISubList> </APIItem> <APIItem name="disableEmptyPlaceholder" type="boolean" optional> Disable empty placeholder when no file is uploading. - **Default:** `false` </APIItem> <APIItem name="disableFileDrop" type="boolean" optional> Disable drag and drop file upload functionality. - **Default:** `false` </APIItem> <APIItem name="maxFileCount" type="number" optional> Maximum number of files that can be uploaded at once, if not specified by `uploadConfig`. - **Default:** `5` </APIItem> <APIItem name="multiple" type="boolean" optional> Allow multiple files of the same type to be uploaded. - **Default:** `true` </APIItem> </APIOptions> </API>api.placeholder.addUploadingFileTracks a file that is currently being uploaded.
<API name="addUploadingFile"> <APIParameters> <APIItem name="id" type="string"> Unique identifier for the placeholder element. </APIItem> <APIItem name="file" type="File"> The file being uploaded. </APIItem> </APIParameters> </API>api.placeholder.getUploadingFileGets a file that is currently being uploaded.
<API name="getUploadingFile"> <APIParameters> <APIItem name="id" type="string"> Unique identifier for the placeholder element. </APIItem> </APIParameters> <APIReturns> <APIItem type="File | undefined"> The uploading file if found, undefined otherwise. </APIItem> </APIReturns> </API>api.placeholder.removeUploadingFileRemoves a file from the uploading tracking state after upload completes or fails.
<API name="removeUploadingFile"> <APIParameters> <APIItem name="id" type="string"> Unique identifier for the placeholder element to remove. </APIItem> </APIParameters> </API>tf.insert.mediaInserts media files into the editor with upload placeholders.
<API name="insertMedia"> <APIParameters> <APIItem name="files" type="FileList"> Files to upload. Validates against configured file types and limits. </APIItem> <APIItem name="options" type="object" optional> Options for the insert nodes transform. </APIItem> </APIParameters> <APIOptions type="object"> <APIItem name="at" type="Path" optional> Location to insert the media. Defaults to current selection. </APIItem> <APIItem name="nextBlock" type="boolean" optional> Whether to insert a new block after the media. - **Default:** `true` </APIItem> </APIOptions> </API>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.imagePlaceholderInserts a placeholder that converts to an image element when completed.
tf.insert.videoPlaceholderInserts a placeholder that converts to a video element when completed.
tf.insert.audioPlaceholderInserts a placeholder that converts to an audio element when completed.
tf.insert.filePlaceholderInserts a placeholder that converts to a file element when completed.
tf.insert.imageInserts an image element into the editor.
<API name="insertImage"> <APIParameters> <APIItem name="url" type="string | ArrayBuffer"> The URL or ArrayBuffer of the image. </APIItem> <APIItem name="options" type="InsertNodesOptions" optional> Additional options for inserting the image element. </APIItem> </APIParameters> <APIOptions type="InsertImageOptions"> <APIItem name="nextBlock" type="boolean" optional> If true, the image will be inserted in the next block. </APIItem> </APIOptions> </API>tf.insert.mediaEmbedInserts a media embed element at the current selection.
<API name="insertMediaEmbed"> <APIOptions type="InsertMediaEmbedOptions"> <APIItem name="url" type="string" optional> The URL of the media embed. - **Default:** `''` </APIItem> <APIItem name="key" type="string" optional> The key of the media embed element. - **Default:** `KEYS.mediaEmbed` </APIItem> <APIItem name="insertNodesOptions" type="InsertNodesOptions" optional> Additional options for inserting nodes. </APIItem> </APIOptions> </API>useResizableHandles the resizable properties of a media element.
<API name="useResizable"> <APIState> <APIItem name="align" type="'left' | 'center' | 'right'"> The alignment of the content within the resizable element. </APIItem> <APIItem name="minWidth" type="ResizeLength"> The minimum width that the resizable element can be adjusted to. </APIItem> <APIItem name="maxWidth" type="ResizeLength"> The maximum width that the resizable element can be adjusted to. </APIItem> <APIItem name="setNodeWidth" type="(width: number | string) => void"> Function to set the width of the node when resizing. </APIItem> <APIItem name="setWidth" type="(width: number | string) => void"> Function to set the width of the resizable element directly. </APIItem> <APIItem name="width" type="Property.Width<string | number> | undefined"> The current width of the resizable element (percentage, 'auto', or pixels). </APIItem> </APIState> <APIReturns type="object"> <APIItem name="wrapperRef" type="React.RefObject<HTMLDivElement>"> React reference to the outermost wrapper div. </APIItem> <APIItem name="wrapperProps.style" type="CSSProperties"> CSS styles for the wrapper div. </APIItem> <APIItem name="props.style" type="CSSProperties"> CSS styles for the resizable element. </APIItem> <APIItem name="context.onResize" type="() => void"> Callback function called when the element is resized. </APIItem> </APIReturns> </API>useMediaStateA state hook for a media element.
<API name="useMediaState"> <APIParameters> <APIItem name="options.urlParsers" type="EmbedUrlParser[]" optional> Array of URL parsers to parse the media element URL.- **`EmbedUrlParser`:** `(url: string) => EmbedUrlData | undefined`
</APIItem>
</APIParameters>
<APIReturns type="object">
<APIItem name="align" type="string">
The alignment of the media element.
</APIItem>
<APIItem name="focus" type="boolean">
Whether the media element is currently focused.
</APIItem>
<APIItem name="selected" type="boolean">
Whether the media element is currently selected.
</APIItem>
<APIItem name="readOnly" type="boolean">
Whether the editor is in read-only mode.
</APIItem>
<APIItem name="embed" type="EmbedUrlData">
The parsed embed data of the media element.
</APIItem>
<APIItem name="isTweet" type="boolean">
Whether the media element is a tweet.
</APIItem>
<APIItem name="isVideo" type="boolean">
Whether the media element is a video.
</APIItem>
<APIItem name="isYoutube" type="boolean">
Whether the media element is a YouTube video.
</APIItem>
</APIReturns>
</API>
useMediaToolbarButtonA behavior hook for a media toolbar button.
<API name="useMediaToolbarButton"> <APIParameters> <APIItem name="options.nodeType" type="string" optional> The type of media node to insert. </APIItem> </APIParameters> <APIReturns type="object"> <APIItem name="props.onClick" type="() => void"> Callback function that inserts the media node and focuses the editor. </APIItem> </APIReturns> </API>useFloatingMediaEditButtonHandles the floating media edit button.
<API name="useFloatingMediaEditButton"> <APIReturns type="object"> <APIItem name="props.onClick" type="() => void"> Callback function to handle the button click. </APIItem> </APIReturns> </API>useFloatingMediaUrlInputHandles the URL input field for media elements.
<API name="useFloatingMediaUrlInput"> <APIProps> <APIItem name="defaultValue" type="string"> The default value for the URL input field. </APIItem> </APIProps> <APIReturns type="object"> <APIItem name="props.onChange" type="() => void"> Callback function to handle input changes. </APIItem> <APIItem name="props.autoFocus" type="boolean"> Whether the URL input field should be focused on mount. </APIItem> <APIItem name="props.defaultValue" type="string"> The default value for the URL input field. </APIItem> </APIReturns> </API>useImageA hook for image elements.
<API name="useImage"> <APIReturns type="object"> <APIItem name="props.src" type="string"> The URL of the media element. </APIItem> <APIItem name="props.alt" type="string"> The caption string for the image. </APIItem> <APIItem name="props.draggable" type="boolean"> Whether the image is draggable. </APIItem> </APIReturns> </API>parseMediaUrlParses a media URL for plugin-specific handling.
<API name="parseMediaUrl"> <APIParameters> <APIItem name="options.pluginKey" type="string"> The key of the media plugin. </APIItem> <APIItem name="options.url" type="string" optional> The URL of the media to be parsed. </APIItem> </APIParameters> </API>parseVideoUrlParses a video URL and extracts the video ID and provider-specific embed URL.
<API name="parseVideoUrl"> <APIParameters> <APIItem name="url" type="string"> The video URL to parse. </APIItem> </APIParameters> <APIReturns type="EmbedUrlData | undefined"> An object containing the video ID and provider if parsing is successful, undefined if URL is invalid or unsupported. </APIReturns> </API>parseTwitterUrlParses a Twitter URL and extracts the tweet ID.
<API name="parseTwitterUrl"> <APIParameters> <APIItem name="url" type="string"> The Twitter URL. </APIItem> </APIParameters> <APIReturns> <APIItem type="EmbedUrlData | undefined"> 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. </APIItem> </APIReturns> </API>parseIframeUrlParses the URL of an iframe embed.
<API name="parseIframeUrl"> <APIParameters> <APIItem name="url" type="string"> The URL or embed code of the iframe. </APIItem> </APIParameters> </API>isImageUrlChecks if a URL is a valid image URL.
<API name="isImageUrl"> <APIParameters> <APIItem name="url" type="string"> The URL to check. </APIItem> </APIParameters> <APIReturns type="boolean"> Whether the URL is a valid image URL. </APIReturns> </API>submitFloatingMediaSubmits a floating media element.
<API name="submitFloatingMedia"> <APIParameters> <APIItem name="options.element" type="TMediaElement"> The floating media element to be submitted. </APIItem> <APIItem name="options.pluginKey" type="string" optional> The key of the media plugin. </APIItem> </APIParameters> </API>withImageUploadEnhances the editor instance with image upload functionality.
<API name="withImageUpload"> <APIParameters> <APIItem name="plugin" type="PlatePlugin"> The plate plugin. </APIItem> </APIParameters> </API>withImageEmbedEnhances the editor instance with image-related functionality.
<API name="withImageEmbed"> <APIParameters> <APIItem name="plugin" type="PlatePlugin"> The plate plugin. </APIItem> </APIParameters> </API>TMediaElementexport interface TMediaElement extends TElement {
url: string;
id?: string;
align?: 'center' | 'left' | 'right';
isUpload?: boolean;
name?: string;
placeholderId?: string;
}
TPlaceholderElementexport interface TPlaceholderElement extends TElement {
mediaType: string;
}
EmbedUrlDataexport interface EmbedUrlData {
url?: string;
provider?: string;
id?: string;
component?: React.FC<EmbedUrlData>;
}
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā