How to async modify pasted stuff

I’m using ProseMirror to build an editor for our CMS. When pasting multimedia, it should check whether it’s already hosted by the CMS, and if that’s not true, it should fetch the media and re-upload it to the CMS to keep it self-contained.

So far, I got this:

// Sube los multimedia que no están todavía en ActiveStorage.
export const autoUploadPlugin = new Plugin({
  appendTransaction(_, __, newState) {
    let tr =;

    let multimediaToCheck: { node: ProsemirrorNode; pos: number }[] = [];
    newState.doc.descendants((node, pos) => {
      if ( === "multimedia")
        multimediaToCheck.push({ node, pos });

    for (const { node, pos } of multimediaToCheck) {
      if (!node.attrs.src.startsWith(activeStorageEndpoint)) {
        const point = insertPoint(
        const id = {};
        tr.setMeta(placeholderPlugin, { add: { id, pos: point } }).delete(
          pos + 1

          // TODO: handlear
          .then((res) => res.blob())
          .then(async (blob) => {
            const url = await uploadFile(blob);

            const placeholderPos = findPlaceholder(newState, id);
            // si se borró el placeholder, no subir imágen
            if (placeholderPos == null) return;

            const node = newState.schema.nodes.multimedia.createChecked({
              src: url,
              kind: mimeToKind(blob.type),

            // ###### this clearly doesn't work because we don't have `view` here.
                .replaceWith(placeholderPos, placeholderPos, node)
                .setMeta(placeholderPlugin, { remove: { id } })
          .catch((err) => console.error("No pude fetchear el src", err));

    return tr;

Instead, I am considering handling this in a handlePaste. However, this won’t work when other methods (like dropping) are used. I think this might be fine though, but I’m not sure. Is there a way to do this with appendTransaction or something similar? Is using handlePaste reasonable?

There’s also handleDrop, and together with handlePaste that should cover the ways in which external content is inserted into the editor. transformPasted can be used to transform content read by both of these.

I ended up using neither of those, because

  • In handlePaste it’s unclear to me what I’m supposed to implement as it "overrides the behaviour of pasting`
  • In transformPasted I don’t know the position of where it is being pasted to set the placeholder.

Instead, I implemented it using PluginSpec.view which seems to work great, is that ok?

Hi there. I am implementing something similar myself. Could you share how you did it with PluginSpec.view? :grinning: Thanks a ton!

Check out the implementation: src/uploads/auto-upload.ts · 38deef1faff294be691db294574b585fefe3481f · sutty / Editor · GitLab

1 Like