- Create a file Image.jsfrom the source below (it is almost a copy of Image.js from tiptap-extensions except that it has a constructor that acceptsuploadFunc(function to be called withimagebeing uploaded) and additional logicif(upload) { ... } else { ... previous base64 logic .. }in thenew Pluginsection.
import {Node, Plugin} from 'tiptap'
import {nodeInputRule} from 'tiptap-commands'
/**
 * Matches following attributes in Markdown-typed image: [, alt, src, title]
 *
 * Example:
 *  -> [, "Lorem", "image.jpg"]
 *  -> [, "", "image.jpg", "Ipsum"]
 *  -> [, "Lorem", "image.jpg", "Ipsum"]
 */
const IMAGE_INPUT_REGEX = /!\[(.+|:?)\]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
export default class Image extends Node {
    constructor(name, parent, uploadFunc = null) {
        super(name, parent);
        this.uploadFunc = uploadFunc;
    }
    get name() {
        return 'image'
    }
    get schema() {
        return {
            inline: true,
            attrs: {
                src: {},
                alt: {
                    default: null,
                },
                title: {
                    default: null,
                },
            },
            group: 'inline',
            draggable: true,
            parseDOM: [
                {
                    tag: 'img[src]',
                    getAttrs: dom => ({
                        src: dom.getAttribute('src'),
                        title: dom.getAttribute('title'),
                        alt: dom.getAttribute('alt'),
                    }),
                },
            ],
            toDOM: node => ['img', node.attrs],
        }
    }
    commands({ type }) {
        return attrs => (state, dispatch) => {
            const { selection } = state;
            const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos;
            const node = type.create(attrs);
            const transaction = state.tr.insert(position, node);
            dispatch(transaction)
        }
    }
    inputRules({ type }) {
        return [
            nodeInputRule(IMAGE_INPUT_REGEX, type, match => {
                const [, alt, src, title] = match;
                return {
                    src,
                    alt,
                    title,
                }
            }),
        ]
    }
    get plugins() {
        const upload = this.uploadFunc;
        return [
            new Plugin({
                props: {
                    handleDOMEvents: {
                        drop(view, event) {
                            const hasFiles = event.dataTransfer
                                && event.dataTransfer.files
                                && event.dataTransfer.files.length;
                            if (!hasFiles) {
                                return
                            }
                            const images = Array
                                .from(event.dataTransfer.files)
                                .filter(file => (/image/i).test(file.type));
                            if (images.length === 0) {
                                return
                            }
                            event.preventDefault();
                            const { schema } = view.state;
                            const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
                            images.forEach(async image => {
                                const reader = new FileReader();
                                if(upload) {
                                    const node = schema.nodes.image.create({
                                        src: await upload(image),
                                    });
                                    const transaction = view.state.tr.insert(coordinates.pos, node);
                                    view.dispatch(transaction)
                                } else {
                                    reader.onload = readerEvent => {
                                        const node = schema.nodes.image.create({
                                            src: readerEvent.target.result,
                                        });
                                        const transaction = view.state.tr.insert(coordinates.pos, node);
                                        view.dispatch(transaction)
                                    };
                                    reader.readAsDataURL(image)
                                }
                            })
                        },
                    },
                },
            }),
        ]
    }
}- Import it:
import Image from './Image';
async function upload(file) {
...
}
new Editor({
  extensions: [
    ...
    new Image(null, null, upload),
    ...- Implement the uploadfunction:
async function upload(file) {
  let formData = new FormData();
  formData.append('file', file);
  const headers = {'Content-Type': 'multipart/form-data'};
  const response = await axios.post('/upload', formData, {headers: headers} );
  return response.data.src;
},This POSTs using axios to /upload and expects a JSON back of this form:
{"src": "https://yoursite.com/images/uploadedimage.jpg"}
- 
Implement server-side logic for /upload
- 
If you want to support pasting of images, modify Image.jsstarting atprops:, ending athandleDOMEvents(re-factor common parts if you want to)
props: {
    handlePaste(view, event, slice) {
        const items = (event.clipboardData  || event.originalEvent.clipboardData).items;
        for (const item of items) {
            if (item.type.indexOf("image") === 0) {
                event.preventDefault();
                const { schema } = view.state;
                const image = item.getAsFile();
                if(upload) {
                    upload(image).then(src => {
                        const node = schema.nodes.image.create({
                            src: src,
                        });
                        const transaction = view.state.tr.replaceSelectionWith(node);
                        view.dispatch(transaction)
                    });
                } else {
                    const reader = new FileReader();
                    reader.onload = readerEvent => {
                        const node = schema.nodes.image.create({
                            src: readerEvent.target.result,
                        });
                        const transaction = view.state.tr.replaceSelectionWith(node);
                        view.dispatch(transaction)
                    };
                    reader.readAsDataURL(image)
                }
            }
        }
        return false;
    },
    handleDOMEvents: {