Hot Updater: self-hosted OTA for React Native, configured through plugins
A self-hostable OTA update solution for React Native (Alternative to CodePush)
At a glance
- What is it?
- Hot Updater is a TypeScript OTA update server for React Native and Expo that replaces CodePush with a plugin-based stack you host yourself. The core judgement: the plugin split is genuinely useful, but the licence file is not machine-readable and the 1.0 line is still at release candidate.
- Who is it for?
- Adopt Hot Updater if you already run Supabase, Cloudflare D1 with R2, S3, or Firebase and you want OTA releases to live in infrastructure you control, with a web console for bundle management. Do not adopt it if you need a stable 1.0 tag today, or if you cannot read the licence file and accept its terms, since the repository reports NOASSERTION rather than a recognised SPDX identifier.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: CodePush shut down and left React Native teams without a default
Microsoft retired CodePush for React Native, and the repository positions Hot Updater as an alternative to it. The practical gap is that over-the-air JavaScript updates are a normal part of shipping a React Native app, and without a hosted service you have to build the whole pipeline yourself: somewhere to store bundle archives, a metadata table that maps app versions to bundles, an endpoint the client polls, and a way to roll a bad release back. Hot Updater packages that pipeline as a CLI plus a runtime plus a web console. The audience is teams already comfortable operating a cloud account. If you want a managed service where someone else holds the pager, this is the wrong shape of tool, because the README's first listed feature is that you get complete control over your update infrastructure, which also means complete responsibility for it.
Three plugin slots: build, storage, database
The architecture splits into three plugin categories. A build plugin handles the bundler, with the README naming Metro, Re.Pack and Expo. A storage plugin holds the bundle archives, with AWS S3, Supabase Storage and Cloudflare R2 Storage listed. A database plugin holds the metadata, with Supabase Database, PostgreSQL and Cloudflare D1 listed. The configuration file is a TypeScript module exporting defineConfig, so the plugin choice is a compile-time decision rather than a runtime string. That matters because it means the CLI and the client both derive their behaviour from the same config object. The README also exposes an updateStrategy key, set to appVersion in every example shown, which is how the release is matched to the installed app version. The repository topics list a plugin-system tag, which is consistent with the README's claim of high extensibility, though the README does not enumerate how a third party writes a plugin beyond naming the three categories.
Bundle diffing: bsdiff patches for changed Hermes bytecode
The most consequential mechanism is bundle diffing. According to the README, deploys prepare .bsdiff patches for changed Hermes bundles by default, and a diff-enabled runtime reuses bundle files that already exist on the device instead of downloading the whole archive again. The README gives one concrete figure: a release that would normally ship a 10 MB archive can be delivered as roughly a 600 KB patch when the Hermes bytecode change is small. That number comes from the project's own documentation, not from independent measurement, and it is explicitly conditioned on a small bytecode change, so it should not be read as a typical case. The fallback rules are the part worth reading carefully: if a patch is missing, incompatible, or not worth using, the update falls back to the normal archive path. That fallback is what keeps diffing from being a correctness risk, but it also means your download sizes are bimodal. You either get a small patch or a full archive, and the client has to handle both.
Getting it running: defineConfig and a .env.hotupdater file
Every configuration example follows the same shape. You import a build plugin, a storage plugin and a database plugin, load environment variables with dotenv from a file named .env.hotupdater, and export the result of defineConfig. For Supabase the imports are bare from @hot-updater/bare and supabaseDatabase plus supabaseStorage from @hot-updater/supabase, with keys HOT_UPDATER_SUPABASE_URL, HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY and HOT_UPDATER_SUPABASE_BUCKET_NAME. For Cloudflare it is d1Database and r2Storage from @hot-updater/cloudflare, with HOT_UPDATER_CLOUDFLARE_R2_BUCKET_NAME, HOT_UPDATER_CLOUDFLARE_ACCOUNT_ID, HOT_UPDATER_CLOUDFLARE_R2_ACCESS_KEY_ID, HOT_UPDATER_CLOUDFLARE_R2_SECRET_ACCESS_KEY, HOT_UPDATER_CLOUDFLARE_D1_DATABASE_ID and HOT_UPDATER_CLOUDFLARE_API_TOKEN. For AWS it is s3Storage and s3Database from @hot-updater/aws sharing one options object with bucketName, region and credentials. Firebase uses firebaseStorage and firebaseDatabase from @hot-updater/firebase with applicationDefault() credentials and the GOOGLE_APPLICATION_CREDENTIALS variable pointing at a service account JSON file. Note the Supabase example passes a service role key, which is a server-side credential with full database access; it belongs in the deploy environment, never in the app bundle. The README also documents an agent skill installed with npx skills add hot-updater/skills, after which prompts like $hot-updater deploy using the current app version are handled by a coding agent. That is a convenience layer over the same CLI, not a separate mechanism.
The release channel is split, and the licence is not declared
Two facts about this repository deserve attention before any adoption decision. First, the most recent releases as of the last push are v1.0.0-rc.14 and v0.36.12, both published on the same day, with v1.0.0-rc.0 arriving two days earlier. A project shipping a stable 0.36 line and a release candidate 1.0 line in parallel is telling you that the 1.0 API is not yet frozen. Pinning to 0.36.x means you avoid churn but you are on the branch the maintainers are presumably moving away from. Second, the repository licence is reported as NOASSERTION. That is not a licence; it is the absence of a machine-readable SPDX identifier. The README does not contain a licence section in the material provided, so the terms under which you may use, modify or redistribute this code cannot be confirmed from what is available. For a component that sits in your release pipeline and touches production app binaries, that is a gap you should resolve by reading the LICENSE file directly before you build anything on it. Nothing here is legal advice, and the licence question is the one item on this list I would not proceed past without an answer.
Where it stops being the right tool
Hot Updater assumes you can operate the backing services. If your team has no Supabase project, no Cloudflare account, no S3 bucket and no Firebase project, the setup cost is the cost of adopting one of those platforms, and Hot Updater is only the last layer. The plugin model also means the failure modes are distributed: a storage misconfiguration and a database misconfiguration look different, and the README's examples do not describe what happens when the database says a bundle exists but the storage object is missing. The diffing path adds a second dimension, since a device that cannot apply a patch has to fall back, and the README points to a separate Bundle Diffing guide for the full runtime behaviour and fallback rules rather than spelling them out inline. If your app ships large native changes with every release, the Hermes bytecode delta will be large, the patch will approach archive size, and the diffing machinery stops paying for itself. And if you are on Expo with managed workflow expectations, the build plugin story is documented at hot-updater.dev rather than in the README, so verify your bundler is covered before assuming it is.
The alternative: Expo Updates and EAS Update
The obvious comparison is Expo's own update system, EAS Update, which is the default path for Expo projects and integrates with the Expo build and submit tooling. The difference in approach is where the update server lives. EAS Update is a hosted service operated by Expo; you publish with the EAS CLI and Expo serves the manifests and assets. Hot Updater inverts that: you choose the storage and the database, and the client fetches from infrastructure in your account. The trade is operational surface for control. With EAS Update you do not run a database, you do not manage credentials for a storage bucket, and you do not think about patch fallbacks, because the service handles delivery. With Hot Updater you get a web console for update management, a plugin system that lets you put bundles in R2 or S3 or Supabase Storage, and no third party in the request path. If your constraint is data residency, or a procurement rule that forbids a managed update service, or an existing Cloudflare or Supabase footprint you want to reuse, the self-hosted model is the reason to pick this. If your constraint is engineering hours, the hosted model is the reason to pick that.
Maintenance cost and what to verify before you commit
The maintenance burden here is not the Hot Updater code, it is the stack underneath it. You own the bucket lifecycle, the database schema migrations, the credentials rotation, and the availability of the endpoint your app polls on launch. The repository shows an active release cadence, with three releases in the three days before the last push, including the 1.0.0-rc line. Frequent releases are good for fixes and awkward for pinning: expect to read changelogs between upgrades, particularly while 1.0 is at rc. The licence question is unresolved in the material available and should be settled first, because it determines whether the whole plan is viable. After that, confirm the specific plugin pair for your provider exists in the packages you intend to install, confirm whether your runtime takes the diff-enabled path or the archive path, and decide which release line you are pinning: 0.36.x for stability or 1.0.0-rc for the current API. The README's own numbers, a 10 MB archive reduced to roughly 600 KB, are a documentation claim tied to small Hermes bytecode changes, and the only way to know your ratio is to build a release and inspect the generated patch.
Editorial conclusion
Adopt Hot Updater if you already run Supabase, Cloudflare D1 with R2, S3, or Firebase and you want OTA releases to live in infrastructure you control, with a web console for bundle management. Do not adopt it if you need a stable 1.0 tag today, or if you cannot read the licence file and accept its terms, since the repository reports NOASSERTION rather than a recognised SPDX identifier. Before committing, verify three things: that the storage and database plugin you intend to use exists for your provider, that your runtime supports the diff-enabled path or will fall back to full archives, and that your release process can tolerate the 0.36.x and 1.0.0-rc lines shipping in parallel.
Community notes