0
# Document Model and Schema
1
2
The document model provides the core data structures for representing rich text documents and defining their allowed structure through schemas. This forms the foundation of ProseMirror's content management system.
3
4
## Capabilities
5
6
### Schema Definition
7
8
Defines the structure and rules for documents, including what nodes and marks are allowed.
9
10
```typescript { .api }
11
/**
12
* Schema defines the structure of documents and available node/mark types
13
*/
14
class Schema {
15
/**
16
* Create a new schema from a specification
17
* @param spec - Schema specification defining nodes, marks, and rules
18
*/
19
constructor(spec: SchemaSpec);
20
21
/** The defined node types, keyed by name */
22
readonly nodes: { [name: string]: NodeType };
23
24
/** The defined mark types, keyed by name */
25
readonly marks: { [name: string]: MarkType };
26
27
/** The schema specification this was created from */
28
readonly spec: SchemaSpec;
29
30
/** Get a node type by name */
31
node(name: string): NodeType;
32
33
/** Get a mark type by name */
34
mark(name: string): MarkType;
35
36
/** Get the default top-level node type */
37
readonly topNodeType: NodeType;
38
39
/** Check if a given text can appear at the given position */
40
text(text: string, marks?: Mark[]): Node | null;
41
}
42
43
interface SchemaSpec {
44
/** Object mapping node names to node specs */
45
nodes: { [name: string]: NodeSpec };
46
47
/** Object mapping mark names to mark specs */
48
marks?: { [name: string]: MarkSpec };
49
50
/** The name of the default top-level node type */
51
topNode?: string;
52
}
53
```
54
55
**Usage Examples:**
56
57
```typescript
58
import { Schema } from "@tiptap/pm/model";
59
60
// Create a simple schema
61
const mySchema = new Schema({
62
nodes: {
63
doc: { content: "paragraph+" },
64
paragraph: { content: "text*", group: "block" },
65
text: { group: "inline" },
66
},
67
marks: {
68
strong: {},
69
em: {},
70
},
71
});
72
73
// Get node types
74
const docType = mySchema.nodes.doc;
75
const paragraphType = mySchema.nodes.paragraph;
76
```
77
78
### Node Types
79
80
Define the characteristics and behavior of different node types in the document.
81
82
```typescript { .api }
83
/**
84
* A node type represents a kind of node that may occur in a document
85
*/
86
class NodeType {
87
/** The name of this node type */
88
readonly name: string;
89
90
/** The schema this node type belongs to */
91
readonly schema: Schema;
92
93
/** The spec this node type was created from */
94
readonly spec: NodeSpec;
95
96
/** True if this node type is inline */
97
readonly isInline: boolean;
98
99
/** True if this node type is a block */
100
readonly isBlock: boolean;
101
102
/** True if this node is an atom (cannot have content edited directly) */
103
readonly isAtom: boolean;
104
105
/** True if this node type is the schema's top node type */
106
readonly isTop: boolean;
107
108
/** True if this node allows marks */
109
readonly allowsMarks: boolean;
110
111
/**
112
* Create a node of this type
113
* @param attrs - The node's attributes
114
* @param content - The node's content
115
* @param marks - The node's marks
116
*/
117
create(attrs?: Attrs, content?: Fragment | Node | Node[], marks?: Mark[]): Node;
118
119
/**
120
* Create a node with the given content and attributes, checking validity
121
*/
122
createChecked(attrs?: Attrs, content?: Fragment | Node | Node[], marks?: Mark[]): Node;
123
124
/**
125
* Create a node and fill any required children
126
*/
127
createAndFill(attrs?: Attrs, content?: Fragment | Node | Node[], marks?: Mark[]): Node | null;
128
}
129
130
interface NodeSpec {
131
/** Expression describing allowed content */
132
content?: string;
133
134
/** Expression describing allowed marks */
135
marks?: string;
136
137
/** Nodes group this node belongs to */
138
group?: string;
139
140
/** Whether this node type is inline */
141
inline?: boolean;
142
143
/** Whether this node is an atom */
144
atom?: boolean;
145
146
/** Whether this node is selectable */
147
selectable?: boolean;
148
149
/** Whether this node can be dragged */
150
draggable?: boolean;
151
152
/** Node's attributes */
153
attrs?: { [name: string]: AttributeSpec };
154
155
/** Rules for parsing DOM to this node */
156
parseDOM?: ParseRule[];
157
158
/** Function to serialize this node to DOM */
159
toDOM?: (node: Node) => DOMOutputSpec;
160
}
161
```
162
163
### Document Nodes
164
165
Represents individual nodes in the document tree structure.
166
167
```typescript { .api }
168
/**
169
* A node in a ProseMirror document
170
*/
171
class Node {
172
/** The node's type */
173
readonly type: NodeType;
174
175
/** The node's attributes */
176
readonly attrs: Attrs;
177
178
/** The node's content as a Fragment */
179
readonly content: Fragment;
180
181
/** The marks applied to this node */
182
readonly marks: Mark[];
183
184
/** The number of children this node has */
185
readonly childCount: number;
186
187
/** The size of this node */
188
readonly nodeSize: number;
189
190
/** True if this node is an atom */
191
readonly isAtom: boolean;
192
193
/** True if this node is inline */
194
readonly isInline: boolean;
195
196
/** True if this node is a block */
197
readonly isBlock: boolean;
198
199
/** True if this node is a text node */
200
readonly isText: boolean;
201
202
/** True if this node is a leaf (no children) */
203
readonly isLeaf: boolean;
204
205
/**
206
* Get the child node at the given index
207
*/
208
child(index: number): Node;
209
210
/**
211
* Get the child node at the given offset
212
*/
213
childAfter(pos: number): { node: Node; index: number; offset: number } | null;
214
215
/**
216
* Get the child node before the given offset
217
*/
218
childBefore(pos: number): { node: Node; index: number; offset: number } | null;
219
220
/**
221
* Call a function for each child node
222
*/
223
forEach(f: (node: Node, offset: number, index: number) => void): void;
224
225
/**
226
* Create a new node with the same type and attributes but different content
227
*/
228
copy(content?: Fragment): Node;
229
230
/**
231
* Check if this node has the same markup as another
232
*/
233
sameMarkup(other: Node): boolean;
234
235
/**
236
* Get a text representation of this node
237
*/
238
textContent: string;
239
240
/**
241
* Resolve a position in this node's content
242
*/
243
resolve(pos: number): ResolvedPos;
244
245
/**
246
* Find all positions in this node that match a predicate
247
*/
248
descendants(f: (node: Node, pos: number, parent: Node) => boolean | void): void;
249
}
250
```
251
252
### Document Fragments
253
254
Represents collections of nodes, used for document content.
255
256
```typescript { .api }
257
/**
258
* A fragment represents a node's collection of child nodes
259
*/
260
class Fragment {
261
/** The number of child nodes in this fragment */
262
readonly size: number;
263
264
/** The combined size of all nodes in this fragment */
265
readonly nodeSize: number;
266
267
/**
268
* Create a fragment from an array of nodes
269
*/
270
static from(nodes?: Node[] | Node | Fragment | null): Fragment;
271
272
/** The empty fragment */
273
static empty: Fragment;
274
275
/**
276
* Get the child node at the given index
277
*/
278
child(index: number): Node;
279
280
/**
281
* Call a function for each child node
282
*/
283
forEach(f: (node: Node, offset: number, index: number) => void): void;
284
285
/**
286
* Find the first position at which this fragment differs from another
287
*/
288
findDiffStart(other: Fragment, pos?: number): number | null;
289
290
/**
291
* Find the last position at which this fragment differs from another
292
*/
293
findDiffEnd(other: Fragment, pos?: number, otherPos?: number): { a: number; b: number } | null;
294
295
/**
296
* Create new fragment with nodes appended
297
*/
298
append(other: Fragment): Fragment;
299
300
/**
301
* Cut out the part of the fragment between the given positions
302
*/
303
cut(from: number, to?: number): Fragment;
304
305
/**
306
* Replace the part between from and to with the given fragment
307
*/
308
replaceChild(index: number, node: Node): Fragment;
309
310
/**
311
* Get the text content of the fragment
312
*/
313
readonly textContent: string;
314
315
/**
316
* Convert the fragment to a JSON representation
317
*/
318
toJSON(): any;
319
320
/**
321
* Create a fragment from a JSON representation
322
*/
323
static fromJSON(schema: Schema, json: any): Fragment;
324
}
325
```
326
327
### Mark Types and Marks
328
329
Define and represent text annotations like bold, italic, links, etc.
330
331
```typescript { .api }
332
/**
333
* A mark type represents a type of mark that can be applied to text
334
*/
335
class MarkType {
336
/** The name of this mark type */
337
readonly name: string;
338
339
/** The schema this mark type belongs to */
340
readonly schema: Schema;
341
342
/** The spec this mark type was created from */
343
readonly spec: MarkSpec;
344
345
/**
346
* Create a mark of this type
347
*/
348
create(attrs?: Attrs): Mark;
349
350
/**
351
* Check if the given set of marks has a mark of this type
352
*/
353
isInSet(set: Mark[]): Mark | null;
354
355
/**
356
* Remove marks of this type from the given set
357
*/
358
removeFromSet(set: Mark[]): Mark[];
359
}
360
361
/**
362
* A mark represents a piece of information attached to inline content
363
*/
364
class Mark {
365
/** The mark's type */
366
readonly type: MarkType;
367
368
/** The mark's attributes */
369
readonly attrs: Attrs;
370
371
/**
372
* Add this mark to a set of marks
373
*/
374
addToSet(set: Mark[]): Mark[];
375
376
/**
377
* Remove this mark from a set of marks
378
*/
379
removeFromSet(set: Mark[]): Mark[];
380
381
/**
382
* Check if this mark is in a set of marks
383
*/
384
isInSet(set: Mark[]): boolean;
385
386
/**
387
* Check if this mark has the same type and attributes as another
388
*/
389
eq(other: Mark): boolean;
390
391
/**
392
* Convert the mark to a JSON representation
393
*/
394
toJSON(): any;
395
396
/**
397
* Create a mark from a JSON representation
398
*/
399
static fromJSON(schema: Schema, json: any): Mark;
400
}
401
402
interface MarkSpec {
403
/** Mark's attributes */
404
attrs?: { [name: string]: AttributeSpec };
405
406
/** Whether this mark should be inclusive (continue when typing at boundaries) */
407
inclusive?: boolean;
408
409
/** Other mark types this mark excludes */
410
excludes?: string;
411
412
/** Group this mark belongs to */
413
group?: string;
414
415
/** Rules for parsing DOM to this mark */
416
parseDOM?: ParseRule[];
417
418
/** Function to serialize this mark to DOM */
419
toDOM?: (mark: Mark, inline: boolean) => DOMOutputSpec;
420
}
421
```
422
423
### Position Resolution
424
425
Provides detailed information about positions within documents.
426
427
```typescript { .api }
428
/**
429
* Represents a position in a document with context information
430
*/
431
class ResolvedPos {
432
/** The position this was resolved from */
433
readonly pos: number;
434
435
/** Path to this position */
436
readonly path: (Node | number)[];
437
438
/** The depth of this position */
439
readonly depth: number;
440
441
/** The parent node at the outermost level */
442
readonly parent: Node;
443
444
/** The document this position is in */
445
readonly doc: Node;
446
447
/**
448
* Get the parent node at the given depth
449
*/
450
node(depth?: number): Node;
451
452
/**
453
* Get the index at the given depth
454
*/
455
index(depth?: number): number;
456
457
/**
458
* Get the index after this position at the given depth
459
*/
460
indexAfter(depth?: number): number;
461
462
/**
463
* Get the start position of the parent at the given depth
464
*/
465
start(depth?: number): number;
466
467
/**
468
* Get the end position of the parent at the given depth
469
*/
470
end(depth?: number): number;
471
472
/**
473
* Get the position before this position
474
*/
475
before(depth?: number): number;
476
477
/**
478
* Get the position after this position
479
*/
480
after(depth?: number): number;
481
482
/**
483
* Get the text offset of this position
484
*/
485
textOffset: number;
486
487
/**
488
* Get the marks active at this position
489
*/
490
marks(): Mark[];
491
}
492
```
493
494
### DOM Parsing and Serialization
495
496
Convert between DOM and ProseMirror document structures.
497
498
```typescript { .api }
499
/**
500
* Parser for converting DOM content to ProseMirror documents
501
*/
502
class DOMParser {
503
/** The schema this parser uses */
504
readonly schema: Schema;
505
506
/** The parsing rules */
507
readonly rules: ParseRule[];
508
509
/**
510
* Create a DOM parser
511
*/
512
static fromSchema(schema: Schema): DOMParser;
513
514
/**
515
* Parse a DOM node to a ProseMirror document
516
*/
517
parse(dom: Element, options?: ParseOptions): Node;
518
519
/**
520
* Parse a DOM node to a ProseMirror fragment
521
*/
522
parseFragment(dom: Element, options?: ParseOptions): Fragment;
523
524
/**
525
* Parse content from a string
526
*/
527
parseSlice(dom: Element, options?: ParseOptions): Slice;
528
}
529
530
/**
531
* Serializer for converting ProseMirror documents to DOM
532
*/
533
class DOMSerializer {
534
/** Node serialization functions */
535
readonly nodes: { [name: string]: (node: Node) => DOMOutputSpec };
536
537
/** Mark serialization functions */
538
readonly marks: { [name: string]: (mark: Mark, inline: boolean) => DOMOutputSpec };
539
540
/**
541
* Create a DOM serializer from a schema
542
*/
543
static fromSchema(schema: Schema): DOMSerializer;
544
545
/**
546
* Serialize a fragment to DOM
547
*/
548
serializeFragment(fragment: Fragment, options?: { document?: Document }): DocumentFragment;
549
550
/**
551
* Serialize a node to DOM
552
*/
553
serializeNode(node: Node, options?: { document?: Document }): Element;
554
}
555
556
interface ParseRule {
557
/** Tag name to match */
558
tag?: string;
559
560
/** CSS selector to match */
561
match?: (node: Element) => boolean;
562
563
/** Priority of this rule */
564
priority?: number;
565
566
/** Node type to create */
567
node?: string;
568
569
/** Mark type to create */
570
mark?: string;
571
572
/** Whether to ignore content */
573
ignore?: boolean;
574
575
/** How to get attributes */
576
attrs?: Attrs | ((node: Element) => Attrs);
577
578
/** How to handle content */
579
contentElement?: string | ((node: Element) => Element);
580
}
581
582
interface ParseOptions {
583
/** Whether to preserve whitespace */
584
preserveWhitespace?: boolean | "full";
585
586
/** Node to find parsing position in */
587
findPositions?: { node: Element; offset: number }[];
588
589
/** Starting position */
590
from?: number;
591
592
/** Ending position */
593
to?: number;
594
}
595
596
type DOMOutputSpec = string | Element | [string, ...any[]];
597
```
598
599
## Types
600
601
```typescript { .api }
602
interface AttributeSpec {
603
/** Default value for this attribute */
604
default?: any;
605
606
/** Validation function */
607
validate?: (value: any) => boolean;
608
}
609
610
type Attrs = { [key: string]: any };
611
612
interface Slice {
613
/** The content of the slice */
614
content: Fragment;
615
616
/** Open depth at the start */
617
openStart: number;
618
619
/** Open depth at the end */
620
openEnd: number;
621
}
622
```