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#
| Property | Type | Notes |
|---|---|---|
Mafold.version | string | SDK semver. |
Mafold.embedded | boolean | true inside a Mafold host, false standalone. |
Mafold.initData | string | The signed launch JWT. Send to your backend and verify it there — see Authentication. |
Mafold.initDataUnsafe | object | null | Decoded claims (user, app, conv, …), display-only, unverified. |
Mafold.theme | object | null | Host 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 flowopts: { 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.
| Event | Fires when |
|---|---|
roomUpdate | the shared room changed (sugar over room.onUpdate) |
themeChanged | the host switched light/dark or accent |
initDataChanged | refreshInitData() 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.