Ship native screens from your backend.
HeimUI is a Server-Driven UI engine for Kotlin Multiplatform. Your backend returns JSON describing a screen; the SDK renders it as native Compose on Android and iOS. Changing a screen becomes a deploy, not an app release.
Compose Multiplatform’s Android target is Jetpack Compose — the same
androidx.compose
classes, no duplication — so an ordinary Android module consumes it with nothing special in the
build. The only requirement is that your app uses Compose.
Mobile SDK
Installation, the component catalogue, actions, theming and the four levels of customisation.
Works in a Kotlin Multiplatform project and in a plain Android one — no KMP plugin required.
Read the docs →Studio
The editor for building screens without writing JSON, and publishing them to your own infrastructure — drafts and releases, environments, read keys and rollback.
One container and a SQLite file on your disk. It signs every screen it serves, and you hold the key.
Read the docs →Backend
Merging a screen with your data: the binding rules, and how to report an expression that resolved to nothing before a user sees it.
Reference implementations in Python and Node, and the 22-case corpus that proves yours is correct.
Read the docs →Object storage
Optional. Mirror published screens into a bucket you own, so an editor is never in the request path of a live app.
S3, R2, Spaces or MinIO — the object layout, the two policies, and the one permission everybody forgets.
Read the docs →What you run
All of it, on your own infrastructure. There is no account to create and nothing of ours between your screens and your users: the Studio is one container against a file on your disk, and the SDK only ever talks to hosts your app names.
YOUR INFRASTRUCTURE THE DEVICE
Studio ──┬── serves /screens ────────────────────────▶ your app
(editor) │ + HeimUI SDK
├── template ──▶ your backend ──▶ hydrated ──▶
│ (your data, your auth)
└── publishes ──▶ your bucket / CDN ─────────▶
The Studio serves them
Fewest moving parts, and where everyone starts. The editor answers the device directly, with environments and read keys deciding who may read what.
Your backend serves them
For a screen that shows data — a balance, a cart, a name. Your service reads the template, fills it with what it knows, and answers. Your auth never leaves your stack.
A bucket or CDN serves them
For screens with no data in them, and when no editor should sit in the request path of a live app. The Studio mirrors every publication into a bucket you own.
They are not exclusive: most teams serve a login page from one and a home screen from another. Two of the three are optional, and the SDK does not know the difference.
The order it happens in
- Run the Studio — one container, one file on disk, and the settings a deployment can set.
- Author a screen and publish it, which is a different act from saving one.
- Decide who serves it: the Studio, your backend, or a bucket you own.
- Point the app at it — one base URL, and screens render as native Compose.
- Turn on signatures when the app should refuse any screen you did not produce. Optional, and off until you ask for it.
- Operate it: access, the signing key and what to back up.
Why it is built this way
Rendering JSON is the easy part. What separates a toy from something you put in front of users is what happens when things go wrong — a payload from a newer server, a network that drops mid-form, a cache written by an attacker on a rooted device.
Forward compatible
A component type the client has never heard of degrades to nothing and reports itself. An old app does not break on a new payload.
Fails closed
Submissions reach allow-listed hosts only, URLs open allow-listed schemes only, and signed payloads are re-verified when read back from cache.
Degrades, never blanks
Stale-while-revalidate, ETag revalidation, a circuit breaker, timeouts, and an emergency bundle compiled into the app.
Yours to extend
Nine providers, native components addressed by name from the payload, and a data layer you can replace outright.