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

# Image

> Upload and display images with full customization

export const PluginPlayground = ({pluginSlug, height = 420}) => {
  const baseUrl = 'https://yoopta.dev';
  return <div className="not-prose my-6 rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden">
      <iframe title={`${pluginSlug} plugin demo`} src={`${baseUrl}/playground/plugin/${pluginSlug}`} className="w-full border-0 bg-white dark:bg-zinc-900" style={{
    height: typeof height === 'number' ? `${height}px` : height
  }} />
    </div>;
};

## 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.

<PluginPlayground pluginSlug="image" height={320} />

## Installation

```bash theme={null}
npm install @yoopta/image
```

## Basic Usage

Pass the plugin to `createYooptaEditor`; do not pass `plugins` to `<YooptaEditor>`.

```jsx theme={null}
import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Image from '@yoopta/image';

const plugins = [Image];

export default function Editor() {
  const editor = useMemo(() => createYooptaEditor({ plugins, marks: [] }), []);
  return <YooptaEditor editor={editor} onChange={() => {}} />;
}
```

<Warning>
  **Required Configuration**

  You **must** configure the `upload` option. Without this option, you'll see an error when trying to use the Image plugin.

  ```jsx theme={null}
  // Required when using themes
  const plugins = [
    Image.extend({
      options: {
        upload: async (file) => {
          // Your upload logic here
          return { id: '...', src: '...' };
        },
        // delete is optional
      },
    }),
  ];
  ```

  See the [Configuration](#configuration) section below for detailed examples.
</Warning>

## 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 (if not configured, deletion only removes from editor)
* **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)

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

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

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

<Tabs>
  <Tab title="Endpoint Object">
    <ResponseField name="upload" type="object">
      <Expandable title="properties">
        <ResponseField name="endpoint" type="string" required>
          URL endpoint for image upload
        </ResponseField>

        <ResponseField name="method" type="'POST' | 'PUT' | 'PATCH'" default="POST">
          HTTP method for upload
        </ResponseField>

        <ResponseField name="headers" type="Record<string, string>">
          Custom headers for upload request
        </ResponseField>

        <ResponseField name="fieldName" type="string" default="file">
          Form field name for the file
        </ResponseField>

        <ResponseField name="maxSize" type="number">
          Maximum file size in bytes
        </ResponseField>

        <ResponseField name="accept" type="string">
          Accepted file types (e.g., "image/png,image/jpeg")
        </ResponseField>

        <ResponseField name="onProgress" type="function">
          Callback for upload progress
        </ResponseField>

        <ResponseField name="onSuccess" type="function">
          Callback when upload succeeds
        </ResponseField>

        <ResponseField name="onError" type="function">
          Callback when upload fails
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Tab>

  <Tab title="Custom Function">
    <ResponseField name="upload" type="function">
      Custom async function for direct uploads to third-party services.

      **Signature:**

      ```typescript theme={null}
      (file: File, onProgress?: (progress: ImageUploadProgress) => void) => Promise<ImageElementProps>
      ```

      **Returns `ImageElementProps`:**

      * `id` - Unique identifier for the image
      * `src` - URL of the uploaded image
      * `sizes` - Object with `width` and `height`
    </ResponseField>
  </Tab>
</Tabs>

### delete (optional)

Can be either an **object** (endpoint-based) or a **function** (custom delete). If not provided, deleting an image block will only remove it from the editor without calling any storage deletion logic.

<Tabs>
  <Tab title="Endpoint Object">
    <ResponseField name="delete" type="object">
      <Expandable title="properties">
        <ResponseField name="endpoint" type="string" required>
          URL endpoint for image deletion
        </ResponseField>

        <ResponseField name="method" type="'DELETE' | 'PATCH'" default="DELETE">
          HTTP method for deletion
        </ResponseField>

        <ResponseField name="headers" type="Record<string, string>">
          Custom headers for delete request
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Tab>

  <Tab title="Custom Function">
    <ResponseField name="delete" type="function">
      Custom async function for deleting images from third-party services.

      **Signature:**

      ```typescript theme={null}
      (src: string) => Promise<void>
      ```

      **Parameters:**

      * `src` - The URL/source of the image to delete
    </ResponseField>
  </Tab>
</Tabs>

<ResponseField name="maxSizes" type="object">
  <Expandable title="properties">
    <ResponseField name="maxWidth" type="number" default="650">
      Maximum image width in pixels
    </ResponseField>

    <ResponseField name="maxHeight" type="number" default="550">
      Maximum image height in pixels
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="optimizations" type="object">
  <Expandable title="properties">
    <ResponseField name="provider" type="'imgix' | 'cloudinary' | 'akamai'">
      Image optimization provider
    </ResponseField>

    <ResponseField name="deviceSizes" type="number[]">
      Responsive image sizes
    </ResponseField>
  </Expandable>
