Skip to content

Activity Alert Custom Scripts

Custom scripts add behavior to the normal alert. They do not replace the built-in renderer, so the title template, message template, media, sounds, CSS and animations still work unless your script changes the DOM itself.

Scripts can be added in three places: Theme, Group and Variant.

When more than one script applies, Synchra loads them in this order: Theme script, then Group script, then Variant script. The selected variant theme is used when the variant has its own theme; otherwise the group theme is used.

Each script uses synchra.defineAlertScript. onShow runs once after the alert DOM is rendered.

synchra.defineAlertScript({
  onShow(ctx) {
    ctx.elements.alert?.classList.add("is-special");
  },
});
HookWhen it runs
onShow(ctx)Once when the alert is rendered. Can return a cleanup function.
onExit(ctx)When the alert starts exiting.
onPreview(ctx)After onShow when the alert is rendered without live playback.
onCleanup(ctx)When the alert is removed. Cleanup functions returned from onShow also run.

The ctx object gives the script access to the current alert:

PropertyMeaning
ctx.activityThe raw activity event.
ctx.varsThe same formatted values used by text templates, such as user, amount, value and message.
ctx.itemIdThe unique ID for this rendered alert item.
ctx.elements.rootThe widget root element.
ctx.elements.alertThe main .activity-alert element.
ctx.elements.titleThe title element, if rendered.
ctx.elements.messageThe message element, if rendered.
ctx.elements.mediaThe media element, if rendered.
ctx.groupThe active alert group.
ctx.ruleThe matched alert rule.
ctx.variantThe selected variant.
ctx.mediaAssetThe selected image or video asset, if any.
ctx.soundAssetThe selected sound asset, if any.
ctx.ttsAssetThe selected TTS asset, if any.
ctx.settingsThe resolved visual, TTS, CSS and script settings for this alert.
ctx.exitingtrue during exit handling.
ctx.playbacktrue during normal widget playback.

The context also exposes a shared registry for scripts on the same alert:

MethodUse
ctx.provide(name, value)Store one helper value.
ctx.provide({ ... })Store multiple helper values.
ctx.get(name)Read a helper value, or undefined if it does not exist.
ctx.require(name)Read a helper value and throw if it does not exist.
ctx.has(name)Check whether a helper value exists.
ctx.remove(name)Remove a helper value.
ctx.helpers<T>()Read helpers through a typed object.
synchra.defineAlertScript({
  async onShow(ctx) {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/users/1",
    );
    const profile = await response.json();

    if (ctx.elements.message) {
      ctx.elements.message.textContent += ` - ${profile.name}`;
    }
  },
});

If you want to take over the markup completely, replace the widget root HTML. This removes the built-in alert DOM for that alert. The ctx.elements.* references are collected before your replacement, so query any new elements from ctx.elements.root after setting innerHTML.

synchra.defineAlertScript({
  onShow(ctx) {
    ctx.elements.root.innerHTML = `
      <div class="custom-alert">
        <img class="custom-alert__avatar" alt="" />
        <div>
          <div class="custom-alert__title"></div>
          <div class="custom-alert__message"></div>
        </div>
      </div>
    `;
  },
});

Return a cleanup function from onShow when the script starts timers, animations or event listeners.

synchra.defineAlertScript({
  onShow(ctx) {
    const timer = window.setInterval(() => {
      ctx.elements.alert?.classList.toggle("pulse");
    }, 300);

    return () => window.clearInterval(timer);
  },
});

Theme, group and variant scripts for the same alert share an in-memory helper registry. This registry only exists while that alert is being shown; it is not a database and it is not persisted.

Use ctx.provide in a theme or group script:

synchra.defineAlertScript({
  onShow(ctx) {
    ctx.provide({
      formatCoins(value: number) {
        return `${value.toLocaleString()} coins`;
      },
      async loadViewer(viewer: string) {
        const response = await fetch(
          `https://example.com/api/viewers/${viewer}`,
        );
        return response.json();
      },
    });
  },
});

Use the helpers in a variant script:

type Helpers = {
  formatCoins(value: number): string;
  loadViewer(viewer: string): Promise<{ level: number }>;
};

synchra.defineAlertScript({
  async onShow(ctx) {
    const helpers = ctx.helpers<Helpers>();
    const viewer = await helpers.loadViewer(ctx.activity.viewer_name);

    if (ctx.elements.message) {
      ctx.elements.message.textContent =
        `${ctx.vars.user} is level ${viewer.level} with ` +
        helpers.formatCoins(ctx.activity.count);
    }
  },
});