If you study enough companies that attempt dynamic UI or server driven UI, you will notice something that does not show up in the documentation, marketing material, or engineering blogs. The migration does not happen in a single moment. It does not appear as a neat initiative on a quarterly roadmap. It emerges gradually, often unintentionally, through a series of architectural pressures and product demands that shape how the system grows.
Most teams do not decide to build a Zero-Release system, they discover it. A system where backend generates layouts, logic, flows, and even conditions." Instead, they begin with the smallest experiment. They take the safest possible step. They try something that feels reversible. And without noticing it, they start climbing a ladder of complexity and capability. By the time they reach the top, they often wonder how they became a company running a DSL based runtime on millions of devices.
Who this is for and what you’ll get
This article is written for mobile engineering leads, staff engineers, product managers, and CTOs who are already experimenting with dynamic UI, Remote Config, feature flags, or Server-Driven UI and feel that something still isn’t clicking.
If you’re reading this, you’ll recognize exactly which level your system is currently operating at, why progress starts to feel fragile past a certain point, and what a safe, incremental path to a true Zero-Release (Level 4) architecture actually looks like — without rewriting your app or betting the company on a big-bang migration.
What begins as a small attempt to make UI more flexible slowly exposes deeper constraints in how mobile applications are built, shipped, and evolved. Each attempt to remove friction reveals another dependency hiding beneath it. Over time, teams move responsibility from the app to the backend, not because of ideology, but because the system demands it.
What follows is not a recommendation and not a theoretical framework. It is simply the truth of how engineering organizations evolve when they attempt to remove release dependency from their product iteration cycle. These Four Levels of Server-Driven UI Migration are the pattern that repeats across companies of all sizes, from young startups to global marketplaces. Whether the teams recognize it or not, they move through this sequence whenever they begin experimenting with server driven interfaces.
Why UI Is Never the Real Bottleneck in Mobile Architecture
Most teams believe they turn to Server-Driven UI because UI changes are painful. And at first glance, that diagnosis feels correct. Copy tweaks require releases. Layout adjustments wait for approval cycles. Small experiments get bundled into large builds. UI becomes the most visible friction point in the system.
So teams do the obvious thing. They move UI configuration to the backend.
For a moment, things feel better. Visual changes ship faster. Designers stop waiting for engineers. Marketing gains flexibility. It looks like progress. But very quickly, a different kind of friction appears.
Product teams stop asking for visual changes and start asking deeper questions. Not “can we change this copy,” but “can we show this only to certain users?” Not “can we rearrange this screen,” but “can this flow behave differently based on what the user does?” Not “can we experiment with layout,” but “can we learn faster from real behavior?”
Those questions expose something uncomfortable. The real constraint was never UI. It was ownership of decisions.
In traditional mobile architecture, the app owns decisions by default. The app decides who sees what. The app decides how flows behave. The app decides when behavior changes. Releases are not just a distribution mechanism for code. They are the gating mechanism for learning.
UI is simply the first layer teams peel back because it is the most visible and the least dangerous. Once that layer moves, the pressure does not disappear. It moves downward. Responsibility shifts from rendering to selection. From selection to behavior. From behavior to orchestration.
This migration is rarely planned. Teams remove one dependency at a time. Each removal exposes another dependency beneath it. Over time, responsibility moves from the app to the backend not because it is fashionable, but because the system stops working otherwise.
The Four Levels of Server-Driven UI Migration below are not a framework to adopt. They are the natural states a mobile system passes through as it tries to reduce its dependence on releases. Each level answers one question and exposes the next.
The four levels, at a glance
Most teams don’t move to Server-Driven UI in one jump. They climb through four distinct levels, each solving one bottleneck and exposing the next.
- Level 1 - Component-Level JSON
- The backend controls how components look, but the app still decides who sees them and how they behave.
- Level 2 - Backend-Decided State
- The server selects which UI a user should see based on state, experiments, or eligibility, but flows and behavior remain hardcoded in the app.
- Level 3 - Backend-Driven Logic
- The backend begins defining flows and behavior, introducing rules and orchestration often turning JSON into an accidental programming language.
- Level 4 - DSL Runtime (Zero-Release)
- The system formalizes behavior as executable programs, with the app acting as a deterministic runtime rather than the owner of logic.
If you already know where you are, you’ll also know which sections matter most. If you don’t, the rest of this article will make it uncomfortably clear.
Level 1: Component-Level JSON (Basic Server-Driven UI)
When the backend controls structure, but not meaning
Level 1 is the most conservative form of backend-driven UI. At this stage, the backend does not describe content, state, or behavior. It only defines which templates appear on a screen and in what order.
The frontend owns everything else.
A Level 1 response might look like this:
{
"screen": "home",
"layout": [
"hero_card",
"feature_list",
"cta_strip"
]
}This payload communicates structure only. It tells the app which template blocks should be assembled to form the screen, but it says nothing about what those templates contain or what they mean.

