Chrome stops running your site's cleanup code on September 22, and it already stopped for four out of five visitors
The unload event is being switched off by default, and it is not happening on one date. Chrome has been ramping it down all year: 1 percent of page loads in March, 60 percent in July, 80 percent since August 25, and 100 percent when Chrome 154 ships on September 22. Edge follows on its own schedule and reaches 100 percent at Edge 155. If something on your site saved data, released a lock, or fired a last analytics beacon on the way out, it has been failing intermittently for months and nothing logged it. Here is how to find out in ten minutes, what to replace it with, and why beforeunload is not the thing being removed.
Most breaking changes on the web arrive as a date. You read about it, you put it in the calendar, you fix it the week before, and on the day nothing happens because you fixed it. The unload deprecation is not that. It has been rolling out since March, it is currently switched off for 80 percent of Chrome page loads, and the September 22 date everyone is about to write about is the end of the ramp, not the start of it.
Which means the useful question is not “what will break.” It is “what has already been breaking, quietly, for a percentage of my visitors, since roughly June.”
What is actually changing
The unload event fires when a document is being torn down. It was the obvious place to put code that needed to run as somebody left a page: save the draft, release the record lock, close the socket, send the last analytics ping.
Chrome is changing the default so that unload handlers do not fire at all unless a site explicitly opts back in. Google published the rollout as a percentage of Chrome page loads, spread across eight milestones and about 32 weeks:
| Chrome | Date | Share of page loads with unload disabled |
|---|---|---|
| 146 | March 10, 2026 | 1% |
| 147 | April 7, 2026 | 5% |
| 148 | May 5, 2026 | 10% |
| 149 | June 2, 2026 | 20% |
| 150 | June 30, 2026 | 40% |
| 151 | July 28, 2026 | 60% |
| 152 | August 25, 2026 | 80% |
| 154 | September 22, 2026 | 100% |
Chrome 152 has been the stable release since August 25. So as you read this, four out of every five Chrome page loads on your site are already running with unload handlers dead.
Two details in that table are worth pausing on.
The first is that it is measured per page load, with consistency over time, not per site and not per user. Google chose that deliberately, in the Intent to Deprecate, so the rollout would not concentrate the pain on particular sites or particular people. The practical consequence for you is that the failure mode is not “my site broke.” It is “this works on my machine and fails for some customers,” which is the single hardest bug shape to get reported, reproduced, or believed.
The second is that Chrome 153 is not in the table. The rollout goes from 152 straight to 154. That is a side effect of the other thing happening in September: Chrome moves to a two-week release cycle starting with Chrome 153 on September 8, so milestone numbers now arrive twice as fast as they used to. Skipping 153 keeps the gap between the 80 percent step and the 100 percent step at the four weeks the plan always assumed.
That same cadence change is why September 22 is a genuinely busy day for anyone who runs a website. Chrome 154 is also the release that puts a full-page warning in front of any site that cannot answer over HTTPS. Same version, same day, two unrelated changes.
The event that is not being removed
Before going further, the correction that needs making every time this comes up.
beforeunload is a different event and it is not being deprecated. That is the cancellable one, the one behind “Changes you made may not be saved.” If you have a form with an unsaved-changes guard, it keeps working. Google’s own guidance is to limit beforeunload and attach it conditionally rather than on every page, because it is unreliable in its own way, it does not fire when a background tab is killed. But it is not part of this change and you do not need to do anything about it this month.
What is going away is window.addEventListener('unload', ...), window.onunload, and the jQuery shorthand $(window).unload(...) that a lot of older code still uses.
What on a small business site actually uses this
For a marketing site built in the last few years, honestly: probably nothing, and the change is a free performance win. Skip to the ten-minute check, confirm it, and get on with your day.
The exposure is in the app-shaped parts of a business. In our own client work the recurring patterns are:
Booking and scheduling. A customer opens a time slot, the system holds it, and the hold is released when they leave without completing. If that release runs on unload, the slot now stays held until whatever timeout you built as a backstop, and if you did not build one, it stays held. This is the failure we would look at first, because it is the one that costs money and looks like a mystery rather than a bug: slots vanishing from availability with no booking attached.
Record locking in internal tools. Same shape. Two staff cannot edit the same record, the lock releases on exit, the exit no longer fires, and somebody is now locked out of a customer file by a colleague who closed the tab an hour ago.
Draft and cart persistence. Autosave on the way out. The visitor comes back and their half-filled quote form or cart is empty. Nobody files a support ticket for this. They just do not come back a third time.
Last-gasp analytics. Time-on-page and session-end beacons sent from unload. Current versions of the major analytics libraries moved off this pattern years ago, so the risk here is not Google Analytics itself. It is custom tags, older third-party scripts, and hand-rolled tracking. The symptom is quietly wrong numbers rather than an error, which is why it can run wrong for a year.
Chat, booking and scheduling widgets you embed. These are somebody else’s JavaScript running on your page, and users have been reporting unload deprecation warnings from embedded third-party tags throughout this rollout. Vendors ship fixes on their own schedule and you find out by testing, not by asking.
One thing you can cross off the list: WordPress core is fine. Core replaced its unload handlers with pagehide and pageshow in WordPress 6.4, back in October 2023, covering the classic editor’s post lock release, the text widget editor, post preview cleanup, and the Heartbeat API. Plugins and themes are a separate question, and that is where a WordPress site’s remaining exposure lives.
Edge and Firefox are not on Chrome’s schedule
Chromium is not one browser. Microsoft is running the same deprecation with its own percentages, published in the Edge release notes:
| Edge | Share of page loads with unload disabled |
|---|---|
| 152 and 153 | 60% |
| 154 | 80% |
| 155 | 100% |
Edge 152 shipped August 27, 2026. Edge 154 is targeted for the week of September 24 and Edge 155, the 100 percent step, for the week of October 8. So Edge runs roughly one milestone behind Chrome, and the full switch-off lands about two and a half weeks later. If you have a business customer base skewed toward managed Windows desktops, your real deadline is October, not September, and the intermediate state is messier because two Chromium browsers are running different percentages at the same time.
Firefox never announced a deprecation, but it does not need to for the practical outcome to be similar: Firefox declines to put a page in the back/forward cache at all if it has an unload listener. Safari behaves the same way on desktop. Code that depends on unload firing has been unreliable across every major engine for years. September 22 is when Chrome stops pretending otherwise.
The ten-minute check
Three ways to find out, in increasing order of effort and confidence.
1. DevTools, on your own machine. Open the page, open DevTools, go to Application > Background services > Back/forward cache, and click Test back/forward cache. Chrome navigates away and back, then lists whatever made the page ineligible for bfcache. If you have unload handlers, they show up under Actionable. This takes about thirty seconds per page template and it is the fastest signal you will get.
Run it on your homepage, a product or service page, your booking or contact flow, and any logged-in screen. Templates, not every URL.
2. notRestoredReasons, on real traffic. The PerformanceNavigationTiming object exposes a notRestoredReasons property explaining why a document was not restored from bfcache. An unload listener reports as:
{
children: [],
id: null,
name: null,
reasons: [ { "reason", "unload-listener" } ],
src: null,
url: "https://www.example.com/page/"
}
Ship a few lines that read that on navigation and post it to whatever you already use for logging. Now you are measuring your actual visitors instead of your laptop, which matters when the offender is a third-party script that only loads on some pages.
3. Reporting API with a read-only Permissions Policy. The heaviest option and the most complete one: Chrome supports using the Reporting API together with a read-only unload permission policy to collect reports from the field about where unload is being used. Worth it if you run a large site or a lot of embedded third-party code and you need an inventory rather than a yes or no.
The fix
There is no drop-in replacement, because the thing being removed was never doing what people thought it was doing. There are two events that cover the real cases.
visibilitychange is the one to reach for. It fires when the page becomes hidden: tab switch, window minimise, app backgrounded, navigation away. Google’s guidance is blunt and worth quoting as a rule: treat the hidden state as the last reliable time to save app and user data.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/api/session-end', payload);
}
});
navigator.sendBeacon() is the right transport here. It hands the request to the browser to send in the background and does not care whether the page survives long enough to see the response. A fetch() without keepalive: true will simply be cancelled.
pagehide fires when the user actually navigates away, reloads, or closes the window, and unlike unload it does not disqualify the page from bfcache. The catch, and it is a real one: because the page may be sitting in bfcache, it can be restored after pagehide fires. If you tear down resources there, be ready to rebuild them in pageshow. This is exactly the pattern WordPress core adopted in 6.4.
For the booking and locking cases specifically, the honest answer is that a browser event was always the wrong place for that logic. visibilitychange plus sendBeacon gets you a best-effort release. A server-side TTL on the hold gets you a correct one. If a customer’s phone dies mid-checkout, no client-side event of any kind is going to fire, and it never was. This deprecation is a good excuse to add the timeout you should already have.
The escape hatches, and what they cost
Google shipped three ways to delay this. All of them are real, all of them are supported, and none of them is a fix.
Per site, via HTTP header. To keep unload firing on your own origin:
Permissions-Policy: unload=self
Cross-origin iframes need allow="unload" on the frame element as well. To go the other way and opt into the deprecation immediately, ahead of the rollout:
Permissions-Policy: unload=()
That second one is genuinely useful. It disables unload for first-party code, third-party scripts and extensions in one line, which makes your site’s bfcache eligibility deterministic instead of a coin flip, and it gives you a clean staging environment to test against.
Per fleet, via enterprise policy. ForcePermissionPolicyUnloadDefaultEnabled keeps unload enabled by default on managed devices. This is the one to know about if you have a line-of-business web app that depends on unload and a vendor who has not shipped a fix. It buys the fleet time; it does nothing for your public site’s visitors.
Per developer, via flag. chrome://flags/#deprecate-unload set to enabled brings the deprecation forward for one browser. Use it to test, not to ship.
Google’s stated position is that it plans to support the opt-outs “for the considerable future,” while also stating that the aim is to remove unload completely. Read those together and the opt-out is runway, not a destination. If you use it, put an end date on it.
The part that is actually good news
Every unload handler you remove makes the page eligible for the back/forward cache, and bfcache is the single largest performance win available for repeat navigation. A restored page comes back instantly, with its JavaScript state intact, no network round trip and no re-render. On a site where people browse a few services and hit back, that is the difference between a snappy site and a merely acceptable one, and it costs you nothing but the deletion.
It also feeds the metrics you are already being scored on. We have written before about why INP is the Core Web Vital that actually reflects how a site feels; bfcache eligibility is in the same category of change, in that it is invisible in a synthetic test on your own machine and very visible in field data from real people who use the back button.
So the framing worth holding onto: this is not a migration you are being forced into. It is a cleanup that was overdue, that Chrome is now doing on your behalf whether or not you participate, and that pays you back in speed if you do it properly instead of reaching for the opt-out header.
What we would do this week
- Run the DevTools bfcache test on four or five page templates. Ten minutes. Most sites stop here with nothing to do.
- If anything shows up, find out whether it is your code or somebody else’s. Search your bundle for
unload, and check embedded widgets separately. - For your own code: move it to
visibilitychangewithsendBeacon, orpagehidewith apageshowrestore. - For anything holding a resource, a slot, or a lock, add or verify a server-side timeout. Do not let a browser event be the only thing that frees it.
- Once it is clean, set
Permissions-Policy: unload=()and keep it there, so a future script cannot quietly reintroduce the problem. - Note that your Edge visitors reach 100 percent about two and a half weeks after your Chrome visitors do, and do not close the ticket on September 22.
If you run a booking system, a customer portal, or an internal tool that has been in production for more than a couple of years, this is worth thirty minutes of somebody’s attention before the 22nd. If you would like us to look, get in touch. Checking is genuinely quick, and it is a much better use of an afternoon than working out in October why some appointment slots are disappearing.
Sources
- Deprecating the unload event, Chrome for Developers, published August 10, 2023, timeline updated June 29, 2026, page last updated July 14, 2026, for the rollout table, detection methods, Permissions Policy and enterprise policy names, and the alternatives
- Chrome Platform Status release schedule, Chromium Dash, for Chrome 154’s stable date of September 22, 2026
- Get features faster with Chrome’s two-week release cycle, Chrome for Developers, March 3, 2026, for the cadence change beginning at Chrome 153
- Microsoft Edge release notes for Stable and Extended Stable Channels, Microsoft Learn, version 152.0.4191.53, August 27, 2026, for the Edge unload rollout percentages
- Microsoft Edge release schedule, Microsoft Learn, for the Edge 154 and 155 target release weeks
- Window: unload event, MDN, for Firefox and Safari bfcache behaviour and the recommended alternatives
- Ticket #55491: Replace unload event handlers from core, WordPress Trac, closed fixed in WordPress 6.4 via changeset 56809, October 9, 2023
- Permissions-Policy: unload, Chromium documentation, for the header semantics