Guides
Image Upload (New)
Uploading images in the editor
1
Add image extension
Configure image extension with your styling. The imageClass
is used for styling the placeholder image.
//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} />
}
2
Create upload function
onUpload
should return a Promise<string>
validateFn
is triggered before an image is uploaded. It should return a boolean
value.
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;
},
});
3
Configure events callbacks
This is required to handle image paste and drop events in the editor.
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),
...
}}
/>
...
4
Update slash-command suggestionsItems
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();
},
}
])