Enforcing the latest version of your Power App
In this blog post I will show you what I consider to be the best way of enforcing the latest version of your Power App. There are, of course, a number of ways of doing this, but I have found this method to be the most effective and user-friendly way of doing so.
TLDR: Use a list for versioning, check version in app against most-recently-created version in list, inform user and force-reload app if version is incorrect.
The Problem (Browser caching)
The problem we are facing in this case (and often face) in Power Apps, is Browsers.
Browsers (Edge, Chrome, Firefox, IE) like to think they are really smart, so much so that they will try to cache or ‘keep a copy locally’ of whatever resource you are viewing in order to help it load faster.
This is unfortunate in the case of Power Apps, as any update to an App may take a few page refreshes before the latest version of your Power App is shown. We’ve all been there, asking a user to refresh the page repeatedly.
In the steps listed below I will advise on my pattern for handling this version mismatch and provide the code and steps to do this for your own Apps.
The Basics (Version Numbering explained)
Ok, so before we get into ensuring the latest version of your Power App is the one the user is looking at, let’s have a look at how we should be numbering our versions.
Why do we need version numbering ‘when Power Apps has its own version numbering’? Well, because you can’t access that versioning in any meaningful way for the user.
There are a number of versioning and numbering systems you can use and I will outline below the method I think works best while remaining simple.
The formula we use is:
Prod.Test.Dev
i.e. 3.1.37

There are also some people who extend this to a fourth layer, where is Prod.Test.DevPublished.DevSaved to seperate the saved and published version of the Dev layer, however I have found this to be a bit tedious at times for very little gain as I include comments whenever I save and use these more regularly to identify when an App is leaving Dev.
So, how do we use this three-layer versioning?
When our App is first created, we start with version 0.0.0 – we should ideally set this as a global variable during the App OnStart or else during the OnVisible of the Start Screen.
You should ideally have a separate environment for Dev/Test/Prod, but let’s for the simplicity of this example assume you have one environment and three Apps:
DevApp, TestApp, ProdApp
App.OnStart: Set(gblAppVersion, "v0.0.0")
Every time we make and save a change (always with notes!) in the DevApp, we increase the Dev version number of the internal global variable by one and save that as part of our version notes:
App.OnStart: Set(gblAppVersion, "v0.0.4")
"v0.0.4 - Made a correction to the OnVisible of Home Screen".

When we are happy with the changes that will be going to Test/UAT, we increase the Test version number of the internal global variable by one, then we reset the Dev version number back to zero, then we save and publish.
We then Save As (and either create new if there is no existing TestApp, or else overwrite) our copy of the TestApp with the last published copy of the DevApp. (This is sometimes called ‘pushing’ or ‘promoting’):
App.OnStart: Set(gblAppVersion, "v0.1.0")
"v0.1.0 - Fixes to OnVisible complete. Ready for Testing."

When our testing is complete, we may have feedback and changes from our testers, and so we would increase the DevApp’s Dev version number with each change we make, until we are ready to push the next TestApp version:
App.OnStart: Set(gblAppVersion, "v0.1.24")
"v0.1.24 - Finally fixed bug with label contents"

Now that we are ready for the next round of testing, we update the DevApp‘s Test version number and reset it’s Dev version number and publish our changes for testing. We Overwrite the TestApp with this new copy of the DevApp that is ready for testing:
App.OnStart: Set(gblAppVersion, "v0.2.0")
"v0.2.0 - Bugs raised during testing resolved, Ready for Testing"

If there are no further feedback items from this round of testing, we can update our DevApp‘s Prod version number and reset its Test and Dev version numbers and then save and publish. We can then Save As (either creating new if there is no existing ProdApp or else overwriting) the ProdApp with this most recent version of the DevApp:
App.OnStart: Set(gblAppVersion, "v1.0.0")
"v1.0.0 - All tests cleared, ready for push to Prod"

Then any further changes we do to the DevApp follow the same cycle above of updating the Dev, Test and Prod version numbers, with pushes to TestApp and ProdApp where needed. The example below shows how each individual change would progress through from for each version number.
Dev:
1.0.1 -> 1.0.2 -> 1.1.0 -> 1.1.1 -> 1.1.2 -> 1.1.3 -> 1.2.0 -> 2.0.0
Test:
1.0.0 -> 1.1.0 -> 1.2.0 -> 2.0.0
Prod:
1.0.0 -> 2.0.0

