Skip to main content

Image Plugin

The Image plugin provides image upload, embedding, and manipulation capabilities with support for custom upload handlers, image providers, and responsive sizing.

Installation

npm install @yoopta/image

Usage

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

const plugins = [Image];
const editor = createYooptaEditor({ plugins });

Features

  • Image upload with custom handlers
  • URL-based image embedding
  • Resizable images with width/height control
  • Alignment support (left, center, right)
  • Object-fit options (contain, cover, fill)
  • Upload progress tracking
  • Image deletion handlers
  • Cloudinary, imgix, and Akamai optimization support
  • HTML, Markdown, and Email export

Options

import type { ImagePluginOptions } from '@yoopta/image';

const plugins = [
  Image.extend({
    options: {
      upload: {
        // Endpoint-based upload
        endpoint: '/api/upload',
        method: 'POST',
        headers: { 'Authorization': 'Bearer token' },
        fieldName: 'file',
        maxSize: 5 * 1024 * 1024, // 5MB
        onProgress: (progress) => console.log(progress.percentage),
      },
      delete: {
        endpoint: '/api/delete',
        method: 'DELETE',
      },
      maxSizes: {
        maxWidth: 1200,
        maxHeight: 800,
      },
      optimizations: {
        provider: 'cloudinary',
        deviceSizes: [640, 750, 828, 1080, 1200],
      },
    },
  }),
];

Plugin Options Type

type ImagePluginOptions = {
  upload?: ImageUploadOptions;
  delete?: ImageDeleteOptions;
  maxSizes?: {
    maxWidth?: number | string;
    maxHeight?: number | string;
  } | null;
  optimizations?: {
    deviceSizes?: number[];
    provider?: 'imgix' | 'cloudinary' | 'akamai';
  } | null;
};

Element Type

import type { ImageElement, ImageElementProps } from '@yoopta/image';

type ImageElementProps = {
  id: string | null;
  src?: string | null;
  alt?: string | null;
  srcSet?: string | null;
  bgColor?: string | null;
  fit?: 'contain' | 'cover' | 'fill' | null;
  sizes?: {
    width: number | string;
    height: number | string;
  };
};

type ImageElement = SlateElement<'image', ImageElementProps>;

Default Props

{
  id: null,
  src: null,
  alt: null,
  srcSet: null,
  bgColor: null,
  fit: null,
  sizes: { width: 0, height: 0 }
}

Upload Options

Endpoint-Based Upload

import type { ImageUploadEndpointOptions } from '@yoopta/image';

const uploadOptions: ImageUploadEndpointOptions = {
  endpoint: '/api/upload',
  method: 'POST',
  headers: { 'X-API-Key': 'your-key' },
  fieldName: 'image',
  maxSize: 10 * 1024 * 1024, // 10MB
  accept: 'image/png,image/jpeg,image/webp',
  onProgress: (progress) => {
    console.log(`Uploaded: ${progress.percentage}%`);
  },
  onSuccess: (result) => {
    console.log('Upload complete:', result.url);
  },
  onError: (error) => {
    console.error('Upload failed:', error.message);
  },
};

Custom Upload Function

import type { ImageUploadFn } from '@yoopta/image';

const customUpload: ImageUploadFn = async (file, onProgress) => {
  // Upload to Cloudinary, S3, Firebase, etc.
  const formData = new FormData();
  formData.append('file', file);
  
  const response = await fetch('/api/cloudinary-upload', {
    method: 'POST',
    body: formData,
  });
  
  const data = await response.json();
  
  return {
    id: data.public_id,
    src: data.secure_url,
    alt: file.name,
    sizes: {
      width: data.width,
      height: data.height,
    },
  };
};

const plugins = [
  Image.extend({
    options: {
      upload: customUpload,
    },
  }),
];

Hooks

useImageUpload

import { useImageUpload } from '@yoopta/image';
import type { UseImageUploadReturn } from '@yoopta/image';

