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

# Image Upload (New)

> Uploading images in the editor

<Steps>
  <Step title="Add image extension">
    Configure image extension with your styling. The `imageClass` is used for styling the placeholder image.

    ```tsx
    //extensions.ts
    import { UploadImagesPlugin } from "novel/plugins";

    const tiptapImage = TiptapImage.extend({
        addProseMirrorPlugins() {
            return [
                UploadImagesPlugin({
                    imageClass: cx("opacity-40 rounded-lg border border-stone-200"),
                }),
            ];
        },
        }).configure({
        allowBase64: true,
        HTMLAttributes: {
            class: cx("rounded-lg border border-muted"),
        },
    });

    export const defaultExtensions = [
        tiptapImage,
        //other extensions
    ];

    //editor.tsx
    const Editor = () => {
        return <EditorContent extensions={defaultExtensions} />
    }

    ```
  </Step>

  <Step title="Create upload function">
    `onUpload` should return a `Promise<string>`
    `validateFn` is triggered before an image is uploaded. It should return a `boolean` value.

    ```tsx image-upload.ts
    import { createImageUpload } from "novel/plugins";
    import { toast } from "sonner";

    const onUpload = async (file: File) => {
        const promise = fetch("/api/upload", {
            method: "POST",
            headers: {
            "content-type": file?.type || "application/octet-stream",
            "x-vercel-filename": file?.name || "image.png",
            },
            body: file,
        });

        //This should return a src of the uploaded image
        return promise;
    };

    export const uploadFn = createImageUpload({
        onUpload,
        validateFn: (file) => {
            if (!file.type.includes("image/")) {
                toast.error("File type not supported.");
                return false;
            } else if (file.size / 1024 / 1024 > 20) {
                toast.error("File size too big (max 20MB).");
                return false;
            }
            return true;
        },
    });

    ```
  </Step>

  <Step title="Configure events callbacks">
    This is required to handle image paste and drop events in the editor.

    ```tsx editor.tsx
    import { handleImageDrop, handleImagePaste } from "novel/plugins";
    import { uploadFn } from "./image-upload";

    ...
    <EditorContent
            editorProps={{
                handlePaste: (view, event) => handleImagePaste(view, event, uploadFn),
                handleDrop: (view, event, _slice, moved) =>  handleImageDrop(view, event, moved, uploadFn),
                ...
            }}
    />
    ...
    ```
  </Step>

  <Step title="Update slash-command suggestionsItems">
    ```tsx
    import { ImageIcon } from "lucide-react";
    import { createSuggestionItems } from "novel/extensions";
    import { uploadFn } from "./image-upload";

    export const suggestionItems = createSuggestionItems([
        ...,
        {
            title: "Image",
            description: "Upload an image from your computer.",
            searchTerms: ["photo", "picture", "media"],
            icon: <ImageIcon size={18} />,
            command: ({ editor, range }) => {
                editor.chain().focus().deleteRange(range).run();
                // upload image
                const input = document.createElement("input");
                input.type = "file";
                input.accept = "image/*";
                input.onchange = async () => {
                    if (input.files?.length) {
                    const file = input.files[0];
                    const pos = editor.view.state.selection.from;
                    uploadFn(file, editor.view, pos);
                    }
                };
                input.click();
            },
        }
     ])
    ```
  </Step>
</Steps>