This ensures that our versioning is consistent and indicates to us at any given time what version the user/tester/dev is using when they report any bugs. For Example, if a user reports that they are experiencing a bug with version 2.0.0, but your developers have already resolved this in version 2.3.8, you can let the user know that the issue has been resolved and will be fixed once all tests have passed. The same applies if a Tester says they encountered a bug in 2.3.5, you can let the tester know this has been addressed in 2.3.8 and that it needs testing again once this version is published for testing.
Having a version number also allows us to look back at historic changes and gives us a point of reference when investigating!
So now how do we ensure that whoever is using your app is always using the latest version of your Power App instead of a cached version that the browser has held onto?
The Setup (Table of versions and App label/variable)
We want to be able to check against a list of versions, determine which is the most recent version and then check that against the version we have stored in the App. This way we can make sure that the user has the latest version of your Power App.
To do this we will need to create a list/table containing those items for Dev, for Test, and for Prod (the reason is so that we can modify the expected version number for each part of the cycle without affecting the others). For simplicity’s sake I will use SharePoint, however this works the same in Dataverse / SQL, although with SharePoint you will need to take extra care to handle the security side of things to ensure nobody else is able to modify your version list backend.
In your lists, you only need one Column, VersionNumber, assuming your datasource automatically stores the created date for the item. In my list, Version_TemplateApp_Dev, I created the VersionNumber column after making the Title field non-mandatory.
We start by creating a single item in it, “v0.0.0”

Now let’s look at getting our App set up for use with our version list.
We have a variable defined on the App’s OnStart from previously, so what we’re going to do is show that version number on a label in the App, so let’s create a label, and position it somewhere that’s easy to see but that also does not interfere with our App. Traditionally version numbers are put in one of the four corners of an App, however I will leave that decision to you. I am going to put my version label on the bottom right corner, and you can see it here on my loading screen in the following image.

Now we are going to get the version number from our version list/table and compare it with the version number we have in the App. We start by sorting the list in date order so that the newest item is at the top, then we get the First item from that and extract its VersionNumber field:
App.OnStart:
Set(gblAppVersion, "v0.0.0");
Set(gblAppVersion_List, First(SortByColumns(Version_TemplateApp_Dev, "created", Descending)).VersionNumber)
This allows us to take things a step further, we can now compare those two items and perform some kind of action. I will give an example here of the current actions I use when there are version conflicts.
What we are going to do in the first case is check for a version difference, and if there is one, then navigate to our Version Mismatch screen as we don’t have the latest version of your Power App.
This screen let’s the user know that they are not on the current version of the App and they need to either:
– Wait for the App to refresh itself (via a timer and the launch command)
– or else they can press a button in the screen to force the launch
– or else they can press the refresh button on their browser
This gives the user options, and also let’s them know that if they do nothing, the App will try handle the issue itself.
App.OnStart:
Set(gblAppVersion, "v0.0.0");
Set(gblAppVersion_List,
First(
SortByColumns(Version_TemplateApp_Dev,
"Created", Descending
)
).VersionNumber
);
We have a timer on our loading screen that runs once the page becomes visible, it will first check for version number mismatch, and if there is one then don’t bother loading in all the App data, just navigate to the Version Mismatch screen. If there is no version mismatch then load the data and Navigate to our Home Screen.
LoadingTimer.OnTimerEnd:
If(
!(gblAppVersion = gblAppVersion_List),
Navigate('Version Mismatch Screen');
)
//won't reach here if above is executed
So let’s set up our Version mismatch screen – we want a label to display the current message, and we want a timer that will trigger an App refresh after X seconds, and we want a button where the user can force the reload of the App themselves:

The text I’m using for the label is:
"Please note: there is a more recent version of this App.
The current version is: "& gblAppVersion_List &"
Your version is: " & gblAppVersion &"
The App will refresh automatically in 15 seconds,
or you can press the button below to refresh immediately.
You can also refresh using your browser's refresh button."
Ok, so now we have a page to visit when there is a version mismatch.
NB: This is really important! Even if the browser caches the App, it will now look at the list to see if the version is the same and since the cached version will have the old version number but be able to look at the list directly, it will ALWAYS know when your App version is outdated.
Ok, so now we need this page to refresh the App. Let’s start by making the button’s code launch the URL for the App – you can get the URL by looking at the App details. We should set the Launch URL during our App’s OnStart so that you can easily update it in one place and then use it wherever you need it.
Add to App.OnStart:
Set(gblAppLaunchURL, "https://apps.powerapps.com/play/e/ENVIRONMENT_GUID/a/APP_GUID?tenantId=TENANT_GUID");
Then we have our ‘Reload App’ button launch that URL, and we use LaunchTarget.Replace to have it load within the same tab instead of opening a new tab:
Button.OnSelect:
Launch(gblAppLaunchURL, {}, LaunchTarget.Replace)
So currently if you select the button it will reload the App – it’s worth mentioning here that if you have ‘Confirm Exit’ set on your App, it won’t immediately launch, it will ask the user if they want to leave.
I recommend that you make your ‘Confirm Exit’ setting a conditional one. In our case we only want the warning to show when there is no version mismatch, and to not block the refresh of the App when there is a version mismatch:
App.ConfirmExit:
gblAppVersion = gblAppVersion_List
Now let’s get our timer set up. We set the duration to 15000 (which is 15000 milliseconds, which is 15 seconds), and then we use the OnTimerEnd property to Select the Reload App button:
Timer.OnTimerEnd:
Select(btn_ReloadApp_VMS);
And that’s it! We now have a fully functioning Version Checker for our App.
When we save a new copy of the App we must update our list and our App’s version numbers to match, then publish the App.
If we are publishing a Test or Prod version, we need to update the version in the TestApp or ProdApp and in the Version_AppName_Test and Version_AppName_Prod
Testing (Promote App through Dev > Test > Prod)
Ok, so let’s try test this!
We add a new row into our Version_AppName_Dev list, “v0.0.1”, then we open our DevApp copy of the App:


