File layout
miniapp.json points at the built output of each layer:
The background layer
The host evaluates your background bundle inside a per-miniapp JavaScript context and calls yourregisterMiniapp handler once the connection is ready. This is
where every session.* call lives.
src/background/index.ts
The background context is not a browser and not Node: it’s a bare JS engine
(JavaScriptCore on iOS, QuickJS on Android). There’s no
window, no DOM, and no
module resolver at runtime, which is why build.ts bundles the SDK into your
background output rather than leaving it external.Background runtime APIs
Do not infer background support from what TypeScript or Bun accepts at build time. The background bundle targets a small, cross-platform runtime. MentraOS currently provides these browser-like globals:
Common browser and Node APIs are not available in the background. This
includes
window, document, DOM elements, performance,
XMLHttpRequest, Node built-ins such as fs and path, process, and runtime
require() / dynamic module resolution. Put rendering, DOM work, and browser UI
libraries in src/ui/. Put session.* calls, hardware subscriptions, durable
state, and logic that must continue after the UI closes in src/background/.
Exchange only serializable data through mentra.* / session.ui.*.
The UI layer
The UI is a normal React app. It has zero direct native access: it reaches the glasses only by sending messages to its own background layer. It mounts inside<MentraProvider> and must call mentra.ready() on boot so the host
knows it’s mounted.
src/ui/main.tsx
Talking between layers
The two layers never share memory: they pass messages over a typed bus. You declare every channel once insrc/shared/channels.ts; both halves import it, so
names and payload shapes are enforced at compile time.
src/shared/channels.ts
mentra global:
session.ui:
mentra.send from the UI buffers
until the WebView calls ready(), while session.ui.send from the background
drops silently when no WebView is open (there’s no one to receive it).
There’s no runtime channel registry: channel names are opaque strings on the
wire, validated only by TypeScript. See
Interop & Actions for the separate,
cross-miniapp RPC layer (session.actions).
Lifecycle
- Install: the host unzips the bundle, validates the manifest, spawns the
background JS context, and runs your
registerMiniapphandler. Background is alive. - UI opens: the user taps your tile; the host spawns a WebView, injects the
mentrashim, and loadsui/index.html. The WebView callsmentra.ready(), andsession.ui.onOpenfires in the background. - UI closes: the user navigates away; the host destroys the WebView. The
background context stays alive and
session.ui.onClosefires. - Disable / uninstall: the host tears down your subscriptions and kills the background context.
Crash recovery
If your background layer crashes, the host restarts it automatically. Repeated crashes back off and eventually show the user a “try again” prompt, so aim to reach a stable state quickly on startup.Next steps
The manifest
Declare entries, permissions, and hardware in
miniapp.json.The session
The background-side handle to every glasses capability.
The UI layer
React hooks, the capsule menu, and safe areas.
Interop & Actions
Let other miniapps (and Mentra AI) call yours.