function ImageUploader({ editor, blockId }) {
  const { upload, loading, progress, error, cancel, reset } = useImageUpload(
    editor,
    blockId
  );

  const handleUpload = async (file: File) => {
    try {
      const result = await upload(file);
      console.log('Uploaded:', result.url);
    } catch (err) {
      console.error('Upload failed:', err);
    }
  };

  return (
    <div>
      {loading && <p>Uploading: {progress?.percentage}%</p>}
      {error && <p>Error: {error.message}</p>}
      <button onClick={cancel}>Cancel</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

useImageDelete

import { useImageDelete } from '@yoopta/image';
import type { UseImageDeleteReturn } from '@yoopta/image';

function ImageDeleter({ editor, blockId, element }) {
  const { deleteImage, loading, error } = useImageDelete(editor, blockId);

  const handleDelete = async () => {
    try {
      await deleteImage(element);
      console.log('Deleted successfully');
    } catch (err) {
      console.error('Delete failed:', err);
    }
  };

  return (
    <button onClick={handleDelete} disabled={loading}>
      {loading ? 'Deleting...' : 'Delete Image'}
    </button>
  );
}

useImageDimensions

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

const { width, height, loading } = useImageDimensions(imageUrl);

useImagePreview

import { useImagePreview } from '@yoopta/image';
import type { ImageUploadPreview } from '@yoopta/image';

const preview: ImageUploadPreview = useImagePreview(file);
// { url: string, width?: number, height?: number }

Commands

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

// Insert image
editor.commands.Image.insertImage({
  src: 'https://example.com/image.jpg',
  alt: 'Description',
  sizes: { width: 800, height: 600 },
});

// Update image props
editor.commands.Image.updateImage(blockId, {
  fit: 'cover',
  sizes: { width: 1200, height: 800 },
});

Parsers

HTML

Deserialize: Converts <img> tags to image blocks Serialize: Outputs image in flex container for alignment
<div style="
  margin-left: 0px; 
  display: flex; 
  width: 100%; 
  justify-content: center;
">
  <img 
    data-meta-align="center" 
    data-meta-depth="0" 
    src="https://example.com/image.jpg" 
    alt="Image description" 
    width="800" 
    height="600" 
    objectFit="contain"
  />
</div>

Markdown

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

Email

<table style="width:100%;">
  <tbody style="width:100%;">
    <tr>
      <td style="
        display: flex; 
        width: 100%; 
        justify-content: center; 
        margin-top: 1rem;
      ">
        <img 
          src="https://example.com/image.jpg" 
          alt="Image description" 
          width="800" 
          height="600" 
          style="margin: 0 auto; object-fit: contain;"
        />
      </td>
    </tr>
  </tbody>
</table>

Examples

Basic Image Embed

import { Blocks } from '@yoopta/editor';
import { generateId } from '@yoopta/editor';

Blocks.insertBlock(editor, {
  type: 'Image',
  value: [
    {
      id: generateId(),
      type: 'image',
      props: {
        id: 'img-1',
        src: 'https://example.com/photo.jpg',
        alt: 'Beautiful landscape',
        fit: 'cover',
        sizes: { width: 1200, height: 800 },
      },
      children: [{ text: '' }],
    },
  ],
  meta: { align: 'center' },
});

Cloudinary Upload Example

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

const cloudinaryUpload: ImageUploadFn = async (file, onProgress) => {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('upload_preset', 'your_preset');

  const xhr = new XMLHttpRequest();
  
  xhr.upload.addEventListener('progress', (e) => {
    if (onProgress && e.lengthComputable) {
      onProgress({
        loaded: e.loaded,
        total: e.total,
        percentage: Math.round((e.loaded / e.total) * 100),
      });
    }
  });

  const response = await new Promise((resolve, reject) => {
    xhr.onload = () => resolve(JSON.parse(xhr.responseText));
    xhr.onerror = reject;
    xhr.open('POST', 'https://api.cloudinary.com/v1_1/your-cloud/image/upload');
    xhr.send(formData);
  });

  return {
    id: response.public_id,
    src: response.secure_url,
    alt: file.name,
    sizes: {
      width: response.width,
      height: response.height,
    },
  };
};

const plugins = [
  Image.extend({
    options: { upload: cloudinaryUpload },
  }),
];

Build docs developers (and LLMs) love