> ## Documentation Index
> Fetch the complete documentation index at: https://vuemail.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Image Upload

> Upload images via paste, drop, or slash command with the image upload plugin.

## Quick start

Import `useEditorImage`, add the returned extension to your editor, register
`imageSlashCommand`, and render `BubbleMenu.ImageDefault` for inline editing.

```vue theme={null}
<script setup lang="ts">
import { EditorProvider } from '@vuemaildev/editor';
import { StarterKit } from '@vuemaildev/editor/extensions';
import { imageSlashCommand, useEditorImage } from '@vuemaildev/editor/plugins';
import {
  BubbleMenu,
  defaultSlashCommands,
  SlashCommand,
} from '@vuemaildev/editor/ui';
import '@vuemaildev/editor/themes/default.css';

async function uploadImage(file: File) {
  const url = await uploadToStorage(file);
  return { url };
}

const imageExtension = useEditorImage({ uploadImage });
const extensions = [StarterKit, imageExtension];
</script>

<template>
  <EditorProvider :extensions="extensions">
    <BubbleMenu.ImageDefault />
    <SlashCommand :items="[...defaultSlashCommands, imageSlashCommand]" />
  </EditorProvider>
</template>
```

`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](https://vuemail.dev/docs/editor/api-reference/image-upload-api.md) for the composable,
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. Its options can also be a ref or a
  getter, like `() => ({ uploadImage: props.uploadImage })`, so it always uploads with the latest
  `uploadImage`.
* 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:

```vue theme={null}
<script setup lang="ts">
import { EmailEditor } from '@vuemaildev/editor';

async function uploadImage(file: File) {
  return { url: await uploadToStorage(file) };
}
</script>

<template>
  <EmailEditor :on-upload-image="uploadImage" />
</template>
```

When `on-upload-image` 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
`:hide-when-active-nodes="['image']"` so the text menu steps aside when an image
is focused.

```vue theme={null}
<EditorProvider :extensions="[StarterKit, imageExtension]">
  <BubbleMenu :hide-when-active-nodes="['image']" />
  <BubbleMenu.ImageDefault />
</EditorProvider>
```

To expose image uploads from `/`, add `imageSlashCommand` to the slash command
items:

```vue theme={null}
<SlashCommand :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:

```ts theme={null}
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:

```ts theme={null}
import { toast } from 'vue-sonner';

async function uploadImage(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:

> **[Image Upload](https://vuemail.dev/editor/image-upload)**
>
> Paste, drop, and slash-command image upload with a stubbed uploader.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.