Skip to main content
Commands are functions that manipulate the editor state. They’re the primary way you interact with Tiptap to change content, format text, and control the editor.

Using Commands

All commands are available through editor.commands:
// Set content
editor.commands.setContent('<p>Hello World!</p>')

// Toggle formatting
editor.commands.toggleBold()
editor.commands.toggleItalic()

// Insert content
editor.commands.insertContent('<p>New paragraph</p>')

// Set node type
editor.commands.setParagraph()
editor.commands.setHeading({ level: 1 })
Source: /home/daytona/workspace/source/packages/core/src/Editor.ts:232

Command Types

Tiptap provides three ways to execute commands:

Single Commands

Execute one command immediately.
editor.commands.toggleBold()

Chained Commands

Execute multiple commands in sequence.
editor.chain()
  .toggleBold()
  .toggleItalic()
  .run()

Can Commands

Check if a command can be executed.
editor.can().toggleBold()

Single Commands

Single commands execute immediately and return a boolean indicating success:
const success = editor.commands.toggleBold()

if (success) {
  console.log('Bold was toggled')
} else {
  console.log('Bold could not be toggled')
}
Source: /home/daytona/workspace/source/packages/core/src/CommandManager.ts:28

Chained Commands

Chained commands batch multiple operations into a single transaction:
editor
  .chain()
  .focus()
  .toggleBold()
  .toggleItalic()
  .run()
You must call .run() at the end of a chain to execute the commands!
Chains stop executing if a command fails:
// If toggleBold fails, toggleItalic won't run
editor
  .chain()
  .toggleBold() // fails
  .toggleItalic() // won't run
  .run()
The chain returns true if all commands succeeded, false otherwise:
const success = editor
  .chain()
  .toggleBold()
  .toggleItalic()
  .run()

console.log(success) // true or false
Source: /home/daytona/workspace/source/packages/core/src/CommandManager.ts:59

Can Commands

Use editor.can() to check if a command can be executed without actually executing it:
if (editor.can().toggleBold()) {
  console.log('Bold can be toggled')
}

// Disable a button if the command can't run
const isBoldDisabled = !editor.can().toggleBold()
You can also chain can() commands:
if (editor.can().chain().toggleBold().toggleItalic().run()) {
  console.log('Both bold and italic can be toggled')
}
Source: /home/daytona/workspace/source/packages/core/src/CommandManager.ts:95

Command Categories

Content Commands

setContent
(content: string | JSONContent, options?) => boolean
Replace the entire document.
// Set HTML
editor.commands.setContent('<p>Hello World!</p>')

// Set JSON
editor.commands.setContent({
  type: 'doc',
  content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }]
})

// Don't emit update event
editor.commands.setContent('<p>Hello</p>', { emitUpdate: false })
Source: /home/daytona/workspace/source/packages/core/src/commands/setContent.ts:35
insertContent
(content: string | JSONContent, options?) => boolean
Insert content at the current cursor position.
editor.commands.insertContent('<p>New paragraph</p>')
insertContentAt
(position: number | Range, content: string | JSONContent) => boolean
Insert content at a specific position.
// Insert at position 10
editor.commands.insertContentAt(10, '<p>Hello</p>')

// Insert in a range
editor.commands.insertContentAt({ from: 0, to: 10 }, '<p>Hello</p>')
clearContent
(emitUpdate?: boolean) => boolean
Clear the entire document.
editor.commands.clearContent()

Selection Commands

focus
(position?: 'start' | 'end' | number | boolean) => boolean
Focus the editor.
// Focus at current position
editor.commands.focus()

// Focus at start
editor.commands.focus('start')

// Focus at end
editor.commands.focus('end')

// Focus at position 10
editor.commands.focus(10)
blur
() => boolean
Remove focus from the editor.
editor.commands.blur()
setTextSelection
(position: number | Range) => boolean
Set the text selection.
// Select position 10
editor.commands.setTextSelection(10)

// Select from 0 to 10
editor.commands.setTextSelection({ from: 0, to: 10 })
selectAll
() => boolean
Select the entire document.
editor.commands.selectAll()

Node Commands

setNode
(typeOrName: string | NodeType, attributes?) => boolean
Replace the current node with a different type.
// Set to paragraph
editor.commands.setNode('paragraph')

// Set to heading with level
editor.commands.setNode('heading', { level: 1 })
toggleNode
(typeOrName: string | NodeType, toggleTypeOrName: string | NodeType, attributes?) => boolean
Toggle between two node types.
// Toggle between heading and paragraph
editor.commands.toggleNode('heading', 'paragraph', { level: 1 })
deleteNode
(typeOrName: string | NodeType) => boolean
Delete a specific node.
editor.commands.deleteNode('image')
updateAttributes
(typeOrName: string | NodeType | MarkType, attributes: Record<string, any>) => boolean
Update attributes of the current node or mark.
// Update heading level
editor.commands.updateAttributes('heading', { level: 2 })

// Update link href
editor.commands.updateAttributes('link', { href: 'https://example.com' })

Mark Commands

setMark
(typeOrName: string | MarkType, attributes?) => boolean
Apply a mark to the current selection.
editor.commands.setMark('bold')
editor.commands.setMark('link', { href: 'https://example.com' })
toggleMark
(typeOrName: string | MarkType, attributes?, options?) => boolean
Toggle a mark on the current selection.
// Toggle bold
editor.commands.toggleMark('bold')

// Toggle link with attributes
editor.commands.toggleMark('link', { href: 'https://example.com' })

