Skip to content

Image

Insert images from a URL or an application-provided upload service.

Setup

Start with the packages in Getting Started. This complete example registers the feature and renders its UI. In an existing editor, merge the imports and extension entries into your setup, and place the controls inside your existing RichTextProvider.

tsx
'use client';

import { EditorContent, useEditor } from '@tiptap/react';
import { Document } from '@tiptap/extension-document';
import { Paragraph } from '@tiptap/extension-paragraph';
import { Text } from '@tiptap/extension-text';
import { RichTextProvider } from 'reactjs-tiptap-editor';
import { Image, RichTextImage } from 'reactjs-tiptap-editor/image';
import { RichTextBubbleImage } from 'reactjs-tiptap-editor/bubble/media';
import 'reactjs-tiptap-editor/style.css';

const extensions = [Document, Paragraph, Text, Image.configure({ resourceImage: 'link' })];

export default function ImageExample() {
  const editor = useEditor({
    extensions,
    content: '<p>Try this feature here.</p>',
    immediatelyRender: false,
  });

  if (!editor) return null;

  return (
    <RichTextProvider editor={editor}>
      <RichTextImage />
      <RichTextBubbleImage />
      <EditorContent editor={editor} />
    </RichTextProvider>
  );
}

How to use

The example starts in URL-only mode so it works without a backend. To enable local files, configure resourceImage: "both" or "upload" and provide upload: (file: File) => Promise<string>. Return a durable image URL; temporary blob: URLs will not survive a reload. Mount RichTextBubbleImage for image editing controls.

Inline and block images

Image.configure() now registers both image node types used by the editor:

  • image: the legacy inline node, kept for existing ProseMirror JSON compatibility.
  • imageBlock: a block-level node used when defaultInline is false or when setImageInline({ inline: false }) / setImageBlock() inserts an image.

Existing HTML is still accepted:

  • <div class="image"><img ... /></div> is parsed as imageBlock.
  • <p><div class="image"><img inline="false" ... /></div></p> produced by versions before 1.0.26 is parsed as imageBlock without the empty paragraphs the browser adds around it, so the document no longer grows on each save/load.
  • <span class="image"><img inline="true" ... /></span> is parsed as the inline image node.
  • Old JSON with type: "image" continues to load. If you want to migrate stored JSON, use migrateImageJSONToImageBlock(json) before saving the migrated document.

Image Gif

To search a GIF provider and insert a result, use the separate ImageGif extension.

Props

ts
interface IImageOptions extends GeneralOptions<IImageOptions> {
  /** Function for uploading files */
  upload?: (file: File) => Promise<string>;

  HTMLAttributes?: any;

  multiple?: boolean;
  acceptMimes?: string[];
  maxSize?: number;

  /** The source URL of the image */
  resourceImage: 'upload' | 'link' | 'both';
  defaultInline?: boolean;

  enableAlt?: boolean;

  onError?: (error: { type: 'size' | 'type' | 'upload'; message: string; file?: File }) => void;
}
PropertyTypeDescriptionRequiredDefault
upload(file: File) => Promise<string>Custom image upload function that receives a File and returns a Promise with the image URL, suitable for uploading to cloud or local servers.NoNone
HTMLAttributesanyHTML attributes passed to the <img> tag, such as className, style, alt, etc.NoNone
multiplebooleanWhether to allow selecting and uploading multiple images simultaneously.Notrue
acceptMimesstring[]List of allowed image MIME types or file extension restrictions, such as ['image/jpeg', 'image/png'], ['image/*'], or ['.png', '.jpg'], etc. Supports MIME type wildcards and precise file extension restrictions.NoCommon image types ['image/jpeg', 'image/gif', 'image/png', 'image/jpg']
maxSizenumberMaximum size limit for a single image (in bytes), triggers onError when exceeded.No5MB
resourceImage'upload' | 'link' | 'both'Image source method: - 'upload': Upload only - 'link': Link only - 'both': Both supportedNoboth
defaultInlinebooleanWhether to insert images as inline elements by default.Nofalse
enableAltbooleanWhether to enable alt text editing for images.Notrue
onError(error: { type: 'size' | 'type' | 'upload'; message: string; file?: File }) => voidCallback function for upload or validation failures. Contains error type (size, type, upload), error message, and corresponding file.NoNone

resourceImage Type Description

  • 'upload': Users can only select local files for upload
  • 'link': Users can only input image URLs
  • 'both': Supports both upload and URL methods

acceptMimes Usage Instructions

Supports three format types:

  1. MIME types: such as ['image/jpeg', 'image/png']
  2. Wildcard types: such as ['image/*'], matches all image MIME types
  3. Extension types: such as:
ts
[
  '.png',
  '.jpg',
  '.jpeg',
  '.webp',
  '.gif',
  '.svg',
  '.svgz',
  '.xbm',
  '.tiff',
  '.ico',
  '.jfif',
  '.heic',
  '.heif',
  '.avif',
  '.bmp',
  '.apng',
  '.pjpeg',
];

onError Example

  • Customize error handling logic to unify system prompts.
  • We recommend using the message field, which has built-in dynamic prompts and i18n internationalization support.
ts
onError: ({ type, message, file }) => {
  switch (type) {
    case 'size':
      console.warn(`File size exceeds limit: ${file?.name}`);
      break;
    case 'type':
      console.warn(`Unsupported file type: ${file?.type}`);
      break;
    case 'upload':
      console.error(`Upload failed: ${message}`);
      break;
  }
};

Upload a local image

Replace the URL-only configuration in the setup example with this one. Implement /api/images in your application so it accepts a multipart file and returns a JSON object with a url string:

ts
Image.configure({
  resourceImage: 'both',
  upload: async (file: File): Promise<string> => {
    const body = new FormData();
    body.append('file', file);
    const response = await fetch('/api/images', { method: 'POST', body });
    if (!response.ok) throw new Error('Image upload failed');
    const data = await response.json();
    if (typeof data.url !== 'string' || !data.url) {
      throw new Error('Upload response must contain an image URL');
    }
    return data.url;
  },
});

The library handles the editor UI; your application supplies storage and the upload endpoint. Enforce accepted file types and size limits on that endpoint as well as in the client configuration.

Source

SourceDocs

Contributors

Changelog

v1.0.30 on 7/28/2026
b5f5a - fix: preserve percentage image widths after reload
v1.0.27 on 7/9/2026
b9854 - feat(image): add block image node
v1.0.26 on 6/22/2026
67f1c - fix: stabilize image HTML round trip
v1.0.19 on 3/19/2026
546f8 - fix: image extension default option
v1.0.0 on 12/7/2025
c67ed - fix: conflict
51948 - feat: refactor code, dynamic bubble, toolbar, fix error
v0.3.30 on 10/24/2025
07904 - fix(image): avoid pasting html (twice (?)) when clipboard contains file/image
70276 - fix(image): avoid pasting html (twice (?)) when clipboard contains file/image
v0.3.19 on 8/27/2025
1ee18 - feat: add enableAlt prop to control alt text editing
v0.3.16 on 7/29/2025
cbe0c - fix(image): improve crop modal and upload handling
v0.3.15 on 7/27/2025
6eaab - fix(image): enable text wrapping for inline images aligned left or right

Made with ❤️