---
title: Programmatic Templates
description: Build email template content programmatically using factory functions.
---

# 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](/guide/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](/guide/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 defaults
```

### Cloning

Deep-clone a block with a new ID:

```ts
import { cloneBlock } from '@templatical/types';

const copy = cloneBlock(existingBlock);
// copy.id !== existingBlock.id
```

### Type 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](/guide/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](/guide/blocks) -- properties reference for every built-in type.
- [How Rendering Works](/getting-started/how-rendering-works) -- the JSON → MJML pipeline.
- [Custom Blocks](/guide/custom-blocks) -- define your own block types.
