Find out what's new in Tiptap V3

Mark API

The power of Tiptap lies in its flexibility. You can create your own extensions from scratch and build a unique editor experience tailored to your needs.

Creating a mark

Marks are used to add inline formatting to text in Tiptap. Common examples include bold, italic, and underline formatting.

They extend all of the options and methods from the Extension API and add a few more specific to marks.

Let's add a simple mark extension to see how it works.

import { Mark } from '@tiptap/core'

const HighlightMark = Mark.create({
  name: 'highlight',

  addOptions() {
    return {
      HTMLAttributes: {},
    }
  },

  parseHTML() {
    return [
      {
        tag: 'mark',
      },
    ]
  },

  renderHTML({ HTMLAttributes }) {
    return ['mark', HTMLAttributes, 0]
  },
})

You can also use a callback function to create a mark. This is useful if you want to encapsulate the logic of your extension, for example when you want to define event handlers or other custom logic.

import { Mark } from '@tiptap/core'

const CustomMark = Mark.create(() => {
  // Define variables or functions to use inside your extension
  const customVariable = 'foo'

  function onCreate() {}
  function onUpdate() {}

  return {
    name: 'customMark',
    onCreate,
    onUpdate,

    // Your code goes here.
  }
})

This code creates a new mark extension named HighlightMark. It adds an addOptions method to define the mark's options, which are configurable by the user. It also adds parseHTML and renderHTML methods to define how the mark is parsed and rendered as HTML.

It is installed to the editor just like any other extension by adding it to the extensions array.

import { Editor } from '@tiptap/core'

new Editor({
  extensions: [HighlightMark],
})

// Or if using React or Vue

const editor = useEditor({
  extensions: [HighlightMark],
})

Now let's take a closer look at the options and methods available for marks.

Mark options

When creating a mark extension, you can define options that are configurable by the user. These options can be used to customize the behavior or appearance of the mark.

parseHTML

The parseHTML method is used to define how the mark is parsed from HTML. It should return an array of objects representing the mark's attributes.

Maps to the parseDOM attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  parseHTML() {
    return [
      {
        tag: 'span',
        getAttrs: (node) => {
          return {
            class: node.getAttribute('class'),
          }
        },
      },
    ]
  },
})

This will be used during paste events to parse the HTML content into a mark.

renderHTML

The renderHTML method is used to define how the mark is rendered as HTML. It should return an array representing the mark's HTML representation.

Maps to the toDOM attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  renderHTML({ HTMLAttributes }) {
    return ['span', HTMLAttributes, 0]
  },
})

This will be used during copy events to render the mark as HTML. For more details, see the extend existing extensions guide.

addAttributes

The addAttributes method is used to define custom attributes for the mark. It should return an object with the attribute names and their default values.

Maps to the attrs attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  addAttributes() {
    return {
      customAttribute: {
        default: 'value',
        parseHTML: (element) => element.getAttribute('data-custom-attribute'),
      },
    }
  },
})

For more details, see the extend existing extensions guide.

keepOnSplit

By default, when a node is split, marks are removed from the content. You can set keepOnSplit to true to keep the mark on the new node.

const CustomMark = Mark.create({
  name: 'customMark',

  keepOnSplit: true,
})

inclusive

By default, marks are not inclusive, meaning they cannot be applied to the start or end of a node. You can set inclusive to true to allow the mark to be applied to the start or end of a node.

Maps to the inclusive attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  inclusive: true,
})

excludes

By default, marks do not exclude other marks. You can define a list of marks that should be excluded when this mark is applied.

Maps to the excludes attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  excludes: ['bold', 'italic'],
})

exitable

By default, marks are not exitable, meaning they cannot be removed by pressing backspace at the start of the mark. You can set exitable to true to allow the mark to be removed in this way.

Maps to the exitable attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  exitable: true,
})

group

By default, marks are not grouped, meaning they are applied individually. You can define a group for the mark to ensure that only one mark from the group can be applied at a time.

Maps to the group attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  group: 'textStyle',
})

spanning

By default, marks do not span multiple nodes. You can set spanning to true to allow the mark to span multiple nodes.

Maps to the spanning attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  spanning: true,
})

code

By default, marks are not treated as code marks. You can set code to true to treat the mark as a code mark.

Maps to the code attribute in the ProseMirror schema.

const CustomMark = Mark.create({
  name: 'customMark',

  code: true,
})