Creating an implicit text node

Hi @marijn Such a beautiful library, thank you very much for all your work.

I’m creating a wrapper in React, which accepts hashtags and mentions, and I have an issue with inserting text before a node which I create as immutable.

I’ve simplified my code into this example: The problem is:

  1. If I erase all the text prior to the “immutable”, making it the first child of the paragraph, I cannot insert a standard text node. It only tries to edit the immutable node, which is being filtered out by design.
  2. If I put the cursor at the end of the “immutable”, I cannot type in new text. I would like it to start a new text node if that’s the end of the paragraph parent node.

What is the best way to go about it? (naturally, the immutable stands for a more complex hashtag object.) Should it be to switch over to a nodeView (that approach didn’t work for me on mobile) ? to create hidden text nodes? Or appendTransaction to split the nodes whenever new text appears in the beginning or end or an “immutable” node?

For reference, the project I’m working on is here:

Thank you Iswara Chaitanya

Inline nodes with content don’t work well without a lot of extra scripting (see this thread). Browser’s can’t really be made to behave properly when editing on the boundaries. The easiest way around this would be to either make the content in your immutable node a node attribute (so that it becomes an uneditable leaf node) or to define a node view that renders it as a leaf, without contentDOM property.

Thank you very much @marijn

I’ve created this example, based on your second recommendation.

When editing the text on mobile, I cannot erase the nodeView with backspace. Instead of disappearing, it creates extra characters after the nodeView (I’m using Android) Is there something I’m doing wrong?

make the content in your immutable node a node attribute (so that it becomes an uneditable leaf node)

I don’t understand what the first suggestion means. Can you please elaborate?

FWIW when I try backspacing the immutable node in your example in Firefox on a Mac it works as I’d expect and deletes it.

As to the attrs I think Marijn is suggesting you add an attr like textContent (or some such) that you use to draw your view. I took a swing at modifying the relevant parts of your example below. Though I think to be complete the node itself would need a getAttrs that read from the dom.

const schema = new Schema({
  nodes: schemaBasic.spec.nodes
    .addBefore("text", "immutable", {
      group: "inline",
      atom: true,
      attrs: {
        textContent: {
          default: ""
      content: "inline*",
      inline: true,
      toDOM: node => ["span.immutable", this.attrs.textContent],
      parseDOM: [{ tag: "span.immutable" }], //should really have a getAttrs implementation
      selectable: true,
      draggable: true
    .update("doc", schemaBasic.spec.nodes.get("doc")),
  marks: schemaBasic.spec.marks

class ImmutableNodeView {
  constructor(node, view, getPos, decorations) {
    this.dom = document.createElement("span");
    this.dom.innerHTML = node.attrs.textContent;

  selectNode() {
  deselectNode() {

const immutablePlugin = new Plugin({
  key: new PluginKey("immutable view plugin"),
  props: {
    nodeViews: {
      immutable: (node, view, getPos, decorations) => {
        return new ImmutableNodeView(node, view, getPos, decorations);

const state = EditorState.create({
  doc: schema.nodeFromJSON({
    type: "doc",
    content: [
        type: "paragraph",
        content: [
          { type: "text", text: "here is an " },
            type: "immutable",
            attrs: {
              textContent: "immutable node view"
          { type: "text", text: "." }
  plugins: [immutablePlugin]