Templated

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: craftState is 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:

FieldDescription
type.resolvedNameThe block type — one of the names below
propsBlock-specific properties (documented per block)
parentID of the parent node (absent on ROOT)
nodesOrdered array of child node IDs
isCanvastrue on nodes that accept children (Container, Row, Column)
hidden, linkedNodes, displayName, customEditor bookkeeping — include hidden: false and linkedNodes: {}; displayName/custom are optional labels

Structural rules (enforced by the editor and serializer):

  • ROOT is always a ContainerBlock with isCanvas: true.
  • Direct children of ROOT must be section-level blocks (RowBlock or ContainerBlock).
  • A RowBlock's children are only ColumnBlocks, and the column count must match its layout.
  • SpacerBlock is not valid directly under ROOT — use section padding for gaps between sections.

Block types

TextBlock

Paragraphs and headings.

PropTypeDescription
textstringInner HTML — inline tags only (<strong>, <a>, <br>, <span>), no block-level wrappers
tagNamep | h1 | h2 | h3Semantic tag. Headings scale from the base fontSize: h1 = 2×, h2 = 1.5×, h3 = 1.25× (at the default base of 16px: 32 / 24 / 20)
stylesBlockStyles
responsiveResponsiveSettings

ImageBlock

PropTypeDescription
srcstringImage URL — use the CDN URL from image uploads
altstringAlt text (always set it — many clients block images)
width"full" | "auto" | numberfull fills the column; a number is a pixel width
alignleft | center | right
stylesBlockStyles
responsiveResponsiveSettings
unsplashAttributionobjectOptional — { photographerName, photographerUsername, unsplashPhotoId } for Unsplash images

ButtonBlock

PropTypeDescription
textstringButton label
hrefstringLink URL (can be an ESP merge tag)
variantsolid | outline
stylesBlockStyles + button extrasAdds buttonColor (fill), textColor (label), and buttonGradient (gradient fill, solid variant only — see gradients)
responsiveResponsiveSettings

ContainerBlock

Section-level container (also the ROOT canvas). Accepts children.

PropTypeDescription
stylesBlockStyles
responsiveResponsiveSettings + stackColumnsSee responsive settings

RowBlock

Horizontal section with a column layout. Children must be ColumnBlocks matching the layout's column count.

PropTypeDescription
layoutenumOne 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
columnGapnumberOptional gap between columns (px)
stylesBlockStylesSupports background images and gradients
responsiveResponsiveSettings + stackColumnsstackColumns: true stacks columns vertically on mobile

ColumnBlock

Column inside a Row. Width is determined by the parent Row's layout.

PropTypeDescription
widthnumberPercentage width (informational — the Row layout is authoritative)
verticalAligntop | middle | bottom
stylesBlockStyles
responsiveResponsiveSettings

DividerBlock

PropTypeDescription
stylesBlockStyles + lineColor, lineWidthHorizontal rule color and thickness
responsiveResponsiveSettings

SpacerBlock

PropTypeDescription
heightnumberHeight in px
mobileHeightnumberOptional override on mobile
responsiveResponsiveSettings

SocialIconsBlock

PropTypeDescription
iconsarray{ platformId, url, enabled } per icon. ~65 supported platform IDs, e.g. facebook, x, instagram, linkedin, youtube, tiktok, whatsapp, discord
stylesobjecticonSize (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.

PropTypeDescription
addressstringBusiness physical address (CAN-SPAM requirement)
unsubscribeLabel / unsubscribeHrefstringUnsubscribe link — href is usually an ESP merge tag like {{unsubscribe_url}}
showPreferences, preferencesLabel, preferencesHrefOptional "Manage preferences" link ({{preferences_url}})
showCopyright, companyNameOptional © <year> <company> line
stylesBlockStyles

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.

GroupProperties
BackgroundbackgroundColor, backgroundGradient, backgroundImage, backgroundSize (cover | contain | "<w> <h>"), backgroundPosition, backgroundRepeat
Typographycolor, fontSize, fontFamily, fontWeight (normal | bold | 500 | 600 | 700), textAlign (left | center | right)
Spacingpadding + paddingTop/Bottom/Left/Right, margin + marginTop/Right/Bottom/Left
BorderborderRadius, 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
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

On this page