Configuration
The Templatical SDK offers various configuration options to customize its behavior and appearance.
Basic Configuration
import { init } from '@templatical/embedded';
const editor = await init({
container: '#email-editor',
auth: {
url: 'https://your-app.com/api/token',
},
});Required Options
| Option | Type | Description |
|---|---|---|
container | string | HTMLElement | CSS selector or DOM element for the editor |
auth.url | string | URL of your token endpoint (see Authentication) |
Options
All options below are passed inside the init() call alongside the required options:
const editor = await init({
container: '#email-editor',
auth: { url: 'https://your-app.com/api/token' },
// ...options
});locale
Sets the UI language. Defaults to 'en'.
locale: 'de',Supported locales: en (English), de (German). You can also use full locale codes like en-GB or de-AT — the SDK will match the base language.
mergeTags
Configures merge tag detection and display. When the editor detects a matching value in the template content, it displays the human-readable label instead. This makes merge tags like {{first_name}} appear as friendly labels (e.g., "First Name") in the editor UI.
mergeTags: {
tags: [
{ label: 'First Name', value: '{{first_name}}' },
{ label: 'Last Name', value: '{{last_name}}' },
{ label: 'Email', value: '{{email}}' },
{ label: 'Unsubscribe URL', value: '{{unsubscribe_url}}' },
],
},| Property | Type | Description |
|---|---|---|
tags | MergeTag[] | List of known merge tags |
tags[].label | string | Display name shown in the editor when the merge tag is detected |
tags[].value | string | The merge tag to match in the template content |
syntax | string | SyntaxPreset | Syntax preset name or custom regex (default: 'liquid') |
The syntax option controls how the editor detects merge tags typed or pasted into content. Built-in presets: 'liquid' (default), 'handlebars', 'mailchimp', 'ampscript', 'django'.
// Use Handlebars syntax
mergeTags: {
syntax: 'handlebars',
tags: [
{ label: 'First Name', value: '{{first_name}}' },
],
},
// Use custom regex patterns
mergeTags: {
syntax: {
value: /\$\{.+?\}/g, // Matches ${variable}
logic: /\$\{#(\w+).*?\}/g, // Matches ${#if ...}
},
tags: [
{ label: 'First Name', value: '${first_name}' },
],
},See Merge Tags for the full guide including all syntax presets, custom syntax, and best practices.
If you want to let users pick merge tags dynamically from a custom UI, see the onRequestMergeTag callback.
displayConditions
Configures block-level display conditions. Display conditions allow users to conditionally show or hide blocks based on recipient data. The consumer defines available conditions with before/after wrapping strings, and users select from a dropdown in the toolbar.
displayConditions: {
conditions: [
{
label: 'VIP customers only',
before: '{% if customer.vip %}',
after: '{% endif %}',
group: 'Customer',
description: 'Only shown to customers with VIP status.',
},
{
label: 'Free plan users',
before: '{% if customer.plan == "free" %}',
after: '{% endif %}',
group: 'Customer',
description: 'Targets users on the free plan tier.',
},
{
label: 'English recipients',
before: '{% if contact.language == "en" %}',
after: '{% endif %}',
group: 'Language',
},
],
allowCustom: true,
},Top-level properties:
| Property | Type | Description |
|---|---|---|
conditions | DisplayCondition[] | Array of preset display conditions |
allowCustom | boolean? | When true, users can write their own before/after logic (default: false) |
Condition properties:
| Property | Type | Description |
|---|---|---|
label | string | Human-readable name shown in the toolbar dropdown |
before | string | Markup inserted before the block in exported HTML |
after | string | Markup inserted after the block in exported HTML |
group | string? | Optional grouping for organizing conditions in the dropdown |
description | string? | Plain-language explanation shown below the condition label |
When a user selects a condition for a block, the block displays a filter icon badge in the editor canvas. At export time, the block's rendered HTML is wrapped with the before and after strings. When allowCustom is enabled, a "Custom condition" option appears at the bottom of the dropdown, allowing users to write their own before/after wrapping logic.
See Display Conditions for the full guide including platform-specific examples and use cases.
autoSave
Plan Feature
This feature is only available on Launch, Growth, and Scale plans.
Enables automatic saving. When true, the editor will save the template automatically after changes. Use autoSaveDebounce to control the delay in milliseconds (minimum 3000, default 5000).
autoSave: true,
autoSaveDebounce: 10000, // 10 secondsai
Controls which AI features are available in the editor. By default, all AI features are enabled when your plan includes AI generation. You can selectively disable individual features or disable all AI features at once.
Set to false to disable all AI features:
ai: false,Or pass an object to control individual features:
ai: {
chat: true, // AI chat assistant
scoring: true, // Template quality scoring
designToTemplate: true, // Design-to-template conversion
rewrite: true, // AI text rewrite in toolbar
},| Property | Type | Default | Description |
|---|---|---|---|
chat | boolean | true | Enable/disable the AI chat assistant |
scoring | boolean | true | Enable/disable template quality scoring |
designToTemplate | boolean | true | Enable/disable design-to-template conversion |
rewrite | boolean | true | Enable/disable AI text rewrite in the block toolbar |
Behavior notes:
- Omitting the
aioption enables all AI features (default behavior). - Setting
ai: falsedisables all AI features. - Each feature defaults to
truewhen theaiobject is provided but the property is omitted. - The AI menu button in the header is hidden when
chat,scoring, anddesignToTemplateare all disabled. Therewritefeature lives in the block toolbar independently. - AI features also require your subscription plan to include AI generation. If the plan does not include it, AI features are hidden regardless of this configuration.
Examples:
// Disable only chat, keep other AI features
ai: { chat: false },
// Only enable rewrite (hides AI menu button, rewrite stays in toolbar)
ai: { chat: false, scoring: false, designToTemplate: false },
// Disable all AI features
ai: false,commenting
Plan Feature
This feature is only available on Growth and Scale plans.
Controls whether the commenting feature is available in the editor. Defaults to true. Set to false to disable commenting regardless of plan availability or user identity configuration.
commenting: false,See Commenting for the full guide including setup, user identity, and the onComment callback.
customBlocks
Plan Feature
This feature is only available on the Scale plan.
Registers custom content blocks that appear alongside built-in blocks in the editor sidebar. Custom blocks are defined declaratively with fields and a Liquid template — no JavaScript required.
customBlocks: [
{
type: 'product-card',
name: 'Product Card',
icon: '<svg>...</svg>',
fields: [
{ key: 'name', type: 'text', label: 'Product Name', default: 'Product' },
{ key: 'price', type: 'text', label: 'Price', default: '$0.00' },
{ key: 'ctaUrl', type: 'text', label: 'Button URL', default: '#' },
],
template: `
<div style="padding: 16px;">
<h3>{{ name }}</h3>
<p style="font-size: 20px; font-weight: bold;">{{ price }}</p>
<a href="{{ ctaUrl }}" style="background: #007bff; color: white; padding: 12px 24px; border-radius: 4px; text-decoration: none; display: inline-block;">Shop Now</a>
</div>
`,
},
],Blocks can also include a dataSource for loading content from external sources — products, articles, events, etc. — through your own picker UI. See Data Sources for details.
See Custom Blocks for the full reference, all field types, and template syntax.
Instance Methods
The init() function returns a TemplaticalInstance with the following methods:
create(content?)
Creates a new template and assigns it a UUID. Call this before save() when starting from scratch.
Accepts an optional content object. When provided, the template is created with the given content instead of the default empty content. This is useful when you have pre-built template content that you want to use as a starting point.
Triggers the onCreate callback.
// Create with default empty content
const template = await editor.create();
console.log(template.id); // New UUID
// Create with pre-built content
const template = await editor.create({
blocks: [/* your blocks */],
settings: {
width: 600,
backgroundColor: '#ffffff',
fontFamily: 'Arial, sans-serif',
preheaderText: 'Check out our latest updates!',
},
});| Parameter | Type | Required | Description |
|---|---|---|---|
content | TemplateContent | No | Template content (blocks and settings) to use instead of defaults |
load('template-id')
Loads an existing template by ID.
Triggers the onLoad callback.
const template = await editor.load('template-id');save()
Saves the current template. Requires create() or load() to be called first — throws an SdkError if no template has been created or loaded.
Triggers the onSave callback.
const result = await editor.save();unmount()
Destroys the editor instance and removes it from the DOM. Call this when you no longer need the editor — for example, when closing a modal or navigating away from the page. After calling unmount(), you must call init() again to re-create the editor.
Triggers the onUnmount callback.
editor.unmount();Callbacks
onCreate
Fired when a new template is created via create(). Receives the Template object containing the new UUID and default content. Use this to update your application state with the new template ID.
onCreate: (template) => {
console.log('Created:', template.id);
// Store the template ID in your application
myApp.currentTemplateId = template.id;
},| Property | Type | Description |
|---|---|---|
template.id | string | The newly created template's unique identifier |
template.content | TemplateContent | The template content (blocks and settings) |
onSave
Triggered by save().
Fired when the template is saved. The save operation automatically renders the template to HTML and MJML, so the callback receives a SaveResult object with the template ID, rendered output, and content.
The mjml property contains the raw MJML source used to generate the HTML. MJML export is only available on Growth and Scale plans — on other plans, mjml will be an empty string.
onSave: (result) => {
console.log('Saved:', result.templateId);
console.log(result.html); // Full HTML output
console.log(result.mjml); // MJML source (Growth / Scale only)
console.log(result.content); // Template content (blocks + settings)
},| Property | Type | Description |
|---|---|---|
result.templateId | string | The saved template's unique identifier |
result.html | string | The fully rendered HTML email output |
result.mjml | string | The MJML source (Growth / Scale only, empty string otherwise) |
result.content | TemplateContent | The template content (blocks and settings) |
INFO
Note: autoSave does not trigger this callback. Auto-saves create snapshots (version history entries) rather than a full save.
onLoad
Triggered by load().
Fired when a template is loaded.
onLoad: (template) => {
console.log('Loaded:', template.id);
},onRequestMergeTag
Called when the user clicks the merge tag button in the editor toolbar. Use this to display your own merge tag picker UI (e.g., a modal or dropdown) and return the selected merge tag. The returned object must have a label and value, just like entries in the mergeTags.tags list. The label will be displayed in the editor and the value will be inserted into the template HTML. Return null if the user cancels the selection.
onRequestMergeTag: async () => {
// Show your own merge tag picker modal
const selected = await showMergeTagModal();
if (!selected) {
return null; // User cancelled
}
// Return a MergeTag object with label and value
return {
label: selected.name, // e.g., "First Name"
value: selected.mergeTag, // e.g., "{{first_name}}"
};
},onError
Fired when an error occurs. The error is an instance of SdkError with statusCode, isNotFound, isUnauthorized, and isServerError properties.
onError: (error) => {
console.error('Editor error:', error.message);
},onUnmount
Triggered by unmount().
Fired after the editor instance is destroyed and removed from the DOM. Use this to run cleanup logic — for example, resetting UI state or removing event listeners in your application.
onUnmount: () => {
console.log('Editor unmounted');
// Reset your application state
myApp.editorOpen = false;
},