Let's demystify the release channels!
In 2017 when I started developing apps for myself using Expo, all I wanted was to have an app live on the App Store & the Play Store.
What I thought was the quickest way to do it back then was Git push master 😮, no branch, no pull request, no staging ENV... When the app was ready, I had to build it for Apple & Android using the Expo CLI. Finally, with my .apk
and .ipa
in my hands, I could publish it by going through the very long and stressful review process of the App Store and the Play Store (this is a whole other topic). And it worked...
A few days later, let's say I have a bug to fix or a feature I want to add. How was I doing it? I had to repeat the same process all over again... So I was doing the changes, pushing on master, building the standalone app and submitting it to the Stores so that the users could see the new version a few days later (provided that the App Store review is accepted). Oh, and they could only see the changes if they had updated my app on their phone!
But wait a moment... That is not what was happening. The users had access to the new version instantly. How was that possible?
I did some googling and I came across OTA updates (OTA stands for Over The Air). What is that?
So here is what Expo says about it:
OTA updates allow you to publish a new version of your app JavaScript and assets without building a new version of your standalone app and re-submitting to app stores
Wow, ok, that was something new for me back then! So Expo is telling me that to make changes on my app I can just write a command and people will see it instantly? That seems like a dream when you usually have to wait a few days to (maybe) get an approval!
How does OTA works? To put it short, you can do any change you want in your javascript app and just write the expo publish
command. Then your app will be minified and 2 versions of your code (iOS & Android) will be uploaded to the Expo CDN.
Ok, but what does it have to do with my users' app on their phone? Good question! What Expo wrote in the documentation is:
By default, Expo will check for updates automatically when your app is launched and will try to fetch the latest published version. If a new bundle is available, Expo will attempt to download it before launching the experience.
That's it! Each time your user opens the app, Expo will check and fetch the latest version of your code that you uploaded with the command expo publish
(more details here)! So there is no need to upload a new standalone build of your app and to go again through the process of review for each Store! (however, there are a few exceptions that you can find here)
This discovery for me was mind blowing! I started using it right away! What a pleasure to fix something and see it available directly on my users' phones!
Ok Julien, but what about release channels?!
Let's get back to them! How did I come across release channels and why OTA updates were not enough by themselves for me? A year ago, I had a freelance project for a client. It was an app which was already live on the Stores and needed to be improved by adding some features/design & bugs fixes. They also needed a way to see the version I was working on, like a staging ENV (they didn't have one at the moment).
I couldn't write expo publish
to show them the WIP because it would have impacted the users. And that is when I came across release channels! You can think of them like different servers where you can upload your app, each with a specific name. So for example, when you write expo publish
, you can also add the flag --release-channel <your-channel>
(eg: expo publish --release-channel staging
) and this way, your app is published to this specific channel. You can add as many channels as you want.
Imagine this simple workflow:
- A production version of your app which is available on
--release-channel production
- A staging version which is available on
--release-channel staging
But how will the users get the right version on their phone?
Let's say you want to build an app and upload it to the Stores, you do expo build:android
or expo build:ios
, right? By default, Expo will automatically add a channel for you which is the default
one.
Remember OTA updates? So the freshly new produced binary will only pull updates published over the default
channel. This way, each time you publish your app with expo publish
(without specifying a channel), expo will publish it on the default
channel and your user will have access to the new version.
We can also improve this workflow by adding different channels! From now on, instead of publishing without specifying a channel, you can publish a production
version of your app with the command expo publish --release-channel production
. This new build will subscribe to the production
channel and listen for new updates.
You just need to upload it to the Stores the first time.
Now, imagine you want to work on a new version of your app and show it to some test users without messing with the live production
version.
You simply need to create another channel, let's say expo publish --release-channel staging
and build a new binary that will subscribe to this specific staging
channel (you can upload this build on Testflight for example and each time you will publish new changes on this channel, your Testflight users will be able to see the Work In Progress! For Android, you can upload this binary on a test release).
When you are happy with your staging app, there is no need to create a new built. You can deploy your changes on the production app by promoting the release to a new channel.
What!? Each time you publish your app, 2 things are created:
a release, identified by a
publicationId
for Android and iOS platforms. A release refers to your bundled source code and assets at the time of publication.a link to the release in the
staging
channel, identified by achannelId
. This is like a commit on a git branch.
In order to see everything that you’ve published, just do expo publish:history
. You will see something like this:
Let's get back to the promotion! If you want to promote your staging
app to the production
one, you just have to identified the publicationId
you want to promote and do expo publish:set --publish-id <publicationId> --release-channel production
.
That's it, your changes are now live in production
! You can do expo publish:history
again to see that the channel has changed!
What if you added a big regression to your users doing that?
No need to worry, just rollback the promotion
channel doing expo publish:rollback --channel-id <channelId>
(Be careful, you rollback the channelId
, not the publicationId
😉)!
This is as simple as that, your release will be back to the staging
channel :)
You can imagine any workflow that suits your needs, for example the one with multiple production
channels corresponding to different versions of your app where you can maintain each version separately!
One more thing!
Now that you know pretty much everything there is to know about release channels, how about accessing channels directly in the code?
For example, this could serve to set ENV variables based on the channel. You can access this info in the releaseChannel field in the manifest object.
Here is the Expo example:
You can create a function that looks for the specific release and sets the correct variable.
function getApiUrl(releaseChannel) {
if (releaseChannel === undefined) return App.apiUrl.dev // since releaseChannels are undefined in dev, return your default.
if (releaseChannel.indexOf('prod') !== -1) return App.apiUrl.prod // this would pick up prod-v1, prod-v2, prod-v3
if (releaseChannel.indexOf('staging') !== -1) return App.apiUrl.staging // return staging environment variables
}
If you are curious to know more about release channels, I invite you to look at the Expo documentation: Release channels & Advanced release channels.
By the way, if you have any question or if I missed something, please don't hesitate to let me know in the comments below!
If you are interested about my next articles, don't forget to follow me here ;)
Top comments (8)
Thank you, that was very helpful. Just one question:
Let's say I add something to my app which can't be published via OTA, like a barcode scanner. There I need to add permissions to "android" in app.json. So now I need to rebuild but as far as I know, rebuilding is also publishing. Does this mean, that users for example already get the new screens I added for the barcode scanner via OTA but can't use it as long as Google did not approve the new build I have to upload?
Or what will happen in this case?
Thank you.
Very good question, I had the same problem when upgrading the Expo SDK :)
Actually, the way I handle it is pretty simple. I just create another production channel.
Let's say my V1 is "prod-01", and I push my OTA when I don't need to add a feature like a barcode scanner for example.
Later, I need to add this feature where I need to have a new built. Ok then, I just create a "prod-02" channel, and this way, I now have 2 prod channels and the first one won't have this new feature until they upgrade the app on the Store.
I also have 2 git branches, one without the new feature and one with it so I can still push new improvements (which don't need a new build) to both channels :)
I hope it makes sense?
but I've a question:
what if my app is like on version 1.0.0 when I released it manually on the stores for the first time then I'm on version 2.0.0 and that has very major updates that changed the app completely, users that are already downloaded the app got the updates whenever I publish and that's okay, but what about new users that downloads my app from the stores, the stores still have the 1.0.0 version, does that mean if new users downloaded my app from the stores at version 2.0.0 they got the 1.0.0 one, and they get terrible experience for their first load of the application until they reload it to get the new updates.
Thank you for the great explanations.
Hi Yassin, there are a few thing you should now. That v1.0.0 you have submitted to the stores has a pre-cached version of your app included for a quick startup.
Also, Updates are handled differently on iOS and Android. On Android, updates are downloaded in the background. This means that the first time a user opens your app after an update they will get the old version while the new version is downloaded in the background. The second time they open the app they'll get the new version. On iOS, updates are downloaded synchronously, so users will get the new version the first time they open your app after an update.
So there are 2 things you can do for those new users who are downloading your app: * create a new build and upgrade app on stores or
updates.fallbackToCacheTimeout
insideapp.json
as it will attempt to download a new update before launching the app.also check official documentation for further information.
Thank you for clarifying that difference on how Android and IOS behaves,
So I made a CI in github actions on my repo, I push the code, it does two things:
1- Publish to Expo.
2- Upload the .AAP to the Stores and submit it for review.
So I've the best of both worlds, users get the updates as quick as possible, and new users have the latest version on the stores, your solution was pretty good too "update your updates.fallbackToCacheTimeout" but late unfortunately.
and that was a very great tutorial for that :
docs.expo.dev/submit/android/#crea...
Thanks for sharing! Clears up Release Channels and their use cases nicely
Hello ! Great article thank you !
I have a small question, I did an update of the build on the store with a prod release channel associate to it ( let's say v1.0.0 )
The next time I'm publishing update ( v1.2.0 ) on that same channel without submitting to the stores, the users that already downloaded the app will get the update, but what about new users that downloaded the app from the store ( which is still v1.0.0 ) ? They will not get the new update right ? Do I have to submit to the store everytime I'm publishing updates to a release channel ?
This is a good read.
Learnt a lot about expo publishing.
Thanks