// Extend empty mark range
editor.commands.toggleMark('bold', {}, { extendEmptyMarkRange: true })
Source: /home/daytona/workspace/source/packages/core/src/commands/toggleMark.ts:39
unsetMark
(typeOrName: string | MarkType, options?) => boolean
Remove a mark from the current selection.
editor.commands.unsetMark('bold')
editor.commands.unsetMark('link')
unsetAllMarks
() => boolean
Remove all marks from the current selection.
editor.commands.unsetAllMarks()

List Commands

toggleList
(listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType) => boolean
Toggle a list.
editor.commands.toggleList('bulletList', 'listItem')
editor.commands.toggleList('orderedList', 'listItem')
wrapInList
(typeOrName: string | NodeType, attributes?) => boolean
Wrap the current selection in a list.
editor.commands.wrapInList('bulletList')
liftListItem
(typeOrName: string | NodeType) => boolean
Lift a list item out of its parent list.
editor.commands.liftListItem('listItem')
sinkListItem
(typeOrName: string | NodeType) => boolean
Sink a list item into the previous list item.
editor.commands.sinkListItem('listItem')

Creating Custom Commands

You can create custom commands in your extensions:
import { Extension } from '@tiptap/core'

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    myExtension: {
      insertGreeting: () => ReturnType
      setColor: (color: string) => ReturnType
    }
  }
}

export const MyExtension = Extension.create({
  name: 'myExtension',
  
  addCommands() {
    return {
      insertGreeting: () => ({ commands }) => {
        return commands.insertContent('<p>Hello!</p>')
      },
      
      setColor: (color: string) => ({ commands, tr, state }) => {
        // Access to transaction and state
        return commands.updateAttributes('textStyle', { color })
      },
    }
  },
})

Command Props

Commands receive props that give you access to the editor state:
addCommands() {
  return {
    myCommand: (arg1, arg2) => ({ commands, editor, state, tr, dispatch, chain, can }) => {
      // commands: Access to all other commands
      // editor: The editor instance
      // state: Current editor state
      // tr: Current transaction
      // dispatch: Function to dispatch the transaction
      // chain: Create a command chain
      // can: Check if commands can run
      
      return true // Return true if successful
    },
  }
}
Source: /home/daytona/workspace/source/packages/core/src/CommandManager.ts:112

Real Example: setParagraph

Here’s the actual implementation of the setParagraph command from the Paragraph extension:
addCommands() {
  return {
    setParagraph: () => ({ commands }) => {
      return commands.setNode(this.name)
    },
  }
}
Source: /home/daytona/workspace/source/packages/extension-paragraph/src/paragraph.ts:109

Real Example: toggleBold

Here’s the implementation from the Bold extension:
addCommands() {
  return {
    setBold: () => ({ commands }) => {
      return commands.setMark(this.name)
    },
    toggleBold: () => ({ commands }) => {
      return commands.toggleMark(this.name)
    },
    unsetBold: () => ({ commands }) => {
      return commands.unsetMark(this.name)
    },
  }
}
Source: /home/daytona/workspace/source/packages/extension-bold/src/bold.tsx:106

Advanced Command Usage

Conditional Execution

editor
  .chain()
  .focus()
  .command(({ tr, state }) => {
    // Custom command logic
    if (state.selection.empty) {
      return false
    }
    
    // Do something with the transaction
    tr.insertText('Hello')
    
    return true
  })
  .run()

Accessing State in Commands

const customCommand = () => ({ tr, state, dispatch }) => {
  const { selection } = state
  const { from, to } = selection
  
  // Get selected text
  const text = state.doc.textBetween(from, to)
  
  console.log('Selected text:', text)
  
  if (dispatch) {
    tr.insertText(text.toUpperCase(), from, to)
  }
  
  return true
}

editor.commands.command(customCommand)

Checking Command Success

const success = editor
  .chain()
  .focus()
  .toggleBold()
  .toggleItalic()
  .run()

if (!success) {
  console.error('Commands failed to execute')
}

Common Command Patterns

Toggle Formatting

const toggleBold = () => {
  if (editor.can().toggleBold()) {
    editor.chain().focus().toggleBold().run()
  }
}

Set Heading Level

const setHeadingLevel = (level: number) => {
  editor
    .chain()
    .focus()
    .toggleHeading({ level })
    .run()
}
const insertLink = (href: string) => {
  editor
    .chain()
    .focus()
    .extendMarkRange('link')
    .setLink({ href })
    .run()
}
const removeLink = () => {
  editor
    .chain()
    .focus()
    .extendMarkRange('link')
    .unsetLink()
    .run()
}

Toggle List

const toggleBulletList = () => {
  editor
    .chain()
    .focus()
    .toggleBulletList()
    .run()
}

TypeScript Types

import type { 
  SingleCommands, 
  ChainedCommands, 
  CanCommands,
  CommandProps,
} from '@tiptap/core'

// Command function type
type CommandFunction = (props: CommandProps) => boolean

// Declare custom commands
declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    myExtension: {
      myCommand: (arg: string) => ReturnType
    }
  }
}

Best Practices

Always Use Chains for Multiple Commands

Batch multiple commands into a single transaction for better performance and UX.
// Bad: Multiple transactions
editor.commands.toggleBold()
editor.commands.toggleItalic()

// Good: Single transaction
editor.chain().toggleBold().toggleItalic().run()

Check Before Executing

Use can() to check if a command can run before executing it.
if (editor.can().toggleBold()) {
  editor.commands.toggleBold()
}

Focus Before Editing

Always focus the editor before running content commands.
editor.chain().focus().toggleBold().run()

Return True on Success

Custom commands should return true if successful, false otherwise.
myCommand: () => ({ commands }) => {
  const success = commands.insertContent('Hello')
  return success
}

Editor

Learn about the Editor class

Extensions

Learn how to create commands in extensions

Nodes & Marks

Learn about content structure

Schema

Learn about the ProseMirror schema

Build docs developers (and LLMs) love