Learn how Tiptap generates a ProseMirror schema from extensions and how it defines your document structure.
The schema is the heart of your editor’s document structure. It defines what nodes and marks are available, how they can be nested, and what attributes they can have.
{ // The mark type bold: { // Parsing rules parseDOM: [ { tag: 'strong' }, { tag: 'b' }, { style: 'font-weight', getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null } ], // Rendering rules toDOM: () => ['strong', 0], // Whether the mark is inclusive (continues when typing at edge) inclusive: true, // Which marks this mark excludes excludes: undefined }}
import { Node } from '@tiptap/core'// Paragraph: can contain any inline contentexport const Paragraph = Node.create({ name: 'paragraph', group: 'block', content: 'inline*', // Zero or more inline elements})// Heading: can contain inline contentexport const Heading = Node.create({ name: 'heading', group: 'block', content: 'inline*',})// BlockQuote: must contain at least one blockexport const Blockquote = Node.create({ name: 'blockquote', group: 'block', content: 'block+', // One or more block elements})// ListItem: specific content structureexport const ListItem = Node.create({ name: 'listItem', content: 'paragraph block*', // Paragraph followed by zero or more blocks})// Image: no content (leaf node)export const Image = Node.create({ name: 'image', group: 'block', atom: true, // No content expression = cannot contain anything})// Doc: top-level nodeexport const Doc = Node.create({ name: 'doc', topNode: true, content: 'block+', // Must contain at least one block})
import { Editor } from '@tiptap/core'import { Document } from '@tiptap/extension-document'import { Text } from '@tiptap/extension-text'import { Paragraph } from '@tiptap/extension-paragraph'import { Heading } from '@tiptap/extension-heading'import { Bold } from '@tiptap/extension-bold'import { Italic } from '@tiptap/extension-italic'// Create a custom doc node that requires a titleconst CustomDoc = Document.extend({ content: 'heading paragraph+',})const editor = new Editor({ extensions: [ CustomDoc, Text, Paragraph, Heading.configure({ levels: [1] }), // Only h1 Bold, Italic, ], content: { type: 'doc', content: [ { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Title' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Content' }] } ] },})// This document structure is now enforced:// - Must start with a heading (level 1)// - Must have at least one paragraph// - Headings can only be level 1
// Get all mark type namesconst markNames = Object.keys(editor.schema.marks)console.log(markNames)// ['bold', 'italic', 'link', 'code', ...]// Get mark type detailsconst boldType = editor.schema.marks.boldconsole.log(boldType.spec.inclusive) // true
// Get node type from schemaconst headingType = editor.schema.nodes.heading// Use in commandseditor.commands.setNode(headingType, { level: 1 })// Get mark type from schemaconst boldType = editor.schema.marks.bold// Use in commandseditor.commands.toggleMark(boldType)