Keep preexisting custom attributes on nodes

Using the dino example from, I can’t figure from the documentation how I can preserve any custom attributes added to the nodes before parsing.

So if I were to add attributes and values to the dino nodes (in the content), they would get removed when the content gets parsed. I need this meta data for an existing workflow. Mostly data attributes. This article seemed to have the same question, but the solution’s parse function doesn’t fire, so maybe the spec as changed.

Any help would be very welcomed. Thanks!

Your parse rule(s) for the node will have to read the values from the DOM and add them as attributes.

Which object/method is that under? It looks like the DOMParser?

For nodes that you define, you’d usually store them in the parseDOM spec. See for example the way the basic schema’s image node constructs document node attributes from DOM attributes.

Thank you for helping. I tried that approach with no success, and many other variations. I feel like there’s just a fundemental piece I’m missing. I’ve built a WYSIWYG with the same behavior as you see in the dino example, but it was done with DOM manipulation/watching, but ProseMirror wrote it the way I would if I were to do it again. While I agree with the complete modularization of ProseMirror, I’m struggling to put the pieces together for these dino nodes, let alone in my own company’s implementation.

So in part desperation, I have really avoided doing this ever, but could you kindly adjust this dino demo, and comment on what you changed and why?

This is the dino demo from the PM website, and I’ve included a data-some="thing" attribute on the tyrannosaurus node in the pre-existing content that I want persisted when PM parses it. (In my real application, we’ll have multiple data attributes with unknown/variable values, and style attributes for those that have to work in Outlook for desktop).

    <!doctype html>
    <title>Dinos in the Document</title>
    <meta charset="utf8">
    <link rel="stylesheet" href="">
    <script src=""></script>
    <script src="require-pm.js"></script>

    <script src="index.js" defer></script>
    <base href="">

      img.dinosaur { height: 40px; vertical-align: bottom; border: 1px solid #0ae; border-radius: 4px; background: #ddf6ff }


    <div id="editor"></div>

    <div id="content" style="display: none">
      <p>This is your dinosaur-enabled editor. The insert menu allows you
      to insert dinosaurs.</p>
      <p>This paragraph <img class="dinosaur" dino-type="stegosaurus">, for example,
      <img class="dinosaur" dino-type="triceratops">
      is full <img class="dinosaur" dino-type="tyrannosaurus" data-some="thing"> of
      <p>Dinosaur nodes can be selected, copied, pasted, dragged, and so on.</p>

    <script type="text/javascript">

      // The supported types of dinosaurs.
      const dinos = ["brontosaurus", "stegosaurus", "triceratops",
                     "tyrannosaurus", "pterodactyl"]

      const dinoNodeSpec = {
        // Dinosaurs have one attribute, their type, which must be one of
        // the types defined above.
        // Brontosaurs are still the default dino.
        attrs: {type: {default: "brontosaurus"}},
        inline: true,
        group: "inline",
        draggable: true,

        // These nodes are rendered as images with a `dino-type` attribute.
        // There are pictures for all dino types under /img/dino/.
        toDOM: node => ["img", {"dino-type": node.attrs.type,
                                src: "/img/dino/" + node.attrs.type + ".png",
                                title: node.attrs.type,
                                class: "dinosaur"}],
        // When parsing, such an image, if its type matches one of the known
        // types, is converted to a dino node.
        parseDOM: [{
          tag: "img[dino-type]",
          getAttrs: dom => {
            let type = dom.getAttribute("dino-type")
            return dinos.indexOf(type) > -1 ? {type} : false

      const {Schema, DOMParser} = require("prosemirror-model")
      const {schema} = require("prosemirror-schema-basic")

      const dinoSchema = new Schema({
        nodes: schema.spec.nodes.addBefore("image", "dino", dinoNodeSpec),
        marks: schema.spec.marks

      let content = document.querySelector("#content")
      let startDoc = DOMParser.fromSchema(dinoSchema).parse(content)

      let dinoType = dinoSchema.nodes.dino

      function insertDino(type) {
        return function(state, dispatch) {
          let {$from} = state.selection, index = $from.index()
          if (!$from.parent.canReplaceWith(index, index, dinoType))
            return false
          if (dispatch)
          return true

      const {MenuItem} = require("prosemirror-menu")
      const {buildMenuItems} = require("prosemirror-example-setup")

      // Ask example-setup to build its basic menu
      let menu = buildMenuItems(dinoSchema)
      // Add a dino-inserting item for each type of dino
      dinos.forEach(name => menu.insertMenu.content.push(new MenuItem({
        title: "Insert " + name,
        label: name.charAt(0).toUpperCase() + name.slice(1),
        enable(state) { return insertDino(name)(state) },
        run: insertDino(name)

      const {EditorState} = require("prosemirror-state")
      const {EditorView} = require("prosemirror-view")
      const {exampleSetup} = require("prosemirror-example-setup")

      window.view = new EditorView(document.querySelector("#editor"), {
        state: EditorState.create({
          doc: startDoc,
          // Pass exampleSetup our schema and the menu we created
          plugins: exampleSetup({schema: dinoSchema, menuContent: menu.fullMenu})


I don’t see any attribute corresponding to this in your dino node spec. In ProseMirror, the document as represented by the schema is the entire model of your document, so if something can’t be represented there, it’ll be dropped. You’ll need to add another attribute to the node that can store your extra information, and make sure that’s parsed and serialized properly in the node’s toDOM and parseDOM specs.