English
Programmatic Templates
Most users will design templates visually in the drag-and-drop editor. But sometimes you need to create templates in code -- seeding default content, building templates from API data, or generating emails programmatically on the server.
Templatical provides factory functions for every block type. Use them to build templates in code, then pass them to init() or render them directly with the renderer.
Blank template
ts
import { createDefaultTemplateContent } from '@templatical/types';
const content = createDefaultTemplateContent();
// { blocks: [], settings: { width: 600, backgroundColor: '#ffffff', fontFamily: 'Arial' } }createDefaultTemplateContent() accepts an optional font family string:
ts
const content = createDefaultTemplateContent('Georgia, serif');Building a template
Every block type has a corresponding create*Block() function. Each accepts an optional partial object to override defaults. All factory functions auto-generate a unique id for each block.
ts
import {
createDefaultTemplateContent,
createTitleBlock,
createParagraphBlock,
createImageBlock,
createButtonBlock,
createDividerBlock,
} from '@templatical/types';
const content = createDefaultTemplateContent();
content.blocks = [
createTitleBlock({
content: '<h1 style="text-align: center;">Welcome aboard</h1>',
level: 1,
}),
createImageBlock({
src: 'https://example.com/hero.jpg',
alt: 'Welcome hero image',
width: 'full',
}),
createDividerBlock(),
createParagraphBlock({
content: '<p>Thanks for signing up. Here is what happens next.</p>',
}),
createButtonBlock({
text: 'Get Started',
url: 'https://example.com/dashboard',
backgroundColor: '#1a73e8',
textColor: '#ffffff',
borderRadius: 6,
}),
];Block factory reference
Title
ts
createTitleBlock({
content: '<h1>Welcome, {{name}}!</h1>',
level: 1,
textAlign: 'center',
})Paragraph
ts
createParagraphBlock({
content: '<p>Thanks for signing up. Here is what happens next.</p>',
})Image
ts
createImageBlock({
src: 'https://cdn.example.com/hero.png',
alt: 'Hero banner',
width: 560,
linkUrl: 'https://example.com',
})Button
ts
createButtonBlock({
text: 'Get Started',
url: 'https://example.com/signup',
backgroundColor: '#6366f1',
borderRadius: 8,
})Divider
ts
createDividerBlock({
lineStyle: 'dashed',
color: '#e5e7eb',
thickness: 2,
})Spacer
ts
createSpacerBlock({ height: 40 })HTML
ts
createHtmlBlock({
content: '<div style="text-align:center;">Custom markup</div>',
})Social Icons
ts
createSocialIconsBlock({
iconStyle: 'circle',
iconSize: 'large',
icons: [
{ id: crypto.randomUUID(), platform: 'twitter', url: 'https://x.com/acme' },
{ id: crypto.randomUUID(), platform: 'github', url: 'https://github.com/acme' },
],
})Menu
ts
createMenuBlock({
items: [
{ id: crypto.randomUUID(), text: 'Home', url: 'https://example.com', openInNewTab: false, bold: false, underline: false },
{ id: crypto.randomUUID(), text: 'Blog', url: 'https://example.com/blog', openInNewTab: false, bold: false, underline: false },
{ id: crypto.randomUUID(), text: 'Docs', url: 'https://docs.example.com', openInNewTab: true, bold: false, underline: false },
],
separator: '-',
})Table
ts
createTableBlock({
hasHeaderRow: true,
rows: [
{ id: crypto.randomUUID(), cells: [{ id: crypto.randomUUID(), content: 'Plan' }, { id: crypto.randomUUID(), content: 'Price' }] },
{ id: crypto.randomUUID(), cells: [{ id: crypto.randomUUID(), content: 'Starter' }, { id: crypto.randomUUID(), content: '$9/mo' }] },
{ id: crypto.randomUUID(), cells: [{ id: crypto.randomUUID(), content: 'Pro' }, { id: crypto.randomUUID(), content: '$29/mo' }] },
],
})Video
ts
createVideoBlock({
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
thumbnailUrl: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
alt: 'Product demo video',
})Countdown
createCountdownBlock builds a countdown block, but countdown GIFs need Cloud or a blockRenderers.countdown override — the OSS renderer emits a placeholder.
Section
ts
createSectionBlock({
columns: '2',
children: [
[createParagraphBlock({ content: '<p>Left column</p>' })],
[createImageBlock({ src: 'https://cdn.example.com/photo.jpg' })],
],
})The columns property accepts: '1' (single), '2' (two equal), '3' (three equal), '2-1' (two-thirds / one-third), '1-2' (one-third / two-thirds). See Sections and Columns for full details.
Custom
createCustomBlock takes a CustomBlockDefinition (not a partial block). It generates field values from the definition's field defaults. See Custom Blocks for defining custom block types.
Utilities
Generic factory
Create any block by type string:
ts
import { createBlock } from '@templatical/types';
const block = createBlock('title'); // TitleBlock with defaultsCloning
Deep-clone a block with a new ID:
ts
import { cloneBlock } from '@templatical/types';
const copy = cloneBlock(existingBlock);
// copy.id !== existingBlock.idType guards
Narrow a Block union to a specific type:
ts
import { isTitle, isParagraph, isImage, isButton, isSection } from '@templatical/types';
if (isTitle(block)) {
console.log(block.level); // TypeScript knows this is TitleBlock
}
if (isParagraph(block)) {
console.log(block.content); // TypeScript knows this is ParagraphBlock
}
if (isImage(block)) {
console.log(block.src);
}Every block type has a corresponding guard: isTitle(), isParagraph(), isImage(), isButton(), isDivider(), isSpacer(), isHtml(), isSocialIcons(), isMenu(), isTable(), isVideo(), isSection(), isCountdown(), isCustomBlock().
Template settings
Template settings control the global properties of the email:
ts
const content = createDefaultTemplateContent();
content.settings.width = 640;
content.settings.backgroundColor = '#f5f5f5';
content.settings.fontFamily = 'Helvetica, Arial, sans-serif';
content.settings.preheaderText = 'Your weekly digest is here';
content.settings.locale = 'en';
content.settings.direction = 'ltr';| Setting | Type | Description |
|---|---|---|
width | number | Email width in pixels |
backgroundColor | string | Outer background color |
textColor | string | Default text color inherited by all text blocks; per-block colors override. Defaults to #1a1a1a |
linkColor | string | Document link color applied to every link (rich-text and menu); links inherit the text color when unset. Optional |
linkUnderline | boolean | Underline body (rich-text) links. Defaults to true |
fontFamily | string | Default font stack |
preheaderText | string | Preview text shown in inbox list |
locale | string | BCP-47 content language (<html lang>) |
direction | 'ltr' | 'rtl' | Writing direction. Unset follows the content language |
For default values and how to customize them, see Block & Template Defaults.
Loading saved content
Pass previously saved JSON back to the editor:
ts
const res = await fetch('/api/templates/123');
const saved = await res.json();
const editor = await init({
container: '#editor',
content: saved,
});You can also update content after initialization:
ts
editor.setContent(newContent);Next steps
- Block Types -- properties reference for every built-in type.
- How Rendering Works -- the JSON → MJML pipeline.
- Custom Blocks -- define your own block types.