Embed the What's new widget
The "What's new" widget puts your changelog inside your own website or app: a button that opens a panel with the 10 newest entries, and a red badge with the number of entries the visitor has not seen yet. This page shows how to embed it in plain HTML and in a React app, and how to drive it from your own code.
Before you start
- The changelog is published and has an active custom domain. The widget loads from that domain, so the console shows no snippet until one is connected. See Changelog settings.
- In Settings, Widget, Show the widget is on. When it is off, the widget shows nothing.
- The changelog is public. On a changelog with password access, the widget is off.
The widget's label comes from Button label in the same tab (1 to 30 characters; "What's new" by default), and its colour from the changelog's brand colour.
Add the script
Copy the snippet
In Settings, Widget, under Add it to your site, copy the Script snippet. It has your changelog's domain and slug filled in:
<script src="https://changelog.example.com/widget.js" data-site="acme" async></script>
data-site must be the changelog's slug, and the script must be loaded from the changelog's own domain. If the slug does not match that changelog, the widget does not appear and the browser console says why.
Attributes
| Attribute | What it does |
|---|---|
data-site |
Required. The changelog's slug. |
data-position |
bottom-right, bottom-left or none. Overrides Position in the settings. With none, no floating button is drawn: you open the panel yourself (see below). |
data-selector |
A CSS selector for your own button or link, for example #whats-new. See Use your own button. |
data-theme |
light, dark or auto (the default, which follows the visitor's system setting). |
Use your own button
To open the widget from a link or button in your own navigation instead of a floating button, point data-selector at it:
<button id="whats-new" type="button">What's new</button>
<script src="https://changelog.example.com/widget.js" data-site="acme" data-selector="#whats-new" async></script>
With a selector, the widget:
- draws no floating button,
- puts the unread badge on the top-right corner of every element the selector matches,
- opens the panel below that element when it is clicked (on narrow screens, the panel opens from the bottom of the screen instead).
The elements must already be on the page when the script has loaded; the widget looks for them once.
React example
In a single-page app, load the script once and keep the element it attaches to on the page — for example in your app's header:
import { useEffect } from "react";
const CHANGELOG = "https://changelog.example.com";
const SITE = "acme";
export function WhatsNewButton() {
useEffect(() => {
// The widget runs once per page; a second copy of the script does nothing.
if (document.querySelector(`script[src="${CHANGELOG}/widget.js"]`)) return;
const script = document.createElement("script");
script.src = `${CHANGELOG}/widget.js`;
script.async = true;
script.dataset.site = SITE;
script.dataset.selector = "#whats-new";
document.body.appendChild(script);
}, []);
return (
<button id="whats-new" type="button">
What's new
</button>
);
}
Render WhatsNewButton in a part of the layout that stays mounted, such as the header. If the button is removed from the page and drawn again later, the badge and the click handler do not come back until the page is reloaded; in that case use the JavaScript API below instead.
The JavaScript API
Once the script has loaded, it adds window.EvoHubChangelog:
| Call | What it does |
|---|---|
EvoHubChangelog.open() |
Opens the panel. |
EvoHubChangelog.close() |
Closes it. |
EvoHubChangelog.unread() |
Returns how many entries the visitor has not seen. |
When it has loaded, and each time the count changes, the widget fires an evohub-changelog:unread event on document, with the count in event.detail.count. Use it to draw a badge of your own.
A click anywhere outside the panel closes it, and that includes the click that called open(). Stop that click from bubbling up to the document, or the panel closes as soon as it opens:
<button id="news" type="button">What's new <span id="news-count"></span></button>
<script src="https://changelog.example.com/widget.js" data-site="acme" data-position="none" async></script>
<script>
document.getElementById("news").addEventListener("click", function (event) {
event.stopPropagation();
if (window.EvoHubChangelog) window.EvoHubChangelog.open();
});
document.addEventListener("evohub-changelog:unread", function (event) {
document.getElementById("news-count").textContent = event.detail.count || "";
});
</script>
In React, call event.stopPropagation() in the onClick handler in the same way.
What the visitor sees
- The panel lists the 10 newest entries, pinned entries first, each with its date, version, categories, title and summary. A title opens the entry on your changelog in a new tab.
- The footer links to the whole changelog and, when email subscriptions are on, to Subscribe.
- Entries the visitor has not seen are highlighted and counted in the badge; above 9 it shows "9+". On a visitor's first visit, entries from the last 30 days count as new. Opening the panel marks everything in it as seen.
- The panel closes with Esc, the close button or a click outside it, and keyboard focus stays inside it while it is open.
- The widget's text follows the changelog's Language setting.
What a visitor has seen is kept in their browser's local storage, so it is per browser, not per person. The widget sets no cookies. It keeps the latest entries in local storage for a minute, so page views within a minute do not fetch them again. When the changelog counts its readers, widget opens and clicks are counted too, except for visitors who send Do Not Track or Global Privacy Control.
Embed the list in a page
Where you cannot add scripts, or to show the list inside a page rather than in a panel, use the iframe from the same Add it to your site card:
<iframe src="https://changelog.example.com/embed" title="What's new" loading="lazy" style="border:0;width:100%;height:480px"></iframe>
Under Allowed sites in Settings, Widget, list the origins allowed to show the iframe, such as https://app.example.com (up to 20; http://localhost is accepted for testing). Leave it empty to allow any site. Allowed sites applies only to the iframe; the script works on any site.
Content Security Policy
If your site sets a Content Security Policy, allow your changelog's domain:
script-src https://changelog.example.com; connect-src https://changelog.example.com; style-src https://changelog.example.com; frame-src https://changelog.example.com
script-src and connect-src are needed for the script. style-src is only needed for older browsers that cannot style the widget from the script, and frame-src only for the iframe.
Troubleshooting
- Nothing appears: check that the changelog is published and public, that Show the widget is on, and that the script's address is the changelog's domain and
data-siteis its slug. The browser console shows a warning starting with[EvoHub Changelog]when the slug does not match. - The panel opens and closes at once: your click handler calls
open()without stopping the click; see The JavaScript API. - No badge on your own button: the element the selector names was not on the page when the script loaded, or it was drawn again since.
- A new entry does not show yet: the widget's list can be up to a couple of minutes old in the browser and at the edge.