</ResponseField>

## Element Props

<ParamField path="src" type="string | null">
  Image source URL
</ParamField>

<ParamField path="alt" type="string | null">
  Alternative text for accessibility
</ParamField>

<ParamField path="srcSet" type="string | null">
  Responsive image source set
</ParamField>

<ParamField path="fit" type="'contain' | 'cover' | 'fill' | null">
  How the image should fit its container
</ParamField>

<ParamField path="sizes" type="object">
  Image dimensions: `{ width: number, height: number }`
</ParamField>

<ParamField path="bgColor" type="string | null">
  Background color for image container
</ParamField>

## Commands

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

```typescript theme={null}
{
  id: string;
  url: string;
  width?: number;
  height?: number;
  size?: number;
  format?: string;
}
```

## Custom Rendering

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

```html theme={null}
<img src="image.jpg" alt="Description" width="650" height="500" />
```

### HTML Serialization

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

### Markdown Serialization

```markdown theme={null}
![Description](https://example.com/image.jpg)
```

## Use Cases

<CardGroup cols={2}>
  <Card title="Blog Posts">Feature images and inline illustrations</Card>
  <Card title="Documentation">Screenshots and diagrams</Card>
  <Card title="Portfolios">Project images and galleries</Card>
  <Card title="Product Catalogs">Product images with descriptions</Card>
</CardGroup>

## Best Practices

<AccordionGroup>
  <Accordion title="Optimize Before Upload">
    Compress and resize images before uploading to reduce load times
  </Accordion>

  {' '}

  <Accordion title="Always Add Alt Text">Provide descriptive alt text for accessibility</Accordion>

  {' '}

  <Accordion title="Set Size Limits">
    Configure appropriate maxWidth and maxHeight for your design
  </Accordion>

  {' '}

  <Accordion title="Use Lazy Loading">Enable lazy loading for better performance</Accordion>

  <Accordion title="Provide Fallbacks">
    Handle upload errors gracefully with user feedback
  </Accordion>
</AccordionGroup>

## Hooks

### useImageUpload

Supports both endpoint-based and custom function approaches:

<Tabs>
  <Tab title="Endpoint-based">
    ```typescript theme={null}
    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);
    };
    ```
  </Tab>

  <Tab title="Custom Function">
    ```typescript theme={null}
    import { useImageUpload } from '@yoopta/image';

    const { upload, loading, progress, error } = useImageUpload(
      async (file, onProgress) => {
        const formData = new FormData();
        formData.append('file', file);

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

        return {
          id: data.public_id,
          src: data.secure_url,
          sizes: { width: data.width, height: data.height },
        };
      }
    );

    const handleUpload = async (file: File) => {
      const result = await upload(file);
    };
    ```
  </Tab>
</Tabs>

### useImageDelete

Supports both endpoint-based and custom function approaches:

<Tabs>
  <Tab title="Endpoint-based">
    ```typescript theme={null}
    import { useImageDelete } from '@yoopta/image';

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

  <Tab title="Custom Function">
    ```typescript theme={null}
    import { useImageDelete } from '@yoopta/image';

    const { deleteImage, loading, error } = useImageDelete(
      async (src) => {
        await fetch('/api/delete', {
          method: 'DELETE',
          body: JSON.stringify({ src }),
        });
      }
    );
    ```
  </Tab>
</Tabs>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Error: Upload options are not configured">
    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:

    ```jsx theme={null}
    Image.extend({
      options: {
        upload: async (file) => {
          // Your upload logic
          return { id: '...', src: '...' };
        },
      },
    })
    ```
  </Accordion>

  <Accordion title="Image not deleted from storage">
    If you want to delete images from your storage when removed from the editor, you need to configure the `delete` option.

    **Solution:** Add a `delete` configuration to your Image plugin:

    ```jsx theme={null}
    Image.extend({
      options: {
        upload: async (file) => { ... },
        delete: async (element) => {
          // Your delete logic
          await deleteFromStorage(element.props.id);
        },
      },
    })
    ```

    Note: The `delete` option is optional. Without it, images are only removed from the editor.
  </Accordion>

  <Accordion title="Error: Missing 'endpoint' in upload/delete options">
    This error occurs when using endpoint-based configuration without providing the `endpoint` URL.

    **Solution:** Make sure to include the `endpoint` property:

    ```jsx theme={null}
    Image.extend({
      options: {
        upload: {
          endpoint: '/api/upload-image', // Required!
        },
        delete: {
          endpoint: '/api/delete-image', // Required!
        },
      },
    })
    ```
  </Accordion>
</AccordionGroup>

## Related Plugins

* [Video Plugin](/plugins/video) - For video content
* [File Plugin](/plugins/file) - For file attachments
* [Carousel Plugin](/plugins/carousel) - For image galleries
