Widget runtime

Treat the browser embed as a small, stable runtime surface.

The widget loader exposes a queue-based global API, a single singleton iframe instance, token refresh support, and an event bridge for host analytics and UI coordination.

Use this guide when you already know the widget is part of the workflow

This page is for engineers and platform teams who need exact runtime behavior after Quickstart, not for teams that are still deciding whether they need the widget at all.

Best for host-app control

Use this when your app owns the trigger, layout, page placement, or analytics hooks around the widget.

Best for richer embed patterns

Use this when you need inline changelog blocks, module-scoped views, or host-controlled panel behavior.

Not the first stop

If your only goal is to get something live, Quickstart is still the better path. Come here when implementation details start to matter.

Minimal runtime call

Start with tokenProvider and tenant defaults. Only override presentation fields when the host app truly owns that decision.

window.ChainlogWidget.init({
  tenantId: "CHAINLOG_TENANT_ID",
  useTenantDefaults: true,
  tokenProvider: async () => {
    const response = await fetch("/api/widget-token", {
      credentials: "include",
    });

    const { token, expiresInSeconds } = await response.json();
    return { token, expiresInSeconds };
  },
});

Most common widget workflows

Most teams use the same runtime in a few repeatable ways.

Launcher or panel for global updates

Best when updates should be available from anywhere in the product with one consistent entry point.

Inline changelog for a specific page

Best when onboarding, settings, billing, or admin pages should show release notes in-place instead of in a floating shell.

Host-owned triggers and analytics

Best when your app already has command surfaces, navigation, or event tracking you want the widget to plug into cleanly.

Configuration reference

These are the public config fields that matter for most implementations.

FieldRequiredBehavior
tenantIdRecommendedRequired when using tenant runtime defaults. Identifies which tenant runtime config to fetch.
tokenOptionalShort-lived widget JWT. Use only when you are not using tokenProvider.
tokenProviderOptionalAsync function that returns a token string or { token, expiresInSeconds | expiresAt }.
useTenantDefaultsOptionalWhen true, loads tenant-managed defaults for mode, theme, trigger, label, sizing, and max items.
modeOptional, default "launcher"Supported values are "launcher", "panel", and "inline".
showTriggerOptional, default trueHide the built-in trigger when your host app owns the button.
positionOptional, default "bottom-right"Floating position for launcher and panel modes.
title / subtitleOptionalOverride the in-widget header copy.
width / panelWidth / triggerWidthOptionalDimension overrides for floating layouts. Strings or numbers are supported.
targetInline onlySelector or mount target for inline mode. Required if there is no [data-chainlog-widget] container.
autoHeightInline onlyDefaults to true. Resize messages from the frame adjust height automatically.
contextModuleOptionalPass a product-area trigger such as billing or dashboard to filter visible entries.
onEventOptionalReceives widget lifecycle, click, filter, height, and error events.

Runtime methods

The runtime surface is intentionally small. Most behavior changes happen through config and entry metadata.

MethodPurpose
init(config)Create the singleton widget instance and mount the iframe.
update(partialConfig)Merge new config, optionally refresh tokenProvider, and repost config to the frame.
destroy()Remove the iframe instance and clear token refresh timers.
open()Set open state to true for launcher or panel mode.
close()Set open state to false for launcher or panel mode.
toggle()Flip the current open state for launcher or panel mode.

There is only one widget instance per page

Calling init again updates the existing singleton. Use update for deliberate runtime changes and destroy when unmounting in SPA transitions.

Events and payloads

Use onEvent for analytics, host UI updates, and debugging. Keep handlers resilient because they run in your app shell.

window.ChainlogWidget.init({
  tenantId: "CHAINLOG_TENANT_ID",
  tokenProvider,
  onEvent(payload) {
    if (payload.type === "entry_clicked") {
      analytics.track("release_note_opened", {
        entryId: payload.entryId,
        slug: payload.slug,
        deliveryType: payload.deliveryType,
      });
    }

    if (payload.type === "filter_changed") {
      console.info("Visible entries", payload.visibleCount);
    }
  },
});
EventCommon payload fieldsWhen it fires
launcher_toggle / panel_toggleopen, modeFires when the floating surface opens or closes.
data_loadedcount, totalLoaded, hasMore, nextCursor, contextModuleFires on initial load and load more.
filter_changeddeliveryFilter, categoryFilter, visibleCount, totalLoadedFires when widget filters change.
entry_clickedentryId, slug, title, deliveryType, changeCategory, releaseStatusFires when an entry opens inside the widget.
load_more_clickednextCursor, loadedCountFires before the widget fetches the next page.
content_heightheightInline mode only. Lets the host resize the iframe.
retry_clicked / errormessage, modeFires on retry attempts and runtime errors.

Common layouts

The same runtime supports host-owned triggers and inline embeds without a separate SDK.

Host-owned button

<button type="button" onclick="window.ChainlogWidget.toggle()">
  Release notes
</button>

<script>
  window.ChainlogWidget.init({
    tenantId: "CHAINLOG_TENANT_ID",
    useTenantDefaults: true,
    tokenProvider: async () => fetch("/api/widget-token", { credentials: "include" }).then((r) => r.json()),
    mode: "panel",
    showTrigger: false,
    launcherLabel: "Updates",
  });
</script>

Inline changelog block

<div id="release-notes"></div>

<script>
  window.ChainlogWidget.init({
    tenantId: "CHAINLOG_TENANT_ID",
    useTenantDefaults: true,
    tokenProvider: async () => {
      const response = await fetch("/api/widget-token", { credentials: "include" });
      return response.json();
    },
    mode: "inline",
    target: "#release-notes",
    title: "Release notes",
    subtitle: "What changed recently",
    autoHeight: true,
  });
</script>

Errors, token refresh, and sizing behavior

These behaviors are easy to miss when reading code casually, so document them explicitly.

  • If you provide tokenProvider and expiry metadata, the loader schedules token refresh before expiry.
  • Recoverable auth errors trigger a forced token refresh attempt when tokenProvider is available.
  • Inline mode sends content height events and auto-resizes unless you set a fixed height or disable autoHeight.
  • Inline mode requires target or a data-chainlog-widget container once the page is mounted.
  • Widget entry fetches currently rate-limit at 120 requests per minute per tenant and widget subject.

What to read next