Skip to content
Open in

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@alpha
bash
npm install @topol.io/editor-react@alpha
bash
npm install @topol.io/editor-vue@alpha
bash
npm install @topol.io/editor-svelte@alpha

v1 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 ​

IntegrationAfter upgradingSections to apply
Landing Page Editor (core, React, Vue)Works unchanged from 1.0.0-alpha.9; earlier alphas need the callback changeLanding Page Editor
Email Editor via core EmailEditor / TopolPlugininit() throws on the old shape; module-level methods are goneAll sections
Email Editor via React, Vue or Svelte componentMounts, but callbacks receive different arguments and some stop firingCallbacks, 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();
AreaBeforeAfterHow to fix
Init callinit({ id, authorize, ...options, callbacks })init({ config: { authorize, ...options }, ...callbacks }, { stage, loaderUrl })Move options under config, spread callbacks at the top level, drop id
Mountinginit() mounted into idawait editor.render(container) with a selector or an elementCall render() after init() resolves; the container needs a height
MethodsTopolPlugin.save(), EmailEditor.destroy(), …editor.save(), editor.destroy(), …Call them on the instance. The old module-level methods are undefined
Return valuesMethods returned nothingEvery method returns a promiseawait where order matters, e.g. await editor.save()
InstancesOne editor per page, on windowEach init() creates an independent instanceKeep a reference per editor
userIdAny string or numberNon-negative integer, or a string matching [A-Za-z0-9_-]+; anything else makes init() throwMap emails and other IDs to a URL-safe value
Authentication errorsReported through onErrorReported through onEditorError; the instance has no working methods afterwardsDestroy and re-init after fixing credentials
onInitEditor loadedEditor loaded and authenticatedWait for onInit before calling methods

Methods ​

BeforeAfter
save(lang?)save({ langCode? }), resolves after your onSave settles
setSavedBlocks(blocks)setSavedSections(sections)
refreshSyncedRows()refreshSyncedSections()
getMutations() returned nothing, result came through onGetMutationsgetMutations() 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.

BeforeAfterHow to fix
onSave(json, html, mutations, syncedSections)onSave({ json, html, mutations, syncedSections }); may return a promise the editor waits forDestructure; 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.

BeforeAfterHow to fix
idRemovedPass the container to render(); the framework components own it
callbacks: { … }RemovedPut callbacks next to config
savedBlockssavedSectionsRename; items keep the { id, name, definition, img? } shape
premadeBlocks, premadeBlocksNoOverwritepremadeSections: false, your groups, or { blocks, override }Rename; replace premadeBlocksNoOverwrite with override
syncedRowsEnablesyncedSectionsEnabledRename
topBarOptions (controls to show)hideTopbarControls (controls to hide)Invert the list
defaultTemplateSettings.langs[].keydefaultTemplateSettings.langs[].codeRename the key
api.SAVED_BLOCKSapi.SAVED_SECTIONSRename
api.SAVE, api.LOAD, api.PREVIEWRemovedPersist through onSave; load with editor.load()
api.AUTOSAVE, api.GET_AUTOSAVE, api.SYNCED_ROWSRemovedServe 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:

ChangeBeforeAfterHow to fix
ContainerFixed id (#editor, #topol-editor-id or #topol-email-editor-id) and position: absoluteNo fixed id, no position: absolute; still width: 100%; height: 100vhStop targeting the ids; size and position the parent yourself
Editor accessGlobal TopolPlugin methodsThe component's instance: React ref.current.getEditor() or onReady, Vue template ref getEditor() or @ready, Svelte bind:this + getEditor() or on:readyCall methods on that instance
Init failuresonError (React, Vue) or nothing (Svelte)onEditorError with source: "integration"Listen to onEditorError
ReconfiguringOptions read once on mountOptions and which handlers exist are captured on mountRemount (React key, Vue key, Svelte {#key}) to change them
New props—loaderUrl; React className, style, onReady; Vue and Svelte callbacksOptional

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 makes init() reject.
  • Use one loader URL per page. Switching stages needs a page reload.
  • The dev and staging stages resolve to the production v4 loader. Use the loaderUrl option for preview or local builds.
  • If you rebuild the packages yourself, the build-time overrides are VITE_TOPOL_V4_URL, VITE_TOPOL_V4_DEV_URL and VITE_TOPOL_V4_STAGING_URL. The old VITE_TOPOL_* variables are ignored.

Types ​

TypeAfterHow to fix
ITopolOptions{ config: IEmailConfig } plus callbacksType your options object as IEmailConfig
IReactEmailOptions, IVueEmailOptions, ISvelteOptionsIEmailConfigNo change; renamed options apply
ITopolPluginDeprecated alias of IEmailEditorInstanceUse IEmailEditorInstance
ISavedBlockDeprecated alias of ISavedSectionUse ISavedSection
ISaveDatamutations 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.

  • [ ] onError is renamed to onEditorError, so editor and init errors still reach you.
  • [ ] onClose is renamed to onEditorClose, so closing the editor still works.
  • [ ] Save handlers read json and html from one object. A two-argument handler would store the payload object as JSON and undefined as HTML.
  • [ ] savedBlocks, setSavedBlocks and the onBlock* callbacks are renamed, so saved sections still appear and persist.
  • [ ] premadeBlocks, syncedRowsEnable and api.SAVED_BLOCKS are renamed.
  • [ ] topBarOptions is converted to hideTopbarControls with the list inverted.
  • [ ] Multilingual code reads code instead of key or lang.
  • [ ] Unsaved-changes guards read { value }, undo/redo buttons read { count }.
  • [ ] The page layout works without the component's fixed id and position: absolute.
  • [ ] Every userId passes 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.