Skip to content

Types Reference

@templatical/types provides all shared TypeScript types, block factory functions, and utilities.

bash
npm install @templatical/types
bash
pnpm add @templatical/types
bash
yarn add @templatical/types
bash
bun add @templatical/types

Template Structure

TemplateContent

The root type representing an email template.

ts
interface TemplateContent {
  blocks: Block[];
  settings: TemplateSettings;
}

TemplateSettings

ts
interface TemplateSettings {
  width: number;                  // Template width in pixels (default: 600)
  backgroundColor: string;        // Background color
  textColor: string;              // Default text color; text blocks inherit it (default #1a1a1a)
  linkColor?: string;             // Link color; links inherit the text color when unset
  linkUnderline: boolean;         // Underline body links (default true)
  fontFamily: string;             // Default font family
  preheaderText?: string;         // Email preheader text
  locale: string;                 // BCP-47 content language; <html lang> (default 'en')
  direction?: ContentDirection;   // 'ltr' | 'rtl'; unset follows the content language
}

type ContentDirection = 'ltr' | 'rtl';

Block

A discriminated union of all block types:

ts
type Block =
  | TitleBlock
  | ParagraphBlock
  | ImageBlock
  | ButtonBlock
  | SectionBlock
  | DividerBlock
  | VideoBlock
  | SpacerBlock
  | SocialIconsBlock
  | MenuBlock
  | TableBlock
  | HtmlBlock
  | CountdownBlock
  | CustomBlock;

BlockType

ts
type BlockType =
  | 'title' | 'paragraph' | 'image' | 'button' | 'section'
  | 'divider' | 'video' | 'spacer' | 'social'
  | 'menu' | 'table' | 'html' | 'countdown' | 'custom';

Base Types

BaseBlock

All blocks extend this interface.

ts
interface BaseBlock {
  id: string;
  type: string;
  styles: BlockStyles;
  visibility?: BlockVisibility;
  displayCondition?: DisplayCondition;
}

BlockStyles

ts
interface BlockStyles {
  padding: SpacingValue;
  backgroundColor?: string;
}

SpacingValue

ts
interface SpacingValue {
  top: number;
  right: number;
  bottom: number;
  left: number;
}

BlockVisibility

Controls on which viewports a block is visible.

ts
interface BlockVisibility {
  desktop: boolean;
  mobile: boolean;
}

Block Types

See Blocks Guide for detailed descriptions of each block type.

TitleBlock

ts
interface TitleBlock extends BaseBlock {
  type: 'title';
  content: string;          // HTML content
  level: 1 | 2 | 3 | 4;   // H1=36px, H2=28px, H3=22px, H4=18px
  color: string;
  textAlign: 'left' | 'center' | 'right';
  fontFamily?: string;
}

ParagraphBlock

ts
interface ParagraphBlock extends BaseBlock {
  type: 'paragraph';
  content: string;          // HTML content (all formatting is inline via TipTap)
}

ImageBlock

ts
interface ImageBlock extends BaseBlock {
  type: 'image';
  src: string;
  alt: string;
  width: number | 'full';
  /** Absent derives the height from the width, keeping the aspect ratio. */
  height?: number;
  align: 'left' | 'center' | 'right';
  /** Corner radius in px. Omitted/0 = square corners. */
  borderRadius?: number;
  linkUrl?: string;
  linkOpenInNewTab?: boolean;
  placeholderUrl?: string;
  decorative?: boolean;
}

ButtonBlock

ts
interface ButtonBlock extends BaseBlock {
  type: 'button';
  text: string;
  url: string;
  backgroundColor: string;
  textColor: string;
  borderRadius: number;
  fontSize: number;
  buttonPadding: SpacingValue;
  fontFamily?: string;
  openInNewTab?: boolean;
  width?: number | 'full';
  align: 'left' | 'center' | 'right';
}

SectionBlock

Container for multi-column layouts.

ts
interface SectionBlock extends BaseBlock {
  type: 'section';
  columns: ColumnLayout;
  children: Block[][];      // Array of columns, each containing blocks
  stackOnMobile?: boolean;  // absent/true: columns stack on mobile (MJML default).
                            // false: rendered as <mj-group> so they stay side-by-side.
}

type ColumnLayout = '1' | '2' | '3' | '2-1' | '1-2';

DividerBlock

ts
interface DividerBlock extends BaseBlock {
  type: 'divider';
  lineStyle: 'solid' | 'dashed' | 'dotted';
  color: string;
  thickness: number;
  width: number | 'full';
}

