Events
The editor communicates with your application through callback functions passed in the configuration.
Content Events
onChange
Called whenever the template content changes. The callback receives the full TemplateContent object. Changes are debounced internally.
const editor = init({
container: '#editor',
onChange(content) {
// Save to your backend
fetch('/api/templates/123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(content),
});
},
});onSave
Called when the user explicitly triggers a save (e.g. via keyboard shortcut). Use this for immediate saves vs. the debounced onChange.
const editor = init({
container: '#editor',
onSave(content) {
saveTemplate(content);
showNotification('Template saved');
},
});onError
Called when an error occurs within the editor.
const editor = init({
container: '#editor',
onError(error) {
console.error('Editor error:', error.message);
reportToSentry(error);
},
});Request Events
onRequestMedia
Called when the user clicks to select an image (e.g. in the image block settings). The editor passes a callback function — call it with the image URL when the user has made their selection.
const editor = init({
container: '#editor',
onRequestMedia(callback) {
// Open your own media picker
openMediaPicker({
onSelect(imageUrl) {
callback(imageUrl);
},
});
},
});If you don't provide onRequestMedia, the editor falls back to a text input where users type or paste image URLs directly.
onRequestMergeTag
Called when the user clicks to insert a merge tag in a text block. Return a Promise that resolves to a MergeTag object or null if the user cancels.
import type { MergeTag } from '@templatical/types';
const editor = init({
container: '#editor',
mergeTags: {
syntax: 'liquid',
tags: [
{ label: 'First Name', value: '{{first_name}}' },
{ label: 'Email', value: '{{email}}' },
],
},
async onRequestMergeTag(): Promise<MergeTag | null> {
// Show your own picker UI and return the selected tag
const tag = await showMergeTagPicker();
return tag; // or null if cancelled
},
});If you provide mergeTags.tags without onRequestMergeTag, the editor uses a built-in dropdown populated with your tags. The onRequestMergeTag callback lets you replace that dropdown with your own UI.
Patterns
Debounced Auto-Save
let saveTimeout: ReturnType<typeof setTimeout>;
const editor = init({
container: '#editor',
onChange(content) {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
saveToBackend(content);
}, 2000);
},
onSave(content) {
clearTimeout(saveTimeout);
saveToBackend(content);
},
});Dirty State Tracking
let isDirty = false;
const editor = init({
container: '#editor',
onChange() {
isDirty = true;
updateSaveButton();
},
onSave(content) {
saveToBackend(content).then(() => {
isDirty = false;
updateSaveButton();
});
},
});
window.addEventListener('beforeunload', (e) => {
if (isDirty) {
e.preventDefault();
}
});