Files
Gabriel Miranda 07bf7ad569 chore(docs): image upload api reference, some other updates (#3354)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-17 09:27:36 -03:00

162 lines
4.7 KiB
Plaintext

---
title: "Image Upload"
sidebarTitle: "Image Upload"
description: "Upload images via paste, drop, or slash command with the image upload plugin."
icon: "image"
---
## Quick start
Import `useEditorImage`, add the returned extension to your editor, register
`imageSlashCommand`, and render `BubbleMenu.ImageDefault` for inline editing.
```tsx
import { StarterKit } from '@react-email/editor/extensions';
import { imageSlashCommand, useEditorImage } from '@react-email/editor/plugins';
import {
BubbleMenu,
defaultSlashCommands,
SlashCommand,
} from '@react-email/editor/ui';
import { EditorProvider } from '@tiptap/react';
import { useCallback } from 'react';
import '@react-email/editor/themes/default.css';
export function MyEditor() {
const uploadImage = useCallback(async (file: File) => {
const url = await uploadToStorage(file);
return { url };
}, []);
const imageExtension = useEditorImage({ uploadImage });
return (
<EditorProvider extensions={[StarterKit, imageExtension]}>
<BubbleMenu.ImageDefault />
<SlashCommand.Root items={[...defaultSlashCommands, imageSlashCommand]} />
</EditorProvider>
);
}
```
`uploadImage` receives a `File` and must resolve with `{ url }`. The returned
URL is written to the image node once the promise resolves.
See [Image Upload API](/editor/api-reference/image-upload-api) for the hook,
types, slash command, and editor commands exposed by this plugin.
## How image upload works
The image upload plugin combines an `image` node extension with a ProseMirror
file-handler plugin:
- `useEditorImage({ uploadImage })` creates the extension and keeps the latest
upload handler wired in.
- Paste and drop events are intercepted when the first file is an image.
- `editor.commands.uploadImage()` opens a file picker for `image/*`.
- All entry points run the same upload flow: insert a temporary blob URL,
await `uploadImage(file)`, then swap the node to the final hosted URL.
If `uploadImage` throws, the temporary image node is removed and the error is
logged, which keeps failed uploads from lingering in the document.
## Using `EmailEditor`
`EmailEditor` wraps the same extension behind a single prop. Use this when you
don't need direct access to the extension or slash command list:
```tsx
import { EmailEditor } from '@react-email/editor';
export function MyEditor() {
return (
<EmailEditor
onUploadImage={async (file) => ({ url: await uploadToStorage(file) })}
/>
);
}
```
When `onUploadImage` is present, `EmailEditor` enables the same upload flow for
paste, drop, and image insertion.
## Bubble menus and slash commands
When pairing `BubbleMenu.ImageDefault` with the default `BubbleMenu`, pass
`hideWhenActiveNodes={['image']}` so the text menu steps aside when an image
is focused.
```tsx
<EditorProvider extensions={[StarterKit, imageExtension]}>
<BubbleMenu hideWhenActiveNodes={['image']} />
<BubbleMenu.ImageDefault />
</EditorProvider>
```
To expose image uploads from `/`, add `imageSlashCommand` to the slash command
items:
```tsx
<SlashCommand.Root items={[...defaultSlashCommands, imageSlashCommand]} />
```
## Upload triggers
Once the extension is registered, three input paths upload automatically:
- **Paste** — paste an image from the clipboard
- **Drop** — drag an image file onto the editor
- **Slash command** — type `/` and pick **Image** (from `imageSlashCommand`)
All three run the same flow: a temporary blob URL renders while `uploadImage`
runs, and the node swaps to the resolved URL on success.
## Programmatic insertion
The extension adds two editor commands:
```tsx
editor.commands.uploadImage();
editor.commands.setImage({
src: 'https://example.com/hero.png',
alt: 'Hero image',
alignment: 'center',
});
```
`uploadImage()` opens a file picker and runs the upload flow. `setImage()`
inserts an image node directly, which is useful when you already have a URL.
Available `setImage` attributes: `src`, `alt`, `width`, `height`,
`alignment` (`'left' | 'center' | 'right'`), and `href` (wraps the image in a
link on export).
## Error handling
If `uploadImage` throws, the plugin removes the temporary node for you and
logs the failure via `console.error`. Handle the error inside your own
function when you need custom UI or telemetry:
```tsx
const uploadImage = useCallback(async (file: File) => {
try {
const url = await uploadToStorage(file);
return { url };
} catch (error) {
toast.error(`Couldn't upload ${file.name}`);
throw error;
}
}, []);
```
## Examples
See image upload in action with a runnable example:
<CardGroup cols={2}>
<Card title="Image Upload" icon="code" href="https://react.email/editor/examples/image-upload">
Paste, drop, and slash-command image upload with a stubbed uploader.
</Card>
</CardGroup>