Appearance
NPM Packages: Upgrading to v1
From 1.0.0-alpha.10 (@topol.io/editor-react 1.0.0-alpha.11), the v1 packages embed Email Editor v4. Moving to v4 changes how you initialize the editor, how callbacks receive data, and several option names. This guide lists every change and how to fix it.
It applies whether you are on 0.3.0 (the current latest) or on an earlier v1 alpha (1.0.0-alpha.1 to 1.0.0-alpha.9, React up to 1.0.0-alpha.10). Both use the same v3 API, so you migrate them the same way.
Install the alpha with:
bash
npm install @topol.io/editor@alphabash
npm install @topol.io/editor-react@alphabash
npm install @topol.io/editor-vue@alphabash
npm install @topol.io/editor-svelte@alphav1 is in alpha
Pin the exact version you integrate against and read the changelog before upgrading. The 1.0.0-alpha.1–1.0.0-alpha.9 builds embedded Email Editor v3 and are not compatible with the API below.
Option and callback reference
The rest of the Email Editor guide still describes the v3 API used by the plain <script> loader. For the npm packages, the differences are listed on this page and in each package's npm README.
Let an AI agent do the upgrade
TOPOL-io/skills has two skills for coding agents, written as plain markdown. topol-editors-integration sets up either editor in a new project and topol-editors-upgrade-v1 moves an existing integration to v1. They install as a Claude Code plugin (/plugin marketplace add TOPOL-io/skills) or into any agent with npx skills@latest add TOPOL-io/skills.
Who needs to change what
| Integration | After upgrading | Sections to apply |
|---|---|---|
| Landing Page Editor (core, React, Vue) | Works unchanged from 1.0.0-alpha.9; earlier alphas need the callback change | Landing Page Editor |
Email Editor via core EmailEditor / TopolPlugin | init() throws on the old shape; module-level methods are gone | All sections |
| Email Editor via React, Vue or Svelte component | Mounts, but callbacks receive different arguments and some stop firing | Callbacks, Options, Framework packages, Checklist |
Package names, import paths and peer dependencies are the same. npm install and your imports keep working. The breaks show up when you type-check, or at runtime.
Landing Page Editor
v1 adds the Landing Page Editor to the same packages, as a named LandingPageEditor export in core, React and Vue. The Svelte package does not include a Landing Page Editor component yet.
If you used it on 1.0.0-alpha.8 or earlier, its callbacks now receive a single object, as in the Email Editor: onSave({ json, html }) instead of onSave(json, html), and errors and closing arrive through onEditorError and onEditorClose. From 1.0.0-alpha.9 on, its API is unchanged.
Initialization and lifecycle
init() now takes { config, ...callbacks } and resolves to an editor instance. It no longer mounts anything: call render() on the instance. Every method lives on the instance and returns a promise.
ts
import TopolPlugin from "@topol.io/editor";
await TopolPlugin.init({
id: "#editor",
authorize: { apiKey: "YOUR_API_KEY", userId: "user-123" },
savedBlocks: blocks,
callbacks: {
onSave(json, html) {
persist(json, html);
},
onError(type, message) {
report(type, message);
},
},
});
TopolPlugin.save("en");
TopolPlugin.destroy();ts
import { EmailEditor } from "@topol.io/editor";
const editor = await EmailEditor.init({
config: {
authorize: { apiKey: "YOUR_API_KEY", userId: "user-123" },
savedSections: blocks,
},
async onSave({ json, html }) {
await persist(json, html);
},
onEditorError({ source, message }) {
report(source, message);
},
});
await editor.render("#editor");
await editor.save({ langCode: "en" });
await editor.destroy();| Area | Before | After | How to fix |
|---|---|---|---|
| Init call | init({ id, authorize, ...options, callbacks }) | init({ config: { authorize, ...options }, ...callbacks }, { stage, loaderUrl }) | Move options under config, spread callbacks at the top level, drop id |
| Mounting | init() mounted into id | await editor.render(container) with a selector or an element | Call render() after init() resolves; the container needs a height |
| Methods | TopolPlugin.save(), EmailEditor.destroy(), … | editor.save(), editor.destroy(), … | Call them on the instance. The old module-level methods are undefined |
| Return values | Methods returned nothing | Every method returns a promise | await where order matters, e.g. await editor.save() |
| Instances | One editor per page, on window | Each init() creates an independent instance | Keep a reference per editor |
userId | Any string or number | Non-negative integer, or a string matching [A-Za-z0-9_-]+; anything else makes init() throw | Map emails and other IDs to a URL-safe value |
| Authentication errors | Reported through onError | Reported through onEditorError; the instance has no working methods afterwards | Destroy and re-init after fixing credentials |
onInit | Editor loaded | Editor loaded and authenticated | Wait for onInit before calling methods |
Methods
| Before | After |
|---|---|
save(lang?) | save({ langCode? }), resolves after your onSave settles |
setSavedBlocks(blocks) | setSavedSections(sections) |
refreshSyncedRows() | refreshSyncedSections() |
getMutations() returned nothing, result came through onGetMutations | getMutations() resolves to { code, primary }[] and still fires onGetMutations |
updateOptions(options) | updateOptions(options), without authorize |
createLanguage, deleteLanguage, selectLanguage, setPrimaryLanguage(lang) | Same names; the argument is the language code |
Every other method keeps its name: load, togglePreview, togglePreviewSize, toggleDarkMode, chooseFile, undo, redo, setPreviewHTML, createNotification, setActiveMembers, changeEmailToMobile, changeEmailToDesktop, toggleBlocksAndStructuresVisibility, updateCustomBlockContent, refreshComments, openPremadeTemplatesSelection, updateApiAuthorizationHeader, setTemplateName, setMergeTags, toggleChatAI, toggleAutosaves, toggleComments, toggleControlPanel, updateTemplate. New: translateLanguage(code, sourceCode?) and translateAllLanguages().
Callbacks
Every callback now receives either no arguments or a single object. Five callbacks are renamed.
| Before | After | How to fix |
|---|---|---|
onSave(json, html, mutations, syncedSections) | onSave({ json, html, mutations, syncedSections }); may return a promise the editor waits for | Destructure; return the promise when you persist asynchronously |
onSaveAndClose(json, html, …) | onSaveAndClose({ json, html, mutations, syncedSections }) | Destructure |
onTestSend(email, json, html) | onTestSend({ email, json, html }); email is string | string[] | Destructure; handle an array of addresses |
onBannerClick(json, html) | onBannerClick({ json, html }) | Destructure |
onUndoChange(count), onRedoChange(count) | ({ count }) | Destructure |
onPreview(html) | onPreview({ html }) | Destructure |
onAlert(notification) | onAlert({ notification }) | Destructure |
onEdittedWithoutSaveChanged(hasUnsavedChanges) | onEdittedWithoutSaveChanged({ value }) | Read value |
onOpenCustomBlockDialog(block) | onOpenCustomBlockDialog({ block }) | Destructure |
onTemplateRename(title) | onTemplateRename({ name }) | Read name |
updateTestingEmailAddresses(emails) | updateTestingEmailAddresses({ emails }) | Destructure |
onLanguageCreated, onLanguageDeleted, onPrimaryLanguageChanged(lang, mutations) | ({ code, mutations }) | Read code |
onLanguageSelected(lang) | onLanguageSelected({ code }) | Read code |
onGetMutations(mutations) | onGetMutations({ mutations }) | Destructure |
onError(type, message, responseBody) | onEditorError({ source, message, detail }) | Rename; type → source, responseBody → detail |
onClose() | onEditorClose() | Rename |
onBlockSave(block) | onSavedSectionSave({ section }) | Rename and destructure |
onBlockRemove(id) | onSavedSectionRemove({ id }) | Rename; id can be a number or a string |
onBlockEdit(id) | onSavedSectionEdit({ id }) | Rename; id can be a number or a string |
onInit(), onLoaded(), onOpenFileManager() | Unchanged | — |
Language mutations change from { key, primary } to { code, primary } everywhere: save payloads, language callbacks, onGetMutations and getMutations().
onClose and onError are dropped before they reach the editor. A handler left under either name never runs, and nothing warns you.
New callbacks: onPreviewClose(), onImageDelete({ items }), onBlocksAndStructuresVisibilityChange({ value }), onTemplateUpdated(), and the optional getNonce() for nonce authentication.
Configuration options
Saved and premade blocks are now called sections. A renamed option left under its old name is ignored without an error.
| Before | After | How to fix |
|---|---|---|
id | Removed | Pass the container to render(); the framework components own it |
callbacks: { … } | Removed | Put callbacks next to config |
savedBlocks | savedSections | Rename; items keep the { id, name, definition, img? } shape |
premadeBlocks, premadeBlocksNoOverwrite | premadeSections: false, your groups, or { blocks, override } | Rename; replace premadeBlocksNoOverwrite with override |
syncedRowsEnable | syncedSectionsEnabled | Rename |
topBarOptions (controls to show) | hideTopbarControls (controls to hide) | Invert the list |
defaultTemplateSettings.langs[].key | defaultTemplateSettings.langs[].code | Rename the key |
api.SAVED_BLOCKS | api.SAVED_SECTIONS | Rename |
api.SAVE, api.LOAD, api.PREVIEW | Removed | Persist through onSave; load with editor.load() |
api.AUTOSAVE, api.GET_AUTOSAVE, api.SYNCED_ROWS | Removed | Serve autosaves from api.AUTOSAVES |
All other options keep their names. v4 also adds optional ones such as permissions, enableSubjectLine, countdown, loop and rating blocks, and cloud.
Framework packages
The React, Vue and Svelte components keep their names and the options and stage props. They follow the callback changes above, plus these:
| Change | Before | After | How to fix |
|---|---|---|---|
| Container | Fixed id (#editor, #topol-editor-id or #topol-email-editor-id) and position: absolute | No fixed id, no position: absolute; still width: 100%; height: 100vh | Stop targeting the ids; size and position the parent yourself |
| Editor access | Global TopolPlugin methods | The component's instance: React ref.current.getEditor() or onReady, Vue template ref getEditor() or @ready, Svelte bind:this + getEditor() or on:ready | Call methods on that instance |
| Init failures | onError (React, Vue) or nothing (Svelte) | onEditorError with source: "integration" | Listen to onEditorError |
| Reconfiguring | Options read once on mount | Options and which handlers exist are captured on mount | Remount (React key, Vue key, Svelte {#key}) to change them |
| New props | — | loaderUrl; React className, style, onReady; Vue and Svelte callbacks | Optional |
React. @topol.io/editor-react has no default export: import { EmailEditor } instead of TopolEditor. Callback props follow the core table, so positional handlers now receive one object.
Vue. Event listeners are notifications and their return value is not awaited. Pass handlers the editor must wait for, such as an async onSave, through the callbacks prop. Vue events already sent objects for onSave, onSaveAndClose, onTestSend, onBannerClick and onGetMutations; those keep their shape. The other events change as in the callbacks table, and language events carry { code, … } instead of { lang, … }.
Svelte. TopolEditor is now the same component as the new EmailEditor. Event payloads in event.detail follow the callbacks table: onLanguageSelected sends { code } instead of a string and onGetMutations sends { mutations } instead of an array. Type the options prop as ISvelteOptions; ITopolOptions now means the core factory props. Pass async handlers through the callbacks prop.
Loader and environments
- The packages load the v4 loader,
https://v4.email-assets.topol.io/loader/build.js. Your API key must work with Email Editor v4. - Let
init()load the script. A loader<script>tag you add to the page yourself makesinit()reject. - Use one loader URL per page. Switching stages needs a page reload.
- The
devandstagingstages resolve to the production v4 loader. Use theloaderUrloption for preview or local builds. - If you rebuild the packages yourself, the build-time overrides are
VITE_TOPOL_V4_URL,VITE_TOPOL_V4_DEV_URLandVITE_TOPOL_V4_STAGING_URL. The oldVITE_TOPOL_*variables are ignored.
Types
| Type | After | How to fix |
|---|---|---|
ITopolOptions | { config: IEmailConfig } plus callbacks | Type your options object as IEmailConfig |
IReactEmailOptions, IVueEmailOptions, ISvelteOptions | IEmailConfig | No change; renamed options apply |
ITopolPlugin | Deprecated alias of IEmailEditorInstance | Use IEmailEditorInstance |
ISavedBlock | Deprecated alias of ISavedSection | Use ISavedSection |
ISaveData | mutations are { code, primary }[] | Read code |
IErrorData | { source, message, detail? } | Rename type and responseBody |
The Landing Page Editor types are unchanged.
Upgrade checklist
These changes raise no error in plain JavaScript. Check each one before you ship the upgrade.
- [ ]
onErroris renamed toonEditorError, so editor and init errors still reach you. - [ ]
onCloseis renamed toonEditorClose, so closing the editor still works. - [ ] Save handlers read
jsonandhtmlfrom one object. A two-argument handler would store the payload object as JSON andundefinedas HTML. - [ ]
savedBlocks,setSavedBlocksand theonBlock*callbacks are renamed, so saved sections still appear and persist. - [ ]
premadeBlocks,syncedRowsEnableandapi.SAVED_BLOCKSare renamed. - [ ]
topBarOptionsis converted tohideTopbarControlswith the list inverted. - [ ] Multilingual code reads
codeinstead ofkeyorlang. - [ ] Unsaved-changes guards read
{ value }, undo/redo buttons read{ count }. - [ ] The page layout works without the component's fixed
idandposition: absolute. - [ ] Every
userIdpasses the new format check. - [ ] Existing templates open and save in Email Editor v4 with your API key.
INFO
Running into trouble while upgrading? Contact our support team here and we'll help you get it working.
