Skip to main content

Overview

The Image plugin provides a complete solution for adding images to your content. It supports file uploads, URL insertion, image resizing, and various display options.

Installation

npm install @yoopta/image

Basic Usage

import { Image } from '@yoopta/image';

const plugins = [
  Image,
  // ... other plugins
];

<YooptaEditor editor={editor} plugins={plugins} />;
Required ConfigurationYou must configure the upload and delete options. Without these options, you’ll see an error when trying to use the Image plugin.
// Required when using themes
const plugins = [
  Image.extend({
    options: {
      upload: async (file) => {
        // Your upload logic here
        return { id: '...', src: '...' };
      },
      delete: async (src) => {
        // Your delete logic here
      },
    },
  }),
];
See the Configuration section below for detailed examples.

Features

  • File Upload: Upload images from device
  • URL Insertion: Insert images via URL
  • Custom Upload Functions: Direct uploads to Cloudinary, S3, Firebase, etc.
  • Image Resizing: Resize images while maintaining aspect ratio
  • Size Limits: Set maximum width and height
  • Object Fit: Control how images fit their container
  • Image Deletion: Optional deletion handling with custom functions
  • Responsive: Automatically adapts to screen size

Configuration

The Image plugin supports two approaches for upload and delete operations:
  1. Endpoint-based: Configure an API endpoint and the plugin handles the request
  2. Custom function: Provide your own async function for complete control (useful for direct uploads to Cloudinary, S3, Firebase, etc.)

Endpoint-based Upload (Backend API)

import { Image } from '@yoopta/image';

const plugins = [
  Image.extend({
    options: {
      upload: {
        endpoint: '/api/upload',
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
        onSuccess: (result) => {
          console.log('Upload successful:', result);
        },
        onError: (error) => {
          console.error('Upload failed:', error);
        },
      },
      delete: {
        endpoint: '/api/delete',
        method: 'DELETE',
      },
      maxSizes: {
        maxWidth: 800,
        maxHeight: 600,
      },
    },
  }),
];

Custom Upload Function (Direct to Cloud)

For direct uploads to third-party services like Cloudinary, AWS S3, or Firebase Storage, you can provide a custom async function:
import { Image } from '@yoopta/image';

const plugins = [
  Image.extend({
    options: {
      // Custom upload function
      upload: async (file, onProgress) => {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('upload_preset', 'your_preset');

        const response = await fetch(
          'https://api.cloudinary.com/v1_1/your_cloud/image/upload',
          { method: 'POST', body: formData }
        );
        const data = await response.json();

        // Return ImageElementProps
        return {
          id: data.public_id,
          src: data.secure_url,
          sizes: {
            width: data.width,
            height: data.height,
          },
        };
      },
      // Custom delete function
      delete: async (src) => {
        // Extract public_id and delete from your service
        const publicId = extractPublicId(src);
        await fetch(`/api/cloudinary-delete/${publicId}`, { method: 'DELETE' });
      },
      maxSizes: {
        maxWidth: 800,
        maxHeight: 600,
      },
    },
  }),
];

Custom Fetch to Your API

You can also use a custom function to call your own API with fetch:
import { Image } from '@yoopta/image';

const plugins = [
  Image.extend({
    options: {
      upload: async (file, onProgress) => {
        const formData = new FormData();
        formData.append('file', file);

        const response = await fetch('/api/image-kit-upload', {
          method: 'POST',
          body: formData,
        });

        if (!response.ok) {
          throw new Error('Upload failed');
        }

        const data = await response.json();

        return {
          id: data.fileId,
          src: data.url,
          sizes: {
            width: data.width,
            height: data.height,
          },
        };
      },
      delete: async (src) => {
        await fetch('/api/image-kit-delete', {
          method: 'DELETE',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ src }),
        });
      },
    },
  }),
];

Options

upload

Can be either an object (endpoint-based) or a function (custom upload):
upload
object

delete

Can be either an object (endpoint-based) or a function (custom delete):
delete
object
maxSizes
object
optimizations
object

Element Props

src
string | null
Image source URL
alt
string | null
Alternative text for accessibility
srcSet
string | null
Responsive image source set
fit
'contain' | 'cover' | 'fill' | null
How the image should fit its container
sizes
object
Image dimensions: { width: number, height: number }
bgColor
string | null
Background color for image container

Commands

import { ImageCommands } from '@yoopta/image';

// Update image source
ImageCommands.updateImage(editor, blockId, {
  src: 'https://example.com/image.jpg',
  alt: 'Description',
});

// Delete image
ImageCommands.deleteImage(editor, blockId);

Upload Response Format

Your upload endpoint should return:
{
  id: string;
  url: string;
  width?: number;
  height?: number;
  size?: number;
  format?: string;
}

Custom Rendering

import { Image } from '@yoopta/image';

const CustomImage = Image.extend({
  elements: {
    image: {
      render: (props) => {
        const { src, alt, sizes, fit } = props.element.props;

        return (
          <figure {...props.attributes} contentEditable={false}>
            <img
              src={src}
              alt={alt}
              width={sizes.width}
              height={sizes.height}
              style={{ objectFit: fit }}
              loading="lazy"
            />
            {alt && <figcaption>{alt}</figcaption>}
            {props.children}
          </figure>
        );
      },
    },
  },
});

Parsers

HTML Deserialization

The plugin automatically deserializes <img> tags:
<img src="image.jpg" alt="Description" width="650" height="500" />

HTML Serialization

<div style="display: flex; justify-content: center;">
  <img src="image.jpg" alt="Description" width="650" height="500" />
</div>

Markdown Serialization

![Description](https://example.com/image.jpg)

Use Cases

Blog Posts

Feature images and inline illustrations

Documentation

Screenshots and diagrams

Portfolios

Project images and galleries

Product Catalogs

Product images with descriptions

Best Practices

Compress and resize images before uploading to reduce load times
Provide descriptive alt text for accessibility
Configure appropriate maxWidth and maxHeight for your design
Enable lazy loading for better performance
Handle upload errors gracefully with user feedback

Hooks

useImageUpload

Supports both endpoint-based and custom function approaches:
import { useImageUpload } from '@yoopta/image';

const { upload, loading, progress, error } = useImageUpload({
  endpoint: '/api/upload',
  onSuccess: (result) => {
    console.log('Uploaded:', result.url);
  },
});

// Upload a file
const handleUpload = async (file: File) => {
  const result = await upload(file);
};

useImageDelete

Supports both endpoint-based and custom function approaches:
import { useImageDelete } from '@yoopta/image';

const { deleteImage, loading, error } = useImageDelete({
  endpoint: '/api/delete',
});

Troubleshooting

This error occurs when you’re using a theme but haven’t configured the upload option.Solution: Add an upload configuration to your Image plugin:
Image.extend({
  options: {
    upload: async (file) => {
      // Your upload logic
      return { id: '...', src: '...' };
    },
  },
})
This error occurs when you’re using a theme but haven’t configured the delete option.Solution: Add a delete configuration to your Image plugin:
Image.extend({
  options: {
    delete: async (src) => {
      // Your delete logic
    },
  },
})
This error occurs when using endpoint-based configuration without providing the endpoint URL.Solution: Make sure to include the endpoint property:
Image.extend({
  options: {
    upload: {
      endpoint: '/api/upload-image', // Required!
    },
    delete: {
      endpoint: '/api/delete-image', // Required!
    },
  },
})