Ok, excellent, so it works, and it automatically refreshed the page after 15 seconds due to the mismatch – perfect!
With this we can have multiple versions running for each stage of development and not affect them.
So let’s say I promote this DevApp to TestApp, I can now add a new row in the Version_AppName_Test list with my Test Version, “v0.1.0”, and update my TestApp to have the same version.
This works perfectly. Now afterwards, I then update the Version_AppName_Dev list with my new Dev version, “v0.1.1” and then update the DevApp to match that.
The same process can be applied when moving items to Production – this will ensure users have the latest version of your Power App.
Bonus! (Put Apps into maintenance mode)
As the title says, now that we have the Apps talking to version lists, we can temporarily take down a particular App stream instead of just taking down everything!
So let’s say we want to take down the ProdApp for maintenance while we change some of the data structure – we don’t want anyone who currently has access to the App to be making changes and we don’t want to be messing around with the ‘Shared With’ side of the App as that usually involves a lot of accounts or groups.
To enable us to take the ProdApp down for maintenance we can do the following:
Create a new entry in Version_AppName_Prod called “Under Maintenance”

Then in our Apps, we don’t have to change the navigate code, as this version text, “Under Maintenance”, will not match our current App version in the App.
What we do have to change is the text for the label on our Version Mismatch Screen:
If(gblAppVersion_List = "Under Maintenance",
"Please note: this App is currently down for maintenance.
Please check back later or contact IT for assistance"
,
"Please note: there is a more recent version of this App.
The current version is: "& gblAppVersion_List &"
Your version is: " & gblAppVersion &"
The App will refresh automatically in 15 seconds,
or you can press the button below to refresh immediately.
You can also refresh using your browser's refresh button."
)
Extra Bonus! (Tell them when the App will be back)
We can of course expand this concept as much as we want now that we can pull data related to our versions, so you could, for example, store your own version notes in there as an additional column (copy paste from the ones you are saving in the app) in order to keep a properly-accessible list of version notes.
In this example however, we will add a single-line-of-text column called “MaintenanceWindow“, then for our “Under Maintenance” row we will add the details we need.
I will add “15:00 Fri 16/12/2022 – 08:00 Mon 19/12/2022”:

Then in our Apps we can modify the Version Mismatch text to the following:
If(gblAppVersion_List = "Under Maintenance",
"Please note: this App is currently down for maintenance.
Maintenance window:
" & First(
SortByColumns(Version_TemplateApp_Dev,
"Created", Descending
)
).MaintenanceWindow
&"
Please check back later or contact IT for assistance"
,
"Please note: there is a more recent version of this App.
The current version is: "& gblAppVersion_List &"
Your version is: " & gblAppVersion &"
The App will refresh automatically in 15 seconds,
or you can press the button below to refresh immediately.
You can also refresh using your browser's refresh button."
)
The under maintenance message then ends up looking like this:

