Would it be possible to have something akin to an amendTransaction hook, that would run just after filterTransaction? It’s come up quite a few times that I want to update a plugin state as an alternative to running a transaction. A couple of examples might be blocking a cursor movement (but remembering that it was blocked in some state), or deleting some content (and keeping it in the document for changeset reasons).

Alternatively is there a way of achieving this at the moment that I’ve missed?


1 Like

I guess we could have formulated filterTransaction as replaceTransaction instead, with the filter case just being a replacement with no transaction. If you want to start formulating an RFC on this (a replaceTransaction feature that can return an array of transactions, with filterTransaction reformulated to just be a backwards-compatibility shim on top of that) that might be interesting.

1 Like

I’ve had a bit of a think about this and I’ve realised it’s tricky implement … I feel like the most sensible approach is to allow plugins to take it in turns to replace / filter a transaction. When one filters it then act as before, but when one replaces it then this should again be run against all the other plugins to see whether they would go further a this point. This perhaps is a touch like the ability for appendTransaction to bounce into an infinite loop but it seems to rely more heavily on ordering than previously.

I’m not sure I quite understand why it would return an array here too?

I’ve done something like this outside of prosemirror before applying the new state

dispatchTransaction (tr) {
      if (this.interceptTransaction) tr = this.interceptTransaction(tr)
      if (!tr) return // leave unchanged
      this.editor.state = this.editor.state.apply(tr)

This sort of thing works ok at the editor level but you don’t have access to this at the plugin level and we’re looking to share the logic between a few editors.

yea the above pattern can definitely get messy too. I’d be ideal to contain the code in its own plugin.

when I was using vue.js the code was actually part of an editor package I used

1 Like

I’ve created an RFC for replaceTransaction that aims to solve this use-case. See, I’d appreciate any feedback on it.


Currently, we are using appendTransaction to fix documents that are correct by schema but by structure is not correct. For example, a table 5x5 where after a transaction is applied the final document doesn’t contain the correct number of cell/rows.

appendTransaction works perfect locally, but we run in a race condition once we add collab into the formula. Imagine two users, where the user A is trying to add a column and the user B is trying to add a row (at the same time).

This will cause the client B to receive a transaction where we are adding N cells (adding a column), but user B has a table of N+1 (user A table + the local row). We are going to attempt to fix the table using an append transaction and this new transaction is sent to the user A. At this point, an infinite bucle starts where each client is trying to fix the missing cell.

After further investigation, we got into the conclusion that we should not create the invalid document in the first place, so we need a way to fix the transaction before affecting the final state. We have a partial solution where we are amending the transaction in our custom dispatch, something like this:

new EditorView(..., {
  dispatchTransaction: (tr: transaction) => {
    amendTransaction(tr, state);
    dispatchTransation(tr, state);

amendTransaction is just a function that goes for each plugin looking for amendTransaction and execute this function in the transaction, mutating the existing transaction with the correct steps to fix the final document.

Do you think we could do something different or is this something that can be added to the core?

See this discussion for reasons why it’s not so simple to do this.

Are you sending transactions to the server separately, in your collab setup? Typically, all steps for a group of appended transactions would be sent over in one batch, so other clients don’t see the invalid state.

Curious if there have been additional thoughts on this…

I can’t think of a way to achieve desired behavior in my product without some kind of replaceTransaction flow.

I am using filterTransaction to ensure that if someone backspaces, they won’t be able to delete an Image node… however, when they do backspace, I want to change the selection so that it looks as though they skipped over the image node.

I’ve got the filterTransaction piece working, but how would I pair this with appendTransaction to get the desired behavior? I don’t think I can do side effects in filterTransaction.

My first thought would be to avoid messing with transactions and write a custom command for backspace.

Using chainCommands it’s easy to write special case key handlers - in your case you would check if they are about to backspace an image, if so move the selection and return true, otherwise return false to pass on to the next handler in the chain.

Then you can chain that with whatever ProseMirror does normally.

You check the transaction for deletes of image nodes in appendTransaction, then if so invert the transaction but move the selection past the image. It can be done although it’s a little work