What it weighs, and when it loads.
The launcher is 7.8 KB compressed and loads asynchronously. The full chat, 59.5 KB, downloads when a visitor opens it, never before your page has loaded. If either grows past its budget, our build fails.
7.8 KB
brotli, launcher
Appears after your page, on every visit.
+59.5 KB
brotli, chat panel
When a visitor opens the chat. Never before your page has loaded.
67.3 KB
brotli, both together
Everything, once the chat is open.
Sizes from a recorded measurement of the widget build dated November 15, 2026: launcher 7,917 bytes, chat panel 60,911 bytes, brotli at quality 11. 1 KB is 1,024 bytes.
Your page first. The chat when someone asks for it.
Four stages, in the order they happen. The timeline shows order and relative size, not time.
A replica on sample data, on a made-up shop's page: a timeline of a page load in two lanes, where the two widget sizes are the real ones. Your page's HTML, CSS and images load and paint first, then the chat launcher appears, 7.8 KB with brotli. The full chat, 59.5 KB more, downloads when a visitor opens it and never before your page has loaded, and the widget build fails if either bundle grows past its size budget.
Your page loads
The snippet is a single script tag with the async attribute, so the browser keeps parsing and rendering your HTML while the launcher downloads. The launcher begins its work once your document has been parsed.
Settings, then a pause
It fetches your workspace settings, giving up after 3 seconds and using defaults, and records the page view. Then it waits: 1 second after your page's load event by default, or, with the LCP option on, until your Largest Contentful Paint settles, for at most 8 seconds.
The launcher appears
Only now does it add anything to your page: one element holding a closed shadow root with the button inside, in your colors from its first frame. While the chat is closed, it checks for unread replies every 30 seconds.
The chat, when it is needed
When a visitor opens the chat, the launcher adds the chat bundle as a second async script and shows the panel's outline until it is ready. It downloads once per page view, never before your load event. A page that passed a signed-in user, or a visitor with an unread reply, gets only a 2.3 KB helper after load, to send the user or show the reply.
Every script is served brotli compressed to browsers that accept it, gzip to the rest. The launcher is cached for an hour; the helper and the chat bundle are cached for a year under their version, and a new build is a new URL. For your Content Security Policy, see the CSP sources and caching.
If it grows, the build fails.
Every widget build compresses each bundle with brotli at quality 11 and compares it with a fixed budget. When a file is over, the build prints OVER and exits with an error.
const BUDGETS = {
'launcher.min.js': 8 * 1024,
'conversation.min.js': 60 * 1024,
'helper.min.js': 3 * 1024,
'lang/<code>.js': 3 * 1024
}Today's build against its budget
Launcherlauncher.min.js
7.8 KBof 8.0 KB
0.2 KB below the budget
Helperhelper.min.js
2.3 KBof 3.0 KB
0.7 KB below the budget
Chat panelconversation.min.js
59.5 KBof 60.0 KB
0.5 KB below the budget
Largest language filelang/<code>.js
2.9 KBof 3.0 KB
0.1 KB below the budget
It keeps to itself.
The widget adds one element to your page and keeps everything it draws inside it. Here is exactly what it touches.
A closed shadow root
Everything the widget draws lives in a closed shadow root on one host element. Its styles are injected there, never into your document, and the host resets inherited styles with all: initial.
Two documented globals
window.bettercom, the function you call, and window.bettercomConfig, the settings you may set. Once loaded, the helper and the panel each add one internal handle, window.__bettercomHelper and window.__bettercomConversation.
Navigation, observed
To count page views in single-page apps, the launcher wraps history.pushState and replaceState, always calling the originals, and listens for popstate and hashchange.
Your domains only
Set allowed domains under Settings, Installation, Allowed domains, and requests from any other origin are refused with HTTP 403, so the widget does not appear there.
Five minutes with DevTools.
You do not have to take our word for any number on this page. Any page that runs the widget will do.
- Open a page that runs the widget in Chrome, Edge, or another Chromium browser, and open DevTools.
- In the Network panel, tick Disable cache, choose the JS filter, type widget in the filter box, and reload.
- One script appears: the launcher, requested from /v1/widget/ followed by the workspace id. Its response headers include content-encoding: br.
- Compare its Transferred column with the launcher size above. Transferred includes the response headers, so it reads slightly higher than the file itself.
- Before the load event, nothing else is in the list. After it, helper.js appears only if the page passed a signed-in user or a reply is waiting. conversation.js stays out until you click the launcher, and then it appears once.
- In the Elements panel, find the div with the id bettercom-root at the end of the body. DevTools marks its shadow root as closed.
What these numbers are not.
- No load times or Core Web Vitals
- The figures above are bytes, not milliseconds. We will publish LCP, INP and CLS figures only with a harness anyone can rerun and dated results.
- No comparisons
- This page does not compare the widget with other chat products. That waits for the same reproducible benchmark. For how other widgets weigh, each figure with its source, see Bettercom vs Crisp and Bettercom vs Tidio.
- No time scale
- The load-order animation shows the order of events, not a benchmark. Its bars show relative size and are not drawn to a clock.
- No promise about your network
- A budget limits what we ship. How fast it arrives still depends on your visitors' connections and devices.
Add chat. Keep your speed.
Start a 14-day trial, or ask us in the chat on this page, which runs on Bettercom.
