Repository navigation
Flutter web - Need documentation on how to conditionally display a "Refresh. New Version is available." prompt when updated code is deployed to production. #104509
Description
We need a foolproof way to be able to force a download of main.dart.js when code is updated and deployed to web live. E.g. if a user is on my flutter website and I deploy a new build and they click to a new screen, they should see new code updates without having to refresh their browser, or in the service worker js in web/index.html, be able to conditionally display a prompt to the user to "Refresh. New Version is available".
Google Messenger on web and other web interfaces are able to detect new code is deployed without a browser refresh and a "Refresh for new version" prompt is displayed. We need this ability via the service worker js code in web/index.html.
In typical html sites the foolproof way to force js/css updates, while a user is actively on your site, is to change the js/css filename reference, which forces a download of the new js/css code immediately, when the user navigates to any new screen on your site. This pattern does not require a prompt at all, but if we can't do that in flutter web, having the ability to display a refresh now prompt would be just fine.
This is a serious issue, for obvious reasons. If users are not getting updated code after a build and deploy to live hosting and we have to hope that users will press their browser cache refresh button, at some point, then there will be all sorts of problems.
It would be great for this behavior in web/index.html to occur by simply changing the version number in pubspec.yaml, and the flutter build web and deploy to hosting would guarantee the user would get new code base when they navigate to a new screen or a refresh prompt.
This is web/index.html
var serviceWorkerVersion = null; after running flutter build web does get a new version number on each build, but this is not forcing the serviceWorker to refresh main.dart.js where all code updates should be after a new build and deploy to live hosting. Only a browser refresh where the user is required to press the browser refresh button or clear the cache does main.dart.js update with new deployed code.
The console.log('Installed new service worker.'); only runs when you do a full browser refresh. This never runs after a deploy with new code to hosting without a full browser refresh. E.g. if you deploy new code to hosting and then click around without a browser refresh it will not log a new service worker is installed.
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>Logs
flutter doctor -v
[✓] Flutter (Channel stable, 3.0.1, on Manjaro Linux 5.4.192-1-MANJARO, locale en_US.UTF-8)
• Flutter version 3.0.1 at /opt/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision fb57da5f94 (5 days ago), 2022-05-19 15:50:29 -0700
• Engine revision caaafc5604
• Dart version 2.17.1
• DevTools version 2.12.2
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0-rc2)
• Android SDK at /home/nate/Android/Sdk
• Platform android-31, build-tools 31.0.0-rc2
• ANDROID_HOME = /opt/android-sdk
• ANDROID_SDK_ROOT = /home/nate/Android/Sdk
• Java binary at: /opt/android-studio/jre/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
• All Android licenses accepted.
[✓] Chrome - develop for the web
• CHROME_EXECUTABLE = /usr/bin/google-chrome-stable
[✓] Linux toolchain - develop for Linux desktop
• clang version 13.0.1
• cmake version 3.23.1
• ninja version 1.10.2
• pkg-config version 1.8.0
[✓] Android Studio (version 2021.2)
• Android Studio at /opt/android-studio
• Flutter plugin version 67.1.2
• Dart plugin version 212.5744
• Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
[✓] Connected device (3 available)
• sdk gphone x86 64 arm64 (mobile) • emulator-5554 • android-x64 • Android 11 (API 30) (emulator)
• Linux (desktop) • linux • linux-x64 • Manjaro Linux 5.4.192-1-MANJARO
• Chrome (web) • chrome • web-javascript • Google Chrome 101.0.4951.64
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!Note: I use firebase hosting. I tried adding the following headers to my firebase.json to prevent caching, but neither header forces a refresh of code updates deployed live.
"headers": [
{ "source":"/main.dart.js", "headers": [{"key": "Cache-Control", "value": "no-cache"}] }
]
"headers": [
{ "source":"/service-worker.js", "headers": [{"key": "Cache-Control", "value": "no-cache"}] }
]
Activity
darshankawar commentedon May 25, 2022
@Zelfapp
Thanks for the report. Can you take a look at this similar issue and specifically this issue comment and check if it helps to answer your question ?
Zelfapp commentedon May 25, 2022
Nothing is jumping out to me on either of those threads and I had visited those before posting my report. It would be great to be able to display a "new version refresh now" prompt. That is exactly what I'm looking for.
@darshankawar unless I'm missing something, this condition is never met without a browser refresh to see that there is new code deployed. I have been testing this and feel pretty confident this condition is never caught without a browser refresh. To test, I've been deploying code updates to web hosting, and clicking around my site watching the console logs, but this condition is never caught clicking around the site. It is only caught when there is a browser refresh, so displaying a prompt when this condition is caught is pointless because the browser has already been refreshed to hit this condition.
This is interesting and appears to solve the issue of recognizing new code has been deployed without a browser refresh, and will display a prompt, but where is the documentation on flutter.dev to implement this for flutter web? Or this appears to be "non-flutter core" work that at one point was under development, but I can't seem to find it on pub.dev or anywhere else.
darshankawar commentedon May 25, 2022
Thanks. The PR you linked above was pretty old and seems he never came back to it, so the status still stands as-is.
I am going to label this as documentation request.
[-]Flutter web - Need documentation on how to programmatically clear cache[/-][+]Flutter web - Need documentation on how to conditionally display a "Refresh. New Version is available." prompt when updated code is deployed to production.[/+]MingSern commentedon Oct 14, 2022
kevmoo commentedon Nov 8, 2022
I'm adding this to my tracking list. We'll either update the docs or fix the framework – or both if needed! Thanks for your patience!
yog-ka commentedon Dec 15, 2022
Any changes in version 3.x?
69 remaining items
DavidOrakpo commentedon Oct 29, 2025
kevmoo commentedon Oct 29, 2025
This would be a GREAT issue for a community member to tackle.
I'm pretty sure this is a solved problem for any SPA/PWA. We just need to understand the Flutter-specific glue.
dsyrstad commentedon Oct 29, 2025
@kevmoo @DavidOrakpo For a solution, see my comment above: #104509 (comment). This solution has been working for us for a few months now.
DavidOrakpo commentedon Oct 29, 2025
Thank you! Can you go into more detail in how you built and set up your REST service? How do I replicate it
luis901101 commentedon Oct 29, 2025
Hi, sorry if this is out of context but I have been using pwa_update_listener since always and it works just fine. I just made a PR today to use
package:web/web.dartinstead of deprecateddart:html.dsyrstad commentedon Oct 29, 2025
@DavidOrakpo
We have a database table that stores the current versions for web, Android, and iOS (e.g. "1.2.22+167" - "<version>+<build-number>"). We store these as distinct values for each platform. When you query the REST endpoint, we simply query the table and return the versions as JSON. We have separate versions for each platform due to publishing delays for Android and iOS. We also have a "minimum required version" with an effective date. This is usually an older version (one version back) that the user must, at minimum, be running and must upgrade from if their version is less than the minimum. If they are above the minimum required version, but still not up to date, we just recommend that they update.
On the Flutter side, we generate the pubspec version and build number as
consts in the code. This is because other methods like reading and comparingversion.jsonas described elsewhere in this issue lead to getting out of date versions due to browser caching issues. Whatever is actually in your code is what you're running, not some externally cached file. We then compare theconstbuild number with the one we received from the REST call for the applicable platform. If the server-provided build number is greater than the app'sconstbuild number, the user is prompted to upgrade and thenupdateApp()(see my comment) is invoked.DavidOrakpo commentedon Oct 29, 2025
Thank you! I'd give this a shot and revert
DavidOrakpo commentedon Oct 30, 2025
24 hours later, and it works! Awesome!
anggaaryas commentedon Mar 25, 2026
use this method solve my problem, so the version will be automatically changed when there are new build
zs-dima commentedon May 8, 2026
@Zelfapp @kevmoo, please, take a look at a significantly improved solution:
https://pub.dev/packages/sw
this package solves Cache invalidation and displays an example "Refresh. New Version is available."
philitell commentedon May 11, 2026
Is there an official recommendation from flutter how to solve this?
seba-db commentedon May 11, 2026
Here is my solution. Has been working great so far. The solution works by creating a version.json file that stores the last version and storing the last detected in SharedPreferences. Then polls the last version every 5 minutes. The key files (main and bootstrap) are renamed to include a hash, so every new commit has a new file, and the cache invalidates them.
I am including here the script to run after the build, the service, provider and banner to add to your app. Took me several tries to have something that truly works. Working now on Vercel.
Cache Buster:
Version Check Service:
Version Check provider:
Banner Widget:
Ortes commentedon Jun 12, 2026
For anyone landing here: the underlying blocker for a reliable "new version available" prompt on web is that the running bundle can't know its own version —
package_info_plusreadsversion.json, fetched from the server, which reflects the deployed version rather than the executing (cached) bundle.Proposals to fix this at the source: #187935 (tool injects
FLUTTER_BUILD_NAME/FLUTTER_BUILD_NUMBER, likeappFlavor) and dart-lang/sdk#38855 (+CL https://dart-review.googlesource.com/c/sdk/+/512380,dart.package.versionfor the Dart CLI). Either makes the running-vs-deployed comparison trivial;package_info_plusis ready to consume them (fluttercommunity/plus_plugins#3874).