All props like copy, images, colors, actions - remain fully defined in native code. The backend cannot influence messaging, personalization, or interaction. It can only rearrange the skeleton of the screen.
Teams usually arrive at Level 1 while trying to solve a very narrow problem: layout churn. By externalizing template composition, they can reorder sections, hide or show blocks, or experiment with structural variations without rebuilding the app.
Importantly, no ownership has moved yet.
It does not say who this card is for.
It does not say whether the user is a member or not.
It does not say whether the card should appear at all.
It does not say what happens after interaction.
Why Level 1 Always Hits a Wall
Component-level JSON feels like progress because it removes visual churn from releases. But it doesn’t change who owns decisions. The backend describes shape, while the app still decides meaning. As long as the client decides eligibility, visibility, and behavior, the experience cannot evolve independently of app updates.
Level 1 reduces noise, not dependency. And once teams try to personalize, experiment, or learn faster, they run straight into that wall.
You know you’re at Level 1 when:
- UI definitions come from the backend, but eligibility logic lives in native code
- Product asks for “show this only to X users” and the answer is still “we need a release”
- The backend doesn’t know why a component is rendered
Level 2: Backend-Decided State (Audience-Aware Server-Driven UI)
When the backend controls structure and presentation
Level 2 begins when the backend stops sending empty shells and starts supplying presentation data. Templates are no longer just placeholders; they are filled with props resolved on the server.
A Level 2 response might look like this:
{
"screen": "home",
"components": [
{
"component": "hero_card",
"props": {
"title": "Discover what’s new",
"subtitle": "Explore features built for you",
"ctaText": "Get Started"
}
},
{
"component": "cta_strip",
"props": {
"label": "Learn more",
"style": "primary"
}
}
]
}At this level, the backend controls how the UI looks, not just how it is arranged. Copy, assets, visual variants, and presentation details can now change without app releases.
\
This is the first point where UI iteration speed improves meaningfully. Designers and product teams no longer wait on mobile builds to adjust messaging or presentation. Marketing surfaces become easier to update. Visual experiments become feasible.
However, meaning and intent still live in the app.
The frontend still decides:
- whether a component should be rendered
- which user contexts it applies to
- what happens when a user interacts
- how flows progress
The backend supplies data, not decisions. It does not know why a component appears or what outcome it is meant to drive. If eligibility rules, navigation logic, or flow behavior change, the app still needs to be updated.
Level 2 increases expressiveness, but it does not yet remove ownership from the client. The system looks more dynamic, but the architecture remains fundamentally frontend-driven.
The transition pressure
Once teams reach Level 2, a new tension appears. Product requests stop being about copy and start being about outcomes. Questions shift from “Can we change this text?” to “Can this behave differently?”
That pressure is what pulls systems into Level 3.

This unlocks real value. Product teams can change who sees what without app updates. Marketing can run UI experiments. Personalization becomes possible at the surface level.
In practice, Level 2 almost always introduces route-specific Backend-for-Frontend (BFF) layers.
Level 2 scales selection, not composition.
What Level 2 Still Can’t Do
Moving audience selection to the backend feels like a major breakthrough and it is. But Level 2 only shifts selection, not behavior. The server decides what to show, but the app still decides what happens next.
As soon as product wants flows to adapt, paths to branch, or behavior to change based on interaction - not just state - the limits of Level 2 become obvious.
You know you’re at Level 2 when:
- The backend decides which components or screens a user sees
- Navigation targets are still hardcoded in the app
- You can change who sees something, but not how the flow behaves
- New onboarding or paywall flows still require shipping app code
- Feature flags are multiplying to compensate for missing orchestration
Why Server-Driven UI Is Not CodePush, Remote Config, or Feature Flags
At this point, most experienced teams pause and ask a reasonable question:
“Isn’t this what we already do with CodePush, Remote Config, or feature flags?”
The short answer is no.
CodePush, Remote Config, and feature flag systems operate on deployment control, not behavioral ownership. They decide when something changes, not where the logic for that change lives.
With feature flags, the app still contains all possible paths. The backend flips switches, but the code that defines behavior is already compiled into the binary. This means learning is limited to what the app already knows how to do. Adding a new flow, changing orchestration, or introducing a new behavioral branch still requires an app update.
CodePush moves faster, but it does not change the architectural contract. You are still shipping code. You are just shipping it through a different pipe. The app remains the owner of logic, flows, and decision-making. You have reduced friction, but not dependency.
Server-Driven UI, as described in the early levels of this article, begins to move selection away from the app. But selection alone is not enough. As long as behavior and orchestration remain embedded in native code, the system can only evolve within the boundaries of what was shipped last.