When you want the maintenance period to end, either create a new row with the previous version number (if there have been no changes to the App), or update both the app and the version number with the next version number if there were App-related changes made.
You can even take this a bit further and have the Onvisible of most screens in your App check for the version mismatch – that way anyone who currently has the App open will also be informed they are now running on an outdated version!
Conclusion!
We have now modified an App so that it can check for the latest version of version of your Power App, advise if there is a version mismatch and then automatically restart when there is one due to browser caching!
AND
We have a way to put our Test/Prod Apps under maintenance when needed!
You can now apply these same steps to any App that you already have to enable the above.
Thank you so much for taking the time to read through to this point, I really do hope you have gained some new knowledge or at the very least some inspiration for your Apps!
If you want to check out my other blog articles you can have a look around here: iAm_ManCat Blog
You can find links to my various social media accounts at the bottom-right of this page 🙂
Hope you have a great day!
Good morning. I have a few question:
1. Regarding your loading screen – I wasn’t familiar with it (still learning) but looked it up and learned about the built-in LoadingSpinner feature. I also found that some add their own GIF/SVG, etc. And others may create a Loading Screen, which is seems like you did (which looks like the screenshot has the gblVersionNumber I was confused about earlier, so I guess that may have been what you named it when you were going through it that time).
Whenever I set my Timer.OnTimerEnd = Select(btnReloadApp); (mine’s named differently, but that’s ok) I get the error: “Select of a control that is not on this screen.” It seems like the discrepancy (at least in my case) is that it’s expecting to have access to the Reload button, but can’t do it from the Loading Screen. Could you elaborate a bit more on the design of the loading page and related items.
2. In regards to versioning – How does that correlate to PowerApps versioning? How do you restore a previous version if needed? To you make any reference/connection between your versioning system and that of Microsoft’s?
3. Your loading screen shows a progress bar. When looking up progress bar in PowerApps, I’ve found info regarding progressing through multiple pages/screen, but nothing related to a Loading Screen.
I’m sure I’ll have other questions as I continue through the tutorial, but these are the issues I’m running into so far.
Thanks,
Clay
Hey Clay!
Wow that’s quite a few questions – happy to answer 🙂
The loading screen I created is part of the template I created to theme/brand Power Apps so that all inserted controls are automatically branded – you can get it here – I have SVGs for the spinner, and I never use the default LoadingSpinner.
When using the Select function, the control you want to Select must be on the same screen as where you are calling the function from – so in my case I have my button on the same screen as the timer, and the timer is not visible.
The Power Apps versioning is poor at best – I do not find it useful as there’s no element of control especially in regards to whether the item is dev/uat/prod – what I do when editing apps is save the same version notes that I will store on the list in the app version notes when saving.
The loading bar in this case is just a standard Power Apps control that I modify the width of to turn it into a loading bar – you can see the details of this in the template link I shared above .
Cheers!
Sancho
In your timer OnTimerEnd, it looks like a typo, since it references a variable that I hadn’t seen mentioned previously. Shouldn’t the code be:
If(
!(gblAppVersion = gblAppVersion_List),
Navigate('Version Mismatch Screen');
Instead of:
If(
!(gblVersionNumber = gblAppVersion_List),
Navigate('Version Mismatch Screen');
Hey Clay!
Thanks for bringing this to my attention – Yes, typo, have updated it now 🙂
Cheers,
Sancho
Thank you so much for writing a detailed blog on browser caching issues with PowerApps.
It would be great if you could clarify couple of queries here:
1. I believe at times, even with reload of the app manually, PowerApps is unable to pick the latest version. So I think the problem may still persist even if we implement this work around unless we clear the cache of the power app when launching it. Sometimes I also feel that it takes a bit of time to load the latest app even after clearing the cache.
2. Also, what do you think of creating only one table App_Versios with 3 parameters such as DEV Version, Test version, PROD version so we do not need to connect the table in every environment and modify the logic accordingly when we promote to the target environment.
We have been facing these issues in our app for a long-time now. Thank you once again for putting up all details here.
Hi Srinivas,
Thanks for reaching out – happy to clarify your queries as best I am able:
1. The caching will always be on the browser side, so if there is a lack of refreshing, this is the browser’s fault (this can be proved by opening the App on a computer that has never opened the app). However, you are right in that there is an approximately 0 to 2 minute delay between hitting publish and the newer version being made available for consumption.
2. In this example I created three tables, one for each environment – the idea was that in the case of Dataverse, you would HAVE to have three separate tables as you cannot reference a table in another environment (however in Dataverse, you could have them all be the same name as you would be promoting the solution through rather than having three with different names).
If you are using SharePoint, I would still advise using three tables, and you could reference them with an environment variable that points to different tables but uses the same variable name between your solutions.
What you have described using a singular list with a column for the parameters is possible, however it’s not something I would advise as there is a lot of room for accidental error and we should, where possible, not have our dev/uat/prod data intermingled.
I’ll give you an example of why we try and mitigate the potential for downtime/damage – let’s say someone changes the column type for your VersionNumber from text to number – that would immediately break all apps connected to the list – if there are three separate lists then either dev OR prod OR uat are affected but only one of the three, but if you have one singular list and something goes wrong, then all versions will not work.
I hope this helps shed some further light on it – let me know if you have other questions around this!
Thank you once again for clarifying my queries with the detailed explanation.