What are the schemas?
The schema defines the rules and structure for your slide documents. It determines what blocks can exist, where they can go, and how they can nest together.
The basic structure
Every Blockslides document follows this hierarchy:
doc → slide+ → block+ → inline contentdocis the top-level container (always present, always the same)slide+means one or more slides (the+means "at least one")block+means one or more blocks inside each slide- inline content is text with formatting (marks like bold, italic, etc.)
Here's what that looks like in practice:
{
type: "doc",
content: [
{
type: "slide",
attrs: { size: "16x9" },
content: [
{
type: "heading",
content: [{ type: "text", text: "Hello" }]
},
{
type: "paragraph",
content: [{ type: "text", text: "World" }]
}
]
}
]
}The schema enforces this structure. You can't put a paragraph directly in the doc, and you can't put a slide inside another slide.
What the schema controls
The schema defines several important rules:
1. Which blocks can exist
The schema defines all available block types: paragraph, heading, bulletList, image, slide, etc.
If a block type isn't defined in the schema, the editor won't recognize it.
2. What content each block can contain
Every block has a content expression that defines what it can hold:
// Paragraph can contain inline content (text + marks)
paragraph: {
content: 'inline*' // * means "zero or more"
}
// Bullet list must contain list items
bulletList: {
content: 'listItem+' // + means "one or more"
}
// Slide can contain any block
slide: {
content: 'block+' // Must have at least one block
}3. What attributes blocks can have
Blocks can define attributes with default values:
slide: {
attrs: {
size: { default: "16x9" },
className: { default: "" }
}
}
heading: {
attrs: {
level: { default: 1 } // H1, H2, H3, etc.
}
}4. How blocks can nest
The schema determines which blocks can be nested inside others:
// ✅ Valid - follows schema rules
{
type: "slide",
content: [
{ type: "paragraph", content: [...] }
]
}
// ❌ Invalid - paragraphs can't be direct children of doc
{
type: "doc",
content: [
{ type: "paragraph", content: [...] }
]
}
// ❌ Invalid - slides can't nest inside slides
{
type: "slide",
content: [
{ type: "slide", content: [...] }
]
}Schema components
The Blockslides schema is made up of two main parts:
Nodes (blocks)
Structural elements that make up your document:
doc- Top-level document containerslide- Individual slide containerparagraph- Text paragraphsheading- Headings (H1-H6)bulletList,orderedList- ListslistItem- Individual list itemsimage,video- Media elementstext- The actual text content
These are defined in packages/core/src/schema/nodes/.
Marks (inline formatting)
Formatting that can be applied to text:
bold,italic,underlinecode,strikelinktextColor,highlightfontSize,fontFamilysuperscript,subscript
These are defined in packages/core/src/schema/marks/.
Technical note
Blockslides uses ProseMirror's schema system under the hood. The schema is defined at packages/core/src/schema/ and combines node specs and mark specs into a single Schema object.
Why schemas matter
The schema is what makes the editor predictable and reliable:
1. Ensures consistent structure
Every document follows the same rules, so you always know what to expect.
2. Prevents invalid content
You can't accidentally create broken structures—the editor won't allow it.
// The editor prevents this invalid structure
editor.commands.insertContent({
type: "paragraph" // ❌ Can't insert paragraph at doc level
})
// Instead, paragraphs must go inside a slide
editor.commands.insertContent({
type: "slide",
content: [
{ type: "paragraph", content: [...] } // ✅ Valid
]
})3. Enables predictable behavior
Commands, keyboard shortcuts, and UI controls all work with the schema to provide consistent editing experiences.
4. Makes serialization reliable
Because the structure is well-defined, converting to/from JSON, HTML, or Markdown is straightforward and lossless.
Extending the schema
While the base schema is fixed, you can extend it by creating custom extensions that add new nodes or marks.
For example, you could add:
- A
calloutblock for highlighted information - A
videomark for inline video timestamps - A
custom-layoutblock with special rendering
Extensions register their nodes and marks, and the editor automatically incorporates them into the schema.
Learn more about building custom extensions in What are extensions?
Content expressions reference
When you're building custom extensions, you'll work with these content expressions:
inline*- Zero or more inline content (text + marks)inline+- One or more inline contentblock*- Zero or more block-level elementsblock+- One or more block-level elementsslide+- One or more slideslistItem+- One or more list itemstext*- Plain text only
You can combine these to create complex content models:
// A block that can contain either inline content OR blocks
content: '(inline | block)+'
// A block that must contain exactly one image
content: 'image'
// A block that contains paragraphs or headings
content: '(paragraph | heading)+'