VideoBlock

Renders as a linked thumbnail image -- email clients do not support embedded playback.

ts
interface VideoBlock extends BaseBlock {
  type: 'video';
  url: string;
  openInNewTab?: boolean;
  thumbnailUrl: string;   // Auto-derived from a YouTube/Vimeo url when empty
  alt: string;
  width: number | 'full';
  /** Absent derives the height from the width, keeping the aspect ratio. */
  height?: number;
  align: 'left' | 'center' | 'right';
  placeholderUrl?: string;
}

SpacerBlock

ts
interface SpacerBlock extends BaseBlock {
  type: 'spacer';
  height: number;
}

SocialIconsBlock

ts
interface SocialIconsBlock extends BaseBlock {
  type: 'social';
  icons: SocialIcon[];
  iconStyle: SocialIconStyle;
  iconSize: SocialIconSize;
  spacing: number;
  align: 'left' | 'center' | 'right';
}

interface SocialIcon {
  id: string;
  platform: SocialPlatform;
  url: string;
}

type SocialPlatform =
  | 'facebook' | 'twitter' | 'instagram' | 'linkedin'
  | 'youtube' | 'tiktok' | 'pinterest' | 'email'
  | 'whatsapp' | 'telegram' | 'discord' | 'snapchat'
  | 'reddit' | 'github' | 'dribbble' | 'behance'
  | 'website';


type SocialIconStyle = 'solid' | 'outlined' | 'rounded' | 'square' | 'circle';
type SocialIconSize = 'small' | 'medium' | 'large';
ts
interface MenuBlock extends BaseBlock {
  type: 'menu';
  items: MenuItemData[];
  fontSize: number;
  fontFamily?: string;
  color: string;
  linkColor?: string;
  textAlign: 'left' | 'center' | 'right';
  separator: string;
  separatorColor: string;
  spacing: number;
}

interface MenuItemData {
  id: string;
  text: string;
  url: string;
  openInNewTab: boolean;
  bold: boolean;
  underline: boolean;
  color?: string;
}

TableBlock

ts
interface TableBlock extends BaseBlock {
  type: 'table';
  rows: TableRowData[];
  hasHeaderRow: boolean;
  headerBackgroundColor?: string;
  borderColor: string;
  borderWidth: number;
  cellPadding: number;
  fontSize: number;
  fontFamily?: string;
  color: string;
  textAlign: 'left' | 'center' | 'right';
}

interface TableRowData {
  id: string;
  cells: TableCellData[];
}

interface TableCellData {
  id: string;
  content: string;
}

HtmlBlock

ts
interface HtmlBlock extends BaseBlock {
  type: 'html';
  content: string;          // Raw HTML
}

CountdownBlock

Renders as an animated GIF, which requires the Templatical Cloud backend -- @templatical/renderer emits a templatical:unrenderable-block marker instead. See Blocks with no renderer.

ts
interface CountdownBlock extends BaseBlock {
  type: 'countdown';
  targetDate: string;       // Local date/time, 'YYYY-MM-DDTHH:mm'
  timezone: string;         // IANA name the target is read in, e.g. 'Europe/Berlin'
  showDays: boolean;
  showHours: boolean;
  showMinutes: boolean;
  showSeconds: boolean;
  separator: ':' | '-' | ' ';
  digitFontSize: number;
  digitColor: string;
  labelColor: string;
  labelFontSize: number;
  backgroundColor: string;
  fontFamily?: string;
  labelDays: string;
  labelHours: string;
  labelMinutes: string;
  labelSeconds: string;
  expiredMessage: string;
  expiredImageUrl: string;  // Shown instead of the timer once expired
  hideOnExpiry: boolean;
}

CustomBlock

ts
interface CustomBlock extends BaseBlock {
  type: 'custom';
  customType: string;
  fieldValues: Record<string, unknown>;
  renderedHtml?: string;
  dataSourceFetched?: boolean;
}

Configuration Types

MergeTag

ts
interface MergeTag {
  label: string;
  value: string;
  group?: string;        // picker-only grouping label
  description?: string;  // picker-only helper text
  sample?: string;       // preview-only example value; never written to MJML
}

interface MergeTagRequestContext {
  reason: 'insert' | 'edit';
  current?: MergeTag;    // on edit, when the token matches tags
}

MergeTagsConfig

