HeimUI GitHub

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.

Kotlin Multiplatform · Android & iOS from one codebase Native Android · no KMP plugin required

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.

Available

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 →
Available

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 →
Available

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 →
Available

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

  1. Run the Studio — one container, one file on disk, and the settings a deployment can set.
  2. Author a screen and publish it, which is a different act from saving one.
  3. Decide who serves it: the Studio, your backend, or a bucket you own.
  4. Point the app at it — one base URL, and screens render as native Compose.
  5. Turn on signatures when the app should refuse any screen you did not produce. Optional, and off until you ask for it.
  6. 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.