Mini-app SDK reference#

Everything a mini-app can do goes through one global, window.Mafold (also the default export of @mafold/webapp). This page is the complete surface.

Capabilities your app actually uses must be declared when you register it — e.g. --capabilities room,chat.send,storage. Calling a capability you didn't declare rejects with permission_denied.

Properties#

PropertyTypeNotes
Mafold.versionstringSDK semver.
Mafold.embeddedbooleantrue inside a Mafold host, false standalone.
Mafold.initDatastringThe signed launch JWT. Send to your backend and verify it there — see Authentication.
Mafold.initDataUnsafeobject | nullDecoded claims (user, app, conv, …), display-only, unverified.
Mafold.themeobject | nullHost theme tokens; also applied as --mafold-<token> CSS variables on <html>.

Lifecycle#

ready(): void#

Tell the host your UI has rendered. Call once, after first paint — the host may show a loader until you do.

close(): void#

Close the app panel.

Identity#

auth(opts?): Promise<MafoldUser>#

Resolve the current user. Embedded: returns instantly from the verified launch token. Standalone: runs Login with Mafold (OAuth authorization code + PKCE, a public client) and returns the user once they approve.

This is also the only permitted way for an app to identify users — own signup forms and third-party logins are rejected from the registry (see Authentication).

const user = await Mafold.auth();                 // embedded: instant
const user = await Mafold.auth({ appId: "you/todo" }); // standalone: login flow

opts: { appId?: string, conv?: string }.

refreshInitData(): Promise<string>#

Re-mint the launch JWT (it expires after one hour). Updates initData / initDataUnsafe in place, fires the initDataChanged event, and returns the new token. Call it before a backend request if the session may be long-lived.

room — the shared realtime document#

Every conversation the app is open in has exactly one room: a CRDT document any participant can read and write, converging and broadcasting live. This is the multiplayer primitive.

Requires the room capability.

room.open(): Promise<void>#

Join the conversation's room. Call once before other room.* methods.

room.get(key?): Promise<unknown>#

Read the whole document, or one key.

room.set(key, value): Promise<void>#

Set a key. Broadcasts to every open copy.

room.increment(key, by?): Promise<void>#

Atomically add to a numeric key (by defaults to 1). Concurrent increments from different people all count — that's the CRDT guarantee.

room.onUpdate(cb): () => void#

Subscribe to changes. cb receives the current document on every update (yours or anyone else's). Returns an unsubscribe function.

await Mafold.room.open();
const off = Mafold.room.onUpdate((doc) => render(doc.votes ?? {}));
await Mafold.room.set(`votes.${choice}`, true);
// later: off();

storage — per-user private KV#

A small key–value store, private to the current user (not shared with the room). Requires the storage capability.

await Mafold.storage.set("draft", text);
const draft = await Mafold.storage.get("draft");
await Mafold.storage.remove("draft");
const keys = await Mafold.storage.keys();

chat#

Interacting with the surrounding conversation. Embedded-only — in standalone mode these reject immediately with { code: "not_embedded" } (they never hang).

sendMessage(text): Promise<void>#

Send a chat message into the conversation as the current user. Requires the chat.send capability.

ui — panel chrome#

ui.setTitle(title): void#

Set the panel title.

ui.resize(size): void#

Request a panel size: "compact", "tall", or "full".

ui.popup(opts?): Promise<unknown>#

Show a host popup (alert / confirm style).

ui.close(): void#

Close the panel (same as Mafold.close()).

Events#

on(event, cb): () => void#

Subscribe to a host event; returns an unsubscribe function.

EventFires when
roomUpdatethe shared room changed (sugar over room.onUpdate)
themeChangedthe host switched light/dark or accent
initDataChangedrefreshInitData() produced a new token

Low-level escape hatch#

invoke(method, params?): Promise<unknown>#

Call a host capability by name directly. Everything above is typed sugar over invoke; use it only for capabilities the typed surface doesn't wrap yet.

Rejections carry a code: not_embedded, timeout, permission_denied, or unknown_method.

Capabilities are a contract

The host enforces the capability list you registered — and, independently, enforces access on the server. A mini-app capability is not a security boundary on its own; anything sensitive is checked again server-side.