ts
interface MergeTagsConfig {
  syntax?: SyntaxPresetName | SyntaxPreset;
  tags?: MergeTag[];
  onRequest?: (context?: MergeTagRequestContext) => Promise<MergeTag | null>;
  showRawValue?: boolean;   // reveal raw token in tooltip (default true)
  autocomplete?: boolean;   // typing-based autocomplete (default true)
}

type SyntaxPresetName = 'liquid' | 'handlebars' | 'mailchimp' | 'ampscript';

DisplayCondition

ts
interface DisplayCondition {
  label: string;
  before: string;
  after: string;
  group?: string;
  description?: string;
}

DisplayConditionsConfig

ts
interface DisplayConditionsConfig {
  conditions: DisplayCondition[];
  allowCustom?: boolean;
}

CustomBlockDefinition

ts
interface CustomBlockDefinition {
  type: string;
  name: string;
  icon?: string;
  description?: string;
  fields: CustomBlockField[];
  template: string;                       // Liquid template
  dataSource?: DataSourceConfig;
  defaultStyles?: Partial<BlockStyles>;   // styles applied to new instances
  stylesheet?: string;                    // CSS emitted once into mj-head
}

See Custom Blocks for field type details.

ThemeOverrides

ts
interface ThemeOverrides {
  bg?: string;
  bgElevated?: string;
  bgHover?: string;
  bgActive?: string;
  border?: string;
  borderLight?: string;
  text?: string;
  textMuted?: string;
  textDim?: string;
  primary?: string;
  primaryHover?: string;
  primaryLight?: string;
  secondary?: string;
  secondaryHover?: string;
  secondaryLight?: string;
  success?: string;
  successLight?: string;
  warning?: string;
  warningLight?: string;
  danger?: string;
  dangerLight?: string;
  canvasBg?: string;
  dark?: Omit<ThemeOverrides, 'dark'>;
}

FontsConfig

ts
interface FontsConfig {
  defaultFallback?: string;
  defaultFont?: string;
  customFonts?: CustomFont[];
  builtIns?: boolean | string[];  // true/omitted: all seven; false: none; string[]: allowlist — see /guide/fonts
}

interface CustomFont {
  name: string;
  url: string;
  fallback?: string;
}

ViewportSize

ts
type ViewportSize = 'desktop' | 'mobile';

Factory Functions

All factory functions accept an optional partial override object and return a complete block with a generated ID.

ts
import {
  createTitleBlock,
  createParagraphBlock,
  createImageBlock,
  createButtonBlock,
  createSectionBlock,
  createDividerBlock,
  createVideoBlock,
  createSpacerBlock,
  createSocialIconsBlock,
  createMenuBlock,
  createTableBlock,
  createHtmlBlock,
  createCountdownBlock,
  createCustomBlock,
  createBlock,
  cloneBlock,
  createDefaultTemplateContent,
  generateId,
} from '@templatical/types';

// Countdown GIFs need Cloud or a `blockRenderers.countdown` override; the OSS renderer emits a placeholder.

// Create with defaults
const paragraph = createParagraphBlock();

// Create with overrides
const heading = createTitleBlock({
  content: '<h1>Hello</h1>',
  level: 1,
});

// Create any block by type string
const block = createBlock('button');

// Deep clone with new ID
const copy = cloneBlock(existingBlock);

// Empty template
const template = createDefaultTemplateContent();

// Generate a UUID
const id = generateId();

Type Guards

ts
import {
  isTitle, isParagraph, isImage, isButton, isSection,
  isDivider, isVideo, isSpacer, isSocialIcons,
  isMenu, isTable, isHtml, isCountdown, isCustomBlock,
} from '@templatical/types';

if (isTitle(block)) {
  console.log(block.level); // TypeScript knows this is TitleBlock
}

EventEmitter

A typed event emitter for subscription patterns.

ts
import { EventEmitter } from '@templatical/types';

type Events = {
  change: TemplateContent;
  select: string | null;
};

const emitter = new EventEmitter<Events>();

const unsubscribe = emitter.on('change', (content) => {
  console.log('Changed:', content);
});

emitter.emit('change', templateContent);
unsubscribe(); // Remove listener

Methods

MethodDescription
on(event, handler)Subscribe. Returns unsubscribe function
off(event, handler)Unsubscribe a specific handler
emit(event, data)Emit an event
removeAllListeners(event?)Remove all listeners, optionally for a specific event
listenerCount(event)Number of listeners for an event

Storage contracts

Template, TemplatePatch, SavedBlockInput, CommentsProvider, MediaProvider and the rest of the BYO storage shapes live under Connect your backend. This page is the block model and editor config.