Blocks
Block Persistence Model
Vextro uses an inline-first persistence model for blocks. Understanding this model helps when building custom renderers, debugging save/load issues, or designing complex nested layouts.
Inline vs reference blocks
Every block in the editor tree is either inline or a reference:
| Kind | Where data lives | When used |
|---|---|---|
| Inline | Embedded in the parent (document field or parent block's children array) | Default for new blocks. Data, children, and layout are all in-place. |
| Reference | On a separate block row in the database | When a block is "saved as shared." The document stores a thin pointer; data lives on the block row. |
New blocks start inline. Authors can explicitly promote a block to shared (reference), at which point a block row is created and the inline entry becomes a thin pointer.
Mixed trees
A single block tree can mix both kinds at any level:
Document field
├── Inline hero block (data embedded here)
│ └── Reference CTA (pointer → block row j97x)
└── Reference Layout (pointer → block row k82m)
├── Inline text block (data on block row k82m's children)
└── Reference CTA (pointer → block row ghi7) The rule is simple: follow the kind. If inline, everything is in the parent. If reference, the entry is a thin pointer — follow the blockId to the block row.
Save ordering
On document save, within a single Convex mutation:
- Serialize the
EditorBlock[]tree toBlockEntry[](inline data + reference pointers) - Walk the tree depth-first (leaves first)
- For each reference block: create or update its block row
- Replace any temporary IDs with real Convex document IDs
- Write the document field
- Sync block usage tracking
All writes happen in one Convex mutation — if any step fails, everything rolls back.
Grid zone persistence
Layout blocks in grid mode use gridZones — a map from cell keys (e.g., r0c0) to arrays of block IDs placed in that cell. This is stored on the Layout block (inline or block row) alongside the children array. See the Layout block docs for details.
Vextro preserves a valid gridZones map across unrelated edits, copies, and save/reload cycles. Invalid or incomplete placement data is normalized at the editor boundary so every child remains reachable without rewriting valid placements.