← All posts
·6 min read·Kennedy Owusu

Flutter apps aren't supposed to update themselves. Mine does.

A store-shipped Flutter app changing its behavior over the air, on a real iPhone — why that's genuinely hard, and what it unlocks.

Here's a thing that shouldn't be possible.

A Flutter app, compiled to native code and shipped through the App Store, changes its behavior a few seconds after I push an update — no resubmission, no review queue, no "a new version is available" prompt, no user doing anything at all. The app they already have on their phone just starts behaving differently. On a real iPhone, over the air.

If your first reaction is "wait, that's not how Flutter works" — good. That was my reaction too, for a long time. It's the correct reaction.

Why the reflex is disbelief

Flutter doesn't ship an interpreter. On release builds it compiles your Dart ahead-of-time straight to native machine code. That's most of why Flutter apps feel fast, and it's also why the conventional wisdom is that you cannot meaningfully update one without going back through the store. The compiled artifact is treated as frozen the moment it's built. Everyone knows this. It's in the architecture.

Which is why, if you're a Flutter developer, the smallest fix costs you a store cycle. One wrong string, one bad conditional, one pricing constant off by a decimal — and on iOS that's hours to days in review before a single user sees the correction. Meanwhile the bug is live.

The React Native world made its peace with this years ago. CodePush let RN developers ship JavaScript fixes over the air; that whole lane is well-trodden. Flutter never got the same thing as a first-class, part-of-your-stack capability. The one serious option that exists is a standalone product you bolt on. Nobody built it into a backend, as one primitive sitting next to your auth and your database.

So I did.

Why this is genuinely hard (and why I'm not worried about telling you that)

I want to be honest about the difficulty, because "we do OTA updates for Flutter" is easy to say, and the internet is full of things that work in a demo and crash on a stranger's device.

The problem lives underneath the SDK, in territory most app developers never touch:

  • AOT is designed to be immutable. The whole point of compiling ahead of time is that nothing changes at runtime. Making a compiled native app accept new behavior means working against the grain of the runtime's core design assumption, not around the edge of it.
  • The compiler throws away anything it thinks you don't need. Release builds aggressively strip out code that isn't statically reachable, to keep binaries small. Anything you might want to swap later has already been deleted before the app ever ships. You're not editing a program; you're editing the survivors of a very thorough deletion pass.
  • The blast radius is a crash on someone's phone. There's no "oops, redeploy." A bad update on a mobile client is a hard crash in the user's hand, at scale, silently, in the field. The correctness bar isn't "it worked when I tried it." It's "it fails closed, every time, on hardware I'll never see."
  • Apple draws a hard line, on purpose. Guideline 3.3.2 governs exactly this space — what a downloaded update is and isn't allowed to do. Any real answer here has to live inside that line, not pretend the line isn't there.

None of that is a recipe. Those are the walls of the room. I'm comfortable describing the room in detail because understanding the constraints is the easy part — every serious Dart engineer knows these walls exist. Getting a store-shipped app to update itself anyway, safely, on real iOS hardware, without tripping any of them, is the part that took me down into the engine and the runtime itself.

And that part I'm not going to write up. The how-to is the company. What I'll tell you is that it's device-proven end to end on a physical iPhone — download, persist, apply on cold launch, app behaves differently — and that one real production app is already running on it today, before we've even launched.

"But isn't this against App Store rules?"

This is the first question every serious person asks, so let me answer it before you have to.

No. An update changes behavior within the bounds of what the app already does and was already approved to do. It does not download a new app, it does not turn your app into a shell that fetches its real self from a server, and it does not change your app's primary purpose. That's the distinction Apple's guideline actually draws, and staying on the right side of it was a design constraint from day one, not an afterthought. If you've ever shipped a server-driven feature flag or a remote config value that changed what your users saw — you've already used the same principle. This is that, taken as far as it can responsibly go.

The part that isn't a trick: free projects never pause

There's a business decision baked into this that I think matters more than the engineering, and it's the opposite of a gimmick because it's structural.

On the free tier, your project never pauses. Not after a week of no traffic, not ever. That's not generosity I'll quietly claw back later — it falls out of how the free tier is built. Free projects run on shared, multi-tenant infrastructure, so an idle one costs us almost nothing to keep warm. Platforms that put every free project on its own dedicated instance have to pause them when idle, because idle dedicated compute is real money burning. We don't have that problem on the free tier, so we don't make it your problem.

(Paid and enterprise tiers are a different story on purpose: those get dedicated infrastructure, which is the thing serious teams actually want to pay for. But nobody on the free tier ever opens their app to a cold, paused backend.)

What this is actually part of

Code push is the wedge, not the whole thing. It's the capability a mobile developer literally cannot get from any other backend today — so it's the reason to show up. But once your updates flow through Koolbase, so does everything else: auth, database, storage, realtime, serverless functions, feature flags, remote config. A backend-as-a-service for Flutter and React Native, with the one primitive nobody else has sitting at the front of it.

Who built this

Me. Solo, from Accra, Ghana. No team, no seed round, one production user already shipping on it, and a public launch on August 1, 2026.

I'm leading with proof rather than claims because I've learned the hard way that Flutter developers can smell vaporware from across the room — so the demo above is a real store binary on a real phone, not a simulator and not a slide.

If you build with Flutter or React Native and you've ever waited on a review queue to ship a fix you already had ready, come kick the tires.

Try Koolbase

Backend-as-a-service for Flutter and React Native, with over-the-air code push built in.

Start Building