Skip to content

Callbacks

Callbacks are how the editor reports events back to your application. Configuration travels into the editor through TOPOL_OPTIONS ("props down"), and events travel back out through the functions you register there ("callbacks up"). Saves, test sends, file manager actions, undo and redo, and errors all surface this way.

Every callback is optional. Register only the ones your integration reacts to.

How to use Callbacks

Callbacks are defined inside the TOPOL_OPTIONS configuration like this:

ts
const TOPOL_OPTIONS = {
  // other options...
  callbacks: {
    onSave(json, html, mutations, syncedSections) {
      // Handle save logic
    },
    onInit() {
      // Handle editor initialization
    },
    // more callbacks...
  },
};

Each callback receives exactly the arguments listed in its heading below. Callbacks documented with empty parentheses receive none.

Available Callbacks and their Usage

onSave(json, html, mutations, syncedSections)

Triggered when the Save button is clicked or when TopolPlugin.save() is called manually. This is the callback that hands you the template to store, so an integration that persists templates needs at least this one.

Use this to save the template data (json) and its HTML output (html) to your backend or local storage. The mutations parameter is used only by the Multilingual Template feature, and arrives as an array of { lang, primary } entries describing each language mutation. Without that feature it is an empty array and can be ignored.

The syncedSections parameter is an array of synced section IDs (number[]) that are currently placed in the template. The editor walks the template's body sections and returns every syncedId it finds. Use this list to look the synced sections up in your api.SAVED_SECTIONS storage and pass them to the Convert JSON template to HTML endpoint when re-rendering. If you are not using synced sections, this will be an empty array. See Server-Side Resolution for the full render pipeline.

The email subject line is accessible from the json parameter at json.attributes["mj-subject"].text. When using multilingual templates, per-language subject lines are stored in json.langs[].translations["mj-subject"].content.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onSave(json, html, mutations, syncedSections) {
      // save json and html to your infrastructure.
      // save mutations only if using Multilingual Templates.
      // persist syncedSections (array of IDs) only if using Synced Sections.
    },
  },
};

onSaveAndClose(json, html, mutations, syncedSections)

Called when the Save and Close button is clicked in the editor Top Bar.

Use this for saving and then exiting the editor or redirecting the user. The mutations and syncedSections parameters are identical to those passed to onSave.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onSaveAndClose(json, html, mutations, syncedSections) {
      // save json and html to your infrastructure and close the editor
    },
  },
};

onTestSend(email, json, html)

Fires when a user sends a test email from the preview screen.

The email parameter is a single address by default. With testingEmails set to true it is always an array of addresses.

Use this callback to send test emails through your own service (SendGrid, Mailgun, custom SMTP, or anything else).

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onTestSend(email, json, html) {
      // send a test email using your preferred sender
    },
  },
};

onOpenFileManager()

Called when a user clicks "Choose a file" inside image or video properties. Takes no arguments.

Use this to open your own file manager UI, as described in custom file manager.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onOpenFileManager() {
      // open your custom filemanager dialog window
    },
  },
};

onImageDelete(items)

Fires when a user removes one or more files or folders from the built-in File Manager. The callback runs only after the deletion has succeeded, so the items it reports have already been removed from your storage.

Use this to keep your own system in sync with the editor, for example to clean up records in your database, update an external asset library, or log removals.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onImageDelete(items) {
      // react to removed files and folders, e.g. clean up your own records
      items.forEach((item) => {
        console.log(`${item.type} removed: ${item.name} (${item.path})`);
      });
    },
  },
};

The items parameter is an array, with one entry per deleted file or folder. Each entry has the following shape:

FieldTypeDescription
namestringThe name of the deleted file or folder.
type"file" | "folder"Whether a file or a folder was deleted.
pathstringThe location of the item within the File Manager.
urlstring (optional)The item's URL. This may be a temporary blob: URL for files that were uploaded in the same session but never persisted, so do not rely on it being long-lived or fetchable.
keystring | nullThe storage key of the item. This is null for folders and for items without an original key, and a string otherwise.

Deletion works normally whether or not this callback is registered.

onInit()

Executed when the editor is fully initialized.

Commonly used to hide loading spinners or initialize user tracking once the editor is ready. Takes no arguments.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onInit() {
      // hide the loader or track the usage
    },
  },
};

onLoaded()

Fires after calling TopolPlugin.load(YOUR_TEMPLATE) and the template is fully rendered. Takes no arguments.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onLoaded() {
      // hide the loader
    },
  },
};

onTemplateUpdated()

Fires after calling TopolPlugin.updateTemplate(YOUR_TEMPLATE) and the template is successfully updated.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onTemplateUpdated() {
      // template was updated programmatically
    },
  },
};

onBlockSave(block)

Triggered when a user saves a section to their personal library and savedBlocks is configured as a local array (manual mode).

Use this to store the block definition on your own infrastructure. See saved blocks for the full picture.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onBlockSave(block) {
      // save the block on your infrastructure
    },
  },
};

Saved blocks vs synced sections

  • onBlockSave fires whenever savedBlocks is not literally true (i.e. an array, or the default false). Setting savedBlocks: true switches the panel into API-based mode and the save flow goes straight to your api.SAVED_SECTIONS endpoint instead.
  • onBlockEdit and onBlockRemove only fire when the legacy local-array panel is rendered. Configuring api.SAVED_SECTIONS is enough to switch the panel into API mode and silence these callbacks, even if savedBlocks is still an array.
  • Synced sections always run in API-based mode and never trigger any of these callbacks. To react to synced section changes, implement the api.SAVED_SECTIONS endpoints on your server instead.

onBlockRemove(blockId)

Fires when a user removes a saved block in manual mode. Does not fire for API-based saved blocks or for synced sections, as those are handled through the api.SAVED_SECTIONS endpoint. See saved blocks.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onBlockRemove(blockId) {
      // remove the block on your infrastructure
    },
  },
};

onBlockEdit(blockId)

Called when a user edits an existing saved block in manual mode. Does not fire for API-based saved blocks or for synced sections, as edits to those are persisted via PATCH calls against your api.SAVED_SECTIONS endpoint. See saved blocks.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onBlockEdit(blockId) {
      // edit the block on your infrastructure
    },
  },
};

onClose()

Fires when a user closes the editor without saving, from the close control in the Top Bar. Takes no arguments.

Use it to navigate away, unmount the editor, or restore your own interface. To warn about unsaved work first, see onEdittedWithoutSaveChanged.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onClose() {
      // leave the editor screen
    },
  },
};

onEdittedWithoutSaveChanged(hasUnsavedChanges)

Reports whether the template currently holds unsaved changes. It fires on every transition in both directions, so the value stays accurate as the user edits and saves.

Store the value to drive your own exit flow, for example blocking a route change or enabling a save button. See the unsaved changes guide for the related showUnsavedDialogBeforeExit option.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onEdittedWithoutSaveChanged(hasUnsavedChanges) {
      // true = unsaved changes, false = fully saved
    },
  },
};

onTemplateRename(title)

Fires when a user renames the template from the editor header, with title holding the new name as a string. Requires the renameTemplate: true option, which places the pencil icon that starts the rename.

Persist the name on your side, then call TopolPlugin.setTemplateName() to reflect it in the editor. See rename template.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onTemplateRename(title) {
      // store the new template name
    },
  },
};

updateTestingEmailAddresses(emails)

Fires when a user edits the list of test email addresses in the preview screen, and receives the updated list as an array of strings. Relevant when testingEmails is enabled, so the addresses persist between sessions rather than being retyped.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    updateTestingEmailAddresses(emails) {
      // persist the updated address list
    },
  },
};

onUndoChange(count)

Fired when the Undo button is used. count indicates how many steps have been undone.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onUndoChange(count) {
      // ...
    },
  },
};

onRedoChange(count)

Fired when the Redo button is clicked. count shows the number of steps redone.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onRedoChange(count) {
      // ...
    },
  },
};

onPreview(html)

Called when the user enters the Preview mode.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onPreview(html) {
      // ...
    },
  },
};

onPreviewClose()

Called when the user exits the Preview mode and returns to the editor. Useful for syncing host-app UI that should only be visible while preview is active (e.g., a host-rendered Dark Mode toggle).

Takes no arguments. The rendered HTML was already delivered to your host on entry via onPreview.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onPreviewClose() {
      // ...
    },
  },
};

onAlert(notification)

Replaces the built-in notifications with your own alert system. This callback fires only while disableAlerts: true is set, since the editor either renders a notification itself or forwards it here, never both. Notifications you raise yourself with TopolPlugin.createNotification() are the exception: those always render in the editor, so they do not arrive here.

See custom notifications for the full setup.

ts
const TOPOL_OPTIONS = {
  callbacks: {
    onAlert(notification) {
      //use your own notification, or create proxy for built-in notifications
    },
  },
};

onError(type, message, responseBody)

Fires when the editor encounters an unrecoverable error. Useful for diagnostics and fallbacks.

There are 4 types of errors:

  • templateLoad - Indicates that the JSON of the template is not correct and editor cannot read it correctly.
  • network - Indicates that network issue happened during internal or external (endpoints given in API property in TOPOL_OPTIONS) Fetch/XHR calls.
  • authorize - Invalid API key or wrong/unverified domain
  • updateTemplate - Indicates that the template passed to TopolPlugin.updateTemplate() could not be parsed (invalid JSON)

The optional third parameter responseBody (unknown) contains the raw parsed response body from your server (error.response.data). It is available for all HTTP error responses (4xx, 5xx) and is undefined for network errors where no response was received. A backend that returns JSON delivers the parsed object directly.

js
const TOPOL_OPTIONS = {
  callbacks: {
    onError(type, message, responseBody) {
      if (type === "network") { // templateLoad | network | authorize | updateTemplate
        console.log("Network issue: " + message);

        // Access custom error data from your backend
        if (responseBody?.errors) {
          responseBody.errors.forEach((err) => {
            TopolPlugin.createNotification({
              text: err.detail,
              type: "error",
            });
          });
        }
      }
    },
  },
};

Language State Callbacks

These callbacks keep external UI in sync with the editor's Multilingual Templates state. They fire whenever a language mutation is created, deleted, or selected, and when the primary language changes. Both the built-in UI and programmatic API calls trigger them.

onLanguageCreated(lang, mutations)

Triggered when a new language mutation is created, either via the editor UI or by calling TopolPlugin.createLanguage(lang).

  • lang: the language code of the newly created mutation (e.g. "fr")
  • mutations: the updated array of all mutations after the creation
ts
const TOPOL_OPTIONS = {
  callbacks: {
    onLanguageCreated(lang, mutations) {
      console.log(`Language "${lang}" created. Current mutations:`, mutations);
      // Update your external language selector
    },
  },
};

onLanguageDeleted(lang, mutations)

Triggered when a language mutation is deleted. Deletion always requires user confirmation via a modal dialog, whether initiated from the UI or via TopolPlugin.deleteLanguage(lang).

  • lang: the language code of the deleted mutation
  • mutations: the updated array of all remaining mutations after the deletion
ts
const TOPOL_OPTIONS = {
  callbacks: {
    onLanguageDeleted(lang, mutations) {
      console.log(`Language "${lang}" deleted. Remaining mutations:`, mutations);
      // Update your external language selector
    },
  },
};

onLanguageSelected(lang)

Triggered when the active editing language changes, either via the editor UI dropdown or by calling TopolPlugin.selectLanguage(lang). Does not fire if the requested language is already active or does not exist.

  • lang: the language code of the newly active mutation
ts
const TOPOL_OPTIONS = {
  callbacks: {
    onLanguageSelected(lang) {
      console.log(`Switched to language "${lang}"`);
      // Highlight the active language in your external UI
    },
  },
};

onPrimaryLanguageChanged(lang, mutations)

Triggered when the primary language is changed via the editor UI or by calling TopolPlugin.setPrimaryLanguage(lang). If the language does not yet exist, it will be created first (firing onLanguageCreated as well).

  • lang: the language code of the new primary mutation
  • mutations: the updated array of all mutations reflecting the new primary assignment
ts
const TOPOL_OPTIONS = {
  callbacks: {
    onPrimaryLanguageChanged(lang, mutations) {
      console.log(`Primary language changed to "${lang}"`);
      // Update your external UI to reflect the new primary
    },
  },
};

onGetMutations(mutations)

Triggered when TopolPlugin.getMutations() is called. Returns the current list of all language mutations without modifying any state.

  • mutations: array of all current mutations, each with lang (string) and primary (boolean)
ts
const TOPOL_OPTIONS = {
  callbacks: {
    onGetMutations(mutations) {
      console.log("Current mutations:", mutations);
      // Use this data to populate your external language selector
    },
  },
};