Template Document Reference
The block-based document model behind craftState — every element type, its properties, and a complete worked example.
Template Document Reference
The visual editor stores a template's design in the craftState field as a serialized JSON document. This page documents that structure: the node tree, every block (element) type, shared styling, responsive behavior, and dark mode.
Stability note:
craftStateis the editor's native document format and is versioned with the product. Additive changes (new block types, new optional properties) can happen at any time. If you author documents programmatically, prefer the HTML import endpoint, which produces a valid document for you — and always round-trip test hand-built documents in the editor.
Document structure
craftState is a JSON string. Parsed, it's a flat map of node IDs to nodes — not a nested tree. Parent/child relationships are expressed by ID references:
{
"ROOT": {
"type": { "resolvedName": "ContainerBlock" },
"isCanvas": true,
"props": { "styles": { "backgroundColor": "#ffffff", "padding": 0 } },
"displayName": "Canvas",
"hidden": false,
"linkedNodes": {},
"nodes": ["row1"]
},
"row1": {
"type": { "resolvedName": "RowBlock" },
"isCanvas": true,
"props": { "layout": "100", "styles": { "paddingTop": 16, "paddingBottom": 16 } },
"parent": "ROOT",
"hidden": false,
"linkedNodes": {},
"nodes": ["col1"]
},
"col1": {
"type": { "resolvedName": "ColumnBlock" },
"isCanvas": true,
"props": { "styles": {}, "verticalAlign": "top" },
"parent": "row1",
"hidden": false,
"linkedNodes": {},
"nodes": ["text1"]
},
"text1": {
"type": { "resolvedName": "TextBlock" },
"props": {
"text": "Hello <strong>world</strong>",
"tagName": "h1",
"styles": { "fontSize": 16, "color": "#374151", "textAlign": "center" }
},
"parent": "col1",
"hidden": false,
"linkedNodes": {},
"nodes": []
}
}Every node has:
| Field | Description |
|---|---|
type.resolvedName | The block type — one of the names below |
props | Block-specific properties (documented per block) |
parent | ID of the parent node (absent on ROOT) |
nodes | Ordered array of child node IDs |
isCanvas | true on nodes that accept children (Container, Row, Column) |
hidden, linkedNodes, displayName, custom | Editor bookkeeping — include hidden: false and linkedNodes: {}; displayName/custom are optional labels |
Structural rules (enforced by the editor and serializer):
ROOTis always aContainerBlockwithisCanvas: true.- Direct children of
ROOTmust be section-level blocks (RowBlockorContainerBlock). - A
RowBlock's children are onlyColumnBlocks, and the column count must match itslayout. SpacerBlockis not valid directly underROOT— use section padding for gaps between sections.
Block types
TextBlock
Paragraphs and headings.
| Prop | Type | Description |
|---|---|---|
text | string | Inner HTML — inline tags only (<strong>, <a>, <br>, <span>), no block-level wrappers |
tagName | p | h1 | h2 | h3 | Semantic tag. Headings scale from the base fontSize: h1 = 2×, h2 = 1.5×, h3 = 1.25× (at the default base of 16px: 32 / 24 / 20) |
styles | BlockStyles | |
responsive | ResponsiveSettings |
ImageBlock
| Prop | Type | Description |
|---|---|---|
src | string | Image URL — use the CDN URL from image uploads |
alt | string | Alt text (always set it — many clients block images) |
width | "full" | "auto" | number | full fills the column; a number is a pixel width |
align | left | center | right | |
styles | BlockStyles | |
responsive | ResponsiveSettings | |
unsplashAttribution | object | Optional — { photographerName, photographerUsername, unsplashPhotoId } for Unsplash images |
ButtonBlock
| Prop | Type | Description |
|---|---|---|
text | string | Button label |
href | string | Link URL (can be an ESP merge tag) |
variant | solid | outline | |
styles | BlockStyles + button extras | Adds buttonColor (fill), textColor (label), and buttonGradient (gradient fill, solid variant only — see gradients) |
responsive | ResponsiveSettings |
ContainerBlock
Section-level container (also the ROOT canvas). Accepts children.
| Prop | Type | Description |
|---|---|---|
styles | BlockStyles | |
responsive | ResponsiveSettings + stackColumns | See responsive settings |
RowBlock
Horizontal section with a column layout. Children must be ColumnBlocks matching the layout's column count.
| Prop | Type | Description |
|---|---|---|
layout | enum | One of: 100, 50-50, 33-33-33, 25-25-25-25, 66-33, 33-66, 25-75, 75-25, 25-50-25, 50-25-25, 25-25-50 |
columnGap | number | Optional gap between columns (px) |
styles | BlockStyles | Supports background images and gradients |
responsive | ResponsiveSettings + stackColumns | stackColumns: true stacks columns vertically on mobile |
ColumnBlock
Column inside a Row. Width is determined by the parent Row's layout.
| Prop | Type | Description |
|---|---|---|
width | number | Percentage width (informational — the Row layout is authoritative) |
verticalAlign | top | middle | bottom | |
styles | BlockStyles | |
responsive | ResponsiveSettings |
DividerBlock
| Prop | Type | Description |
|---|---|---|
styles | BlockStyles + lineColor, lineWidth | Horizontal rule color and thickness |
responsive | ResponsiveSettings |
SpacerBlock
| Prop | Type | Description |
|---|---|---|
height | number | Height in px |
mobileHeight | number | Optional override on mobile |
responsive | ResponsiveSettings |
SocialIconsBlock
| Prop | Type | Description |
|---|---|---|
icons | array | { platformId, url, enabled } per icon. ~65 supported platform IDs, e.g. facebook, x, instagram, linkedin, youtube, tiktok, whatsapp, discord |
styles | object | iconSize (px, default 24), iconColor, iconSpacing, alignment, padding, shape (circle | rounded | square), variant (filled | outline | minimal), backgroundColor |
ComplianceFooterBlock
CAN-SPAM footer with unsubscribe links. New templates get one automatically.
| Prop | Type | Description |
|---|---|---|
address | string | Business physical address (CAN-SPAM requirement) |
unsubscribeLabel / unsubscribeHref | string | Unsubscribe link — href is usually an ESP merge tag like {{unsubscribe_url}} |
showPreferences, preferencesLabel, preferencesHref | Optional "Manage preferences" link ({{preferences_url}}) | |
showCopyright, companyName | Optional © <year> <company> line | |
styles | BlockStyles |
WatermarkBlock
The "Built with Templated" footer added on Free-plan accounts. It is locked — it cannot be moved, deleted, or duplicated, and it has no configurable props. Don't author it yourself; the platform manages it.
BlockStyles
Shared styling object accepted by every block's styles prop. All properties are optional; numbers are pixels.
| Group | Properties |
|---|---|
| Background | backgroundColor, backgroundGradient, backgroundImage, backgroundSize (cover | contain | "<w> <h>"), backgroundPosition, backgroundRepeat |
| Typography | color, fontSize, fontFamily, fontWeight (normal | bold | 500 | 600 | 700), textAlign (left | center | right) |
| Spacing | padding + paddingTop/Bottom/Left/Right, margin + marginTop/Right/Bottom/Left |
| Border | borderRadius, borderColor, borderWidth |
When both are set, backgroundImage takes precedence over backgroundGradient. Always pair a background image with a backgroundColor — many email clients block remote images, and the color keeps text readable when they do.
Gradients
backgroundGradient (and buttonGradient on buttons) is:
{ "from": "#F97066", "to": "#F59E0B", "angle": 135 }angle defaults to 135 (top-left → bottom-right). For clients that can't render CSS gradients (Outlook desktop), a solid midpoint-blend fallback color is emitted automatically.
Responsive settings
Every block accepts a responsive prop:
{
"hideOnMobile": false,
"hideOnDesktop": false,
"mobileStyles": { "fontSize": 14, "paddingLeft": 12, "paddingRight": 12 }
}mobileStyles supports per-side padding and fontSize. Container and Row blocks additionally accept stackColumns: true to stack their columns vertically on small screens.
Dark mode
Any block may declare a darkStyles prop with color overrides applied when the recipient's client reports a dark color scheme (prefers-color-scheme: dark, plus an Outlook.com variant):
{ "backgroundColor": "#1C1917", "color": "#E8E6E3", "buttonColor": "#F97066", "textColor": "#ffffff", "lineColor": "#44403C" }A template opts into dark mode simply by declaring darkStyles on at least one block; templates that declare none render identically to before.
Complete example
A minimal one-section email — hero heading, body text, and a CTA button — as a create-template request:
curl -b cookies.txt -X POST \
https://app.templated.email/api/accounts/{slug}/templates \
-H "Content-Type: application/json" \
-d @template.json{
"name": "Minimal Launch Email",
"source": "import",
"editorMode": "visual",
"craftState": "{\"ROOT\":{\"type\":{\"resolvedName\":\"ContainerBlock\"},\"isCanvas\":true,\"props\":{\"styles\":{\"backgroundColor\":\"#ffffff\",\"padding\":0}},\"displayName\":\"Canvas\",\"hidden\":false,\"linkedNodes\":{},\"nodes\":[\"row1\"]},\"row1\":{\"type\":{\"resolvedName\":\"RowBlock\"},\"isCanvas\":true,\"props\":{\"layout\":\"100\",\"styles\":{\"backgroundColor\":\"#ffffff\",\"paddingTop\":32,\"paddingBottom\":32,\"paddingLeft\":24,\"paddingRight\":24}},\"parent\":\"ROOT\",\"hidden\":false,\"linkedNodes\":{},\"nodes\":[\"col1\"]},\"col1\":{\"type\":{\"resolvedName\":\"ColumnBlock\"},\"isCanvas\":true,\"props\":{\"verticalAlign\":\"top\",\"styles\":{}},\"parent\":\"row1\",\"hidden\":false,\"linkedNodes\":{},\"nodes\":[\"heading\",\"body\",\"cta\"]},\"heading\":{\"type\":{\"resolvedName\":\"TextBlock\"},\"props\":{\"text\":\"Launching Soon\",\"tagName\":\"h1\",\"styles\":{\"fontSize\":16,\"color\":\"#111827\",\"textAlign\":\"center\",\"padding\":8}},\"parent\":\"col1\",\"hidden\":false,\"linkedNodes\":{},\"nodes\":[]},\"body\":{\"type\":{\"resolvedName\":\"TextBlock\"},\"props\":{\"text\":\"Something new is on the way. Be the first to know.\",\"tagName\":\"p\",\"styles\":{\"fontSize\":16,\"color\":\"#374151\",\"textAlign\":\"center\",\"padding\":8}},\"parent\":\"col1\",\"hidden\":false,\"linkedNodes\":{},\"nodes\":[]},\"cta\":{\"type\":{\"resolvedName\":\"ButtonBlock\"},\"props\":{\"text\":\"Notify me\",\"href\":\"https://example.com/launch\",\"variant\":\"solid\",\"styles\":{\"buttonColor\":\"#F97066\",\"textColor\":\"#ffffff\",\"borderRadius\":8,\"padding\":12,\"paddingLeft\":24,\"paddingRight\":24,\"textAlign\":\"center\"}},\"parent\":\"col1\",\"hidden\":false,\"linkedNodes\":{},\"nodes\":[]}}"
}Note the craftState value is a JSON string (escaped), not a nested object.
Next steps
- Import HTML — let the platform build the document from raw HTML
- Create and manage templates
- AI generation — generated results return a ready-made document