Renderer
@templatical/renderer converts Templatical template JSON to MJML. Works in both browser and Node.js environments.
The renderer produces MJML only. To compile MJML to HTML for email sending, use any MJML library (mjml for Node.js, spatie/mjml-php for PHP, etc.).
npm install @templatical/rendererrenderToMjml(content, options?)
Renders a TemplateContent object to an MJML string. Returns a Promise<string> — async so custom blocks (which may require async work to resolve) can be rendered in line.
import { renderToMjml } from '@templatical/renderer';
const mjml = await renderToMjml(templateContent);Parameters:
| Parameter | Type | Description |
|---|---|---|
content | TemplateContent | The template to render |
options | RenderOptions | Optional rendering configuration |
Returns: Promise<string> -- MJML markup
RenderOptions
interface RenderOptions {
customFonts?: CustomFont[];
defaultFallbackFont?: string;
allowHtmlBlocks?: boolean; // default: true
renderCustomBlock?: (block: CustomBlock) => Promise<string>;
socialIconsBaseUrl?: string;
}| Option | Default | Description |
|---|---|---|
customFonts | [] | Custom font definitions for <mj-font> declarations in rendered output |
defaultFallbackFont | 'Arial, sans-serif' | Fallback font stack |
allowHtmlBlocks | true | Set to false to strip HTML blocks from output |
renderCustomBlock | -- | Resolves custom blocks to HTML. Called once per custom block. Editor consumers pass editor.renderCustomBlock; headless consumers wire their own resolver. If omitted, custom blocks fall back to the block's renderedHtml field (if present) and otherwise are omitted. |
socialIconsBaseUrl | version-pinned unpkg URL | Base URL (no trailing slash) for the social icon PNG assets. Resolved per icon to ${baseUrl}/${style}/${platform}.png. See Social icons below. |
Custom blocks
When the content tree contains custom blocks, the renderer asks the supplied renderCustomBlock callback to convert each one to HTML. From the editor:
const mjml = await renderToMjml(editor.getContent(), {
renderCustomBlock: editor.renderCustomBlock,
});Headless / Node.js consumers (no editor mounted) can provide their own resolver — for example, running the same Liquid template against the block's fieldValues:
import { Liquid } from 'liquidjs';
const engine = new Liquid();
const definitionsByType = new Map(/* your CustomBlockDefinition list, keyed by type */);
const mjml = await renderToMjml(content, {
async renderCustomBlock(block) {
const definition = definitionsByType.get(block.customType);
if (!definition) return '';
return engine.parseAndRender(definition.template, block.fieldValues);
},
});Social icons
Social icon blocks are emitted as <img src="…/{style}/{platform}.png">. The default socialIconsBaseUrl points at the version-pinned unpkg mirror of @templatical/renderer, which ships pre-rasterized PNGs (16 platforms × 5 styles) alongside the package:
https://unpkg.com/@templatical/renderer@<version>/assets/social/{style}/{platform}.pngWhy PNGs. Outlook desktop (Word rendering engine) does not support SVG and rejects base64 data URIs in <img src>. Hosted PNGs are the only format that renders across every mainstream email client.
Why version-pinned. Email is archival — recipients open messages months or years after they're sent. The version pin freezes the icon visuals at render time so a future redesign or regression in the package doesn't retroactively break already-delivered emails. It also avoids a per-image 302 redirect and unlocks long-lived immutable cache headers.
Self-hosting. Override socialIconsBaseUrl to serve the assets from your own CDN — useful for air-gapped environments, brand-specific theming, or removing the unpkg dependency:
const mjml = await renderToMjml(content, {
socialIconsBaseUrl: 'https://cdn.example.com/email-assets/social',
});The exact filenames the renderer expects are {style}/{platform}.png where style is one of solid | outlined | rounded | square | circle and platform is one of facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance. The shipped 192×192 PNGs are a reasonable starting point if you want to mirror them.
The package also exports DEFAULT_SOCIAL_ICONS_BASE_URL if you want to compose URLs against the same default:
import { DEFAULT_SOCIAL_ICONS_BASE_URL } from '@templatical/renderer';Utilities
The renderer also exports utility functions:
import {
escapeHtml,
escapeAttr,
convertMergeTagsToValues,
isHiddenOnAll,
toPaddingString,
renderBlock,
getCssClassAttr,
getCssClasses,
getWidthPercentages,
getWidthPixels,
DEFAULT_SOCIAL_ICONS_BASE_URL,
RenderContext,
} from '@templatical/renderer';escapeHtml(text)
Escapes HTML entities (<, >, &, ", ') in a string. Use when inserting user content into HTML:
escapeHtml('<script>alert("xss")</script>');
// '<script>alert("xss")</script>'escapeAttr(text)
Escapes a string for safe use in HTML attribute values:
const alt = escapeAttr('Photo of "sunrise" at O\'Hare');
// 'Photo of "sunrise" at O'Hare'convertMergeTagsToValues(html)
Converts merge tag HTML spans (used internally by the editor's rich text system) back into their plain text syntax. The editor stores merge tags as <span data-merge-tag="..."> elements; this function strips the spans and leaves the raw merge tag syntax:
import { convertMergeTagsToValues } from '@templatical/renderer';
// Input: editor's internal HTML format
const editorHtml = '<span data-merge-tag="{{ first_name }}">First Name</span>';
const cleaned = convertMergeTagsToValues(editorHtml);
// Output: '{{ first_name }}'TIP
You typically don't need to call this directly -- the renderer calls it internally when processing title and paragraph blocks. It's exported for advanced use cases where you're working with editor HTML outside the normal rendering pipeline.
isHiddenOnAll(block)
Returns true if a block's visibility has all viewports set to false. Useful for skipping blocks that shouldn't render at all:
if (isHiddenOnAll(block)) {
// Skip this block entirely
}toPaddingString(padding)
Converts a SpacingValue to a CSS padding string:
toPaddingString({ top: 10, right: 20, bottom: 10, left: 20 });
// '10px 20px 10px 20px'renderBlock(block, context)
Renders a single block to its MJML representation. Used internally by renderToMjml() but exported for advanced use cases where you need to render individual blocks.
RenderContext
The context object passed to block renderers. Contains render options and font configuration.
getCssClassAttr(block) / getCssClasses(block)
Generate CSS class attributes from a block's visibility settings. Used internally for responsive hiding.
getWidthPercentages(layout) / getWidthPixels(layout, containerWidth)
Calculate column widths for a given ColumnLayout. Returns an array of percentage or pixel values per column.
DEFAULT_SOCIAL_ICONS_BASE_URL
The default value of RenderOptions.socialIconsBaseUrl — the version-pinned unpkg URL pointing at this package's bundled social icon PNGs. See Social icons.
Compiling MJML to HTML
After rendering to MJML, compile to HTML using any MJML library:
import { renderToMjml } from '@templatical/renderer';
import mjml2html from 'mjml';
const mjml = await renderToMjml(templateContent);
const { html } = mjml2html(mjml);
// html is ready to send via your email serviceSee How Rendering Works for more on the rendering pipeline.