> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# Node

> The Node class is used to create custom node types in Tiptap.

## Creating a Node

Use the static `create` method to define a new node.

```typescript theme={null}
import { Node } from '@tiptap/core'

const MyNode = Node.create<Options, Storage>({
  name: 'myNode',
  // ... configuration
})
```

<ParamField path="config" type="Partial<NodeConfig<Options, Storage>> | (() => Partial<NodeConfig<Options, Storage>>)">
  The node configuration object or a function that returns a configuration object.
</ParamField>

## Configuration Options

Nodes inherit all configuration options from [Extension](/api/extension#configuration-options), plus the following:

### content

Define the content expression for the node.

```typescript theme={null}
content?: string | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['content']
  editor?: Editor
}) => string)
```

<ParamField path="content" type="string">
  A ProseMirror content expression defining what content this node can contain.
</ParamField>

**Examples**

```typescript theme={null}
// 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.

```typescript theme={null}
marks?: string | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['marks']
  editor?: Editor
}) => string)
```

<ParamField path="marks" type="string">
  Space-separated mark names, `"_"` to allow all marks, or `""` to disallow marks.
</ParamField>

**Example**

```typescript theme={null}
// Allow specific marks
marks: 'strong em'

// Allow all marks
marks: '_'

// Disallow all marks
marks: ''
```

### group

Define the group(s) this node belongs to.

```typescript theme={null}
group?: string | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['group']
  editor?: Editor
}) => string)
```

<ParamField path="group" type="string">
  Space-separated group names (e.g., `'block'`, `'inline'`).
</ParamField>

**Example**

```typescript theme={null}
group: 'block'

// Multiple groups
group: 'block customGroup'
```

### inline

Whether the node is an inline node.

```typescript theme={null}
inline?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['inline']
  editor?: Editor
}) => boolean)
```

<ParamField path="inline" type="boolean" default="false">
  Set to `true` for inline nodes.
</ParamField>

**Example**

```typescript theme={null}
inline: true
```

### atom

Whether the node is atomic (non-editable as a unit).

```typescript theme={null}
atom?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['atom']
  editor?: Editor
}) => boolean)
```

<ParamField path="atom" type="boolean" default="false">
  Set to `true` for atomic nodes that should be treated as a single unit.
</ParamField>

**Example**

```typescript theme={null}
atom: true
```

### selectable

Whether the node can be selected.

```typescript theme={null}
selectable?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['selectable']
  editor?: Editor
}) => boolean)
```

<ParamField path="selectable" type="boolean" default="true">
  Whether the node can be selected with a node selection.
</ParamField>

**Example**

```typescript theme={null}
selectable: false
```

### draggable

Whether the node can be dragged.

```typescript theme={null}
draggable?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['draggable']
  editor?: Editor
}) => boolean)
```

<ParamField path="draggable" type="boolean" default="false">
  Whether the node can be dragged without being selected.
</ParamField>

**Example**

```typescript theme={null}
draggable: true
```

### code

Whether the node contains code.

```typescript theme={null}
code?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['code']
  editor?: Editor
}) => boolean)
```

<ParamField path="code" type="boolean" default="false">
  Indicates that this node contains code, affecting command behavior.
</ParamField>

**Example**

```typescript theme={null}
code: true
```

### defining

Whether the node is defining.

```typescript theme={null}
defining?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['defining']
  editor?: Editor
}) => boolean)
```

<ParamField path="defining" type="boolean" default="false">
  When enabled, affects context for schema operations.
</ParamField>

### isolating

Whether the node is isolating.

```typescript theme={null}
isolating?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['isolating']
  editor?: Editor
}) => boolean)
```

<ParamField path="isolating" type="boolean" default="false">
  When enabled, the sides of this node count as boundaries that regular editing operations won't cross.
</ParamField>

**Example**

```typescript theme={null}
isolating: true // e.g., for table cells
```

### topNode

Whether this node should be the top-level node (document).

```typescript theme={null}
topNode?: boolean
```

<ParamField path="topNode" type="boolean" default="false">
  Set to `true` for the document node.
</ParamField>

**Example**

```typescript theme={null}
topNode: true
```

### parseHTML()

Define how to parse HTML into this node.

```typescript theme={null}
parseHTML?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['parseHTML']
  editor?: Editor
}): ParseRule[]
```

**Example**

```typescript theme={null}
parseHTML() {
  return [
    { tag: 'p' },
    { tag: 'div', getAttrs: node => (node as HTMLElement).classList.contains('paragraph') && null },
  ]
}
```

### renderHTML()

Define how to render the node as HTML.

```typescript theme={null}
renderHTML?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['renderHTML']
  editor?: Editor
}, props: {
  node: ProseMirrorNode
  HTMLAttributes: Record<string, any>
}): DOMOutputSpec
```

**Example**

```typescript theme={null}
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.

```typescript theme={null}
renderText?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['renderText']
  editor?: Editor
}, props: {
  node: ProseMirrorNode
  pos: number
  parent: ProseMirrorNode
  index: number
}): string
```

**Example**

```typescript theme={null}
renderText({ node }) {
  return node.textContent
}
```

### addAttributes()

Define attributes for the node.

```typescript theme={null}
addAttributes?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['addAttributes']
  editor?: Editor
}): Attributes | {}
```

**Example**

```typescript theme={null}
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.

```typescript theme={null}
addNodeView?(this: {
  name: string
  options: Options
  storage: Storage
  editor: Editor
  type: NodeType
  parent: ParentConfig['addNodeView']
}): NodeViewRenderer | null
```

**Example**

```typescript theme={null}
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

### configure()

Create a configured version of the node.

```typescript theme={null}
node.configure(options?: Partial<Options>): Node<Options, Storage>
```

<ParamField path="options" type="Partial<Options>">
  Options to override the default options.
</ParamField>

**Example**

```typescript theme={null}
import Heading from '@tiptap/extension-heading'

const editor = new Editor({
  extensions: [
    Heading.configure({
      levels: [1, 2, 3],
    }),
  ],
})
```

### extend()

Extend the node with additional configuration.

```typescript theme={null}
node.extend<ExtendedOptions, ExtendedStorage, ExtendedConfig>(
  extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)
): Node<ExtendedOptions, ExtendedStorage>
```

<ParamField path="extendedConfig" type="Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)">
  Additional configuration or a function that returns configuration.
</ParamField>

**Example**

```typescript theme={null}
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

```typescript theme={null}
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' })
```
