Creating a Node
Use the static create method to define a new node.
import { Node } from '@tiptap/core'
const MyNode = Node.create<Options, Storage>({
name: 'myNode',
// ... configuration
})
config
Partial<NodeConfig<Options, Storage>> | (() => Partial<NodeConfig<Options, Storage>>)
The node configuration object or a function that returns a configuration object.
Configuration Options
Nodes inherit all configuration options from Extension, plus the following:
content
Define the content expression for the node.
content?: string | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['content']
editor?: Editor
}) => string)
A ProseMirror content expression defining what content this node can contain.
Examples
// Block nodes only
content: 'block+'
// Inline content
content: 'inline*'
// Specific nodes
content: 'heading paragraph block*'
// No content (leaf node)
content: undefined
marks
Define which marks are allowed inside the node.
marks?: string | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['marks']
editor?: Editor
}) => string)
Space-separated mark names, "_" to allow all marks, or "" to disallow marks.
Example
// Allow specific marks
marks: 'strong em'
// Allow all marks
marks: '_'
// Disallow all marks
marks: ''
group
Define the group(s) this node belongs to.
group?: string | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['group']
editor?: Editor
}) => string)
Space-separated group names (e.g., 'block', 'inline').
Example
group: 'block'
// Multiple groups
group: 'block customGroup'
inline
Whether the node is an inline node.
inline?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['inline']
editor?: Editor
}) => boolean)
Set to true for inline nodes.
Example
atom
Whether the node is atomic (non-editable as a unit).
atom?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['atom']
editor?: Editor
}) => boolean)
Set to true for atomic nodes that should be treated as a single unit.
Example
selectable
Whether the node can be selected.
selectable?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['selectable']
editor?: Editor
}) => boolean)
Whether the node can be selected with a node selection.
Example
draggable
Whether the node can be dragged.
draggable?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['draggable']
editor?: Editor
}) => boolean)
Whether the node can be dragged without being selected.
Example
code
Whether the node contains code.
code?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['code']
editor?: Editor
}) => boolean)
Indicates that this node contains code, affecting command behavior.
Example
defining
Whether the node is defining.
defining?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['defining']
editor?: Editor
}) => boolean)
When enabled, affects context for schema operations.
isolating
Whether the node is isolating.
isolating?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['isolating']
editor?: Editor
}) => boolean)
When enabled, the sides of this node count as boundaries that regular editing operations won’t cross.
Example
isolating: true // e.g., for table cells
topNode
Whether this node should be the top-level node (document).
Set to true for the document node.
Example
parseHTML()
Define how to parse HTML into this node.
parseHTML?(this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['parseHTML']
editor?: Editor
}): ParseRule[]
Example
parseHTML() {
return [
{ tag: 'p' },
{ tag: 'div', getAttrs: node => (node as HTMLElement).classList.contains('paragraph') && null },
]
}
renderHTML()
Define how to render the node as HTML.
renderHTML?(this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['renderHTML']
editor?: Editor
}, props: {
node: ProseMirrorNode
HTMLAttributes: Record<string, any>
}): DOMOutputSpec
Example
renderHTML({ node, HTMLAttributes }) {
return ['p', HTMLAttributes, 0]
}
// With custom attributes
renderHTML({ node, HTMLAttributes }) {
return [
'div',
{ ...HTMLAttributes, class: 'my-node', 'data-id': node.attrs.id },
0, // 0 represents where content goes
]
}
renderText()
Define how to render the node as plain text.
renderText?(this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['renderText']
editor?: Editor
}, props: {
node: ProseMirrorNode
pos: number
parent: ProseMirrorNode
index: number
}): string
Example
renderText({ node }) {
return node.textContent
}
addAttributes()
Define attributes for the node.
addAttributes?(this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['addAttributes']
editor?: Editor
}): Attributes | {}
Example
addAttributes() {
return {
level: {
default: 1,
rendered: true,
parseHTML: element => element.getAttribute('data-level'),
renderHTML: attributes => {
return { 'data-level': attributes.level }
},
},
id: {
default: null,
parseHTML: element => element.getAttribute('id'),
renderHTML: attributes => {
if (!attributes.id) return {}
return { id: attributes.id }
},
},
}
}
addNodeView()
Define a custom node view.
addNodeView?(this: {
name: string
options: Options
storage: Storage
editor: Editor
type: NodeType
parent: ParentConfig['addNodeView']
}): NodeViewRenderer | null
Example
import { NodeViewWrapper } from '@tiptap/react'
addNodeView() {
return ({ node, getPos, editor }) => {
const dom = document.createElement('div')
dom.classList.add('my-custom-node')
dom.textContent = node.textContent
return {
dom,
contentDOM: dom,
update: (updatedNode) => {
if (updatedNode.type !== node.type) return false
// Update view
return true
},
destroy: () => {
// Cleanup
},
}
}
}
Methods
Create a configured version of the node.
node.configure(options?: Partial<Options>): Node<Options, Storage>
Options to override the default options.
Example
import Heading from '@tiptap/extension-heading'
const editor = new Editor({
extensions: [
Heading.configure({
levels: [1, 2, 3],
}),
],
})
extend()
Extend the node with additional configuration.
node.extend<ExtendedOptions, ExtendedStorage, ExtendedConfig>(
extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)
): Node<ExtendedOptions, ExtendedStorage>
extendedConfig
Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)
Additional configuration or a function that returns configuration.
Example
import Heading from '@tiptap/extension-heading'
const CustomHeading = Heading.extend({
addAttributes() {
return {
...this.parent?.(),
customId: {
default: null,
parseHTML: element => element.getAttribute('data-custom-id'),
renderHTML: attributes => {
if (!attributes.customId) return {}
return { 'data-custom-id': attributes.customId }
},
},
}
},
})
Complete Example
import { Node, mergeAttributes } from '@tiptap/core'
interface ImageOptions {
inline: boolean
allowBase64: boolean
HTMLAttributes: Record<string, any>
}
const Image = Node.create<ImageOptions>({
name: 'image',
addOptions() {
return {
inline: false,
allowBase64: false,
HTMLAttributes: {},
}
},
inline() {
return this.options.inline
},
group() {
return this.options.inline ? 'inline' : 'block'
},
draggable: true,
addAttributes() {
return {
src: {
default: null,
parseHTML: element => element.getAttribute('src'),
renderHTML: attributes => {
if (!attributes.src) return {}
return { src: attributes.src }
},
},
alt: {
default: null,
parseHTML: element => element.getAttribute('alt'),
renderHTML: attributes => {
if (!attributes.alt) return {}
return { alt: attributes.alt }
},
},
title: {
default: null,
parseHTML: element => element.getAttribute('title'),
renderHTML: attributes => {
if (!attributes.title) return {}
return { title: attributes.title }
},
},
}
},
parseHTML() {
return [
{
tag: this.options.allowBase64 ? 'img[src]' : 'img[src]:not([src^="data:"])',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]
},
addCommands() {
return {
setImage: (options) => ({ commands }) => {
return commands.insertContent({
type: this.name,
attrs: options,
})
},
}
},
})
// Usage
const editor = new Editor({
extensions: [
Image.configure({
inline: true,
allowBase64: true,
}),
],
})
editor.commands.setImage({ src: 'image.jpg', alt: 'Description' })