Every Mentra miniapp has two layers. Understanding the split is the key to the whole SDK: it determines where your code runs, what it can reach, and how the two halves communicate.

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 your registerMiniapp handler once the connection is ready. This is where every session.* call lives.
src/background/index.ts
The background runs even when no UI is open. Register your subscriptions in the handler; they live for as long as the miniapp is enabled.
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.*.
If an AI coding agent introduces performance.now(), DOM access, or a Node API under src/background/, move that code to the UI or replace it with a supported primitive. For elapsed time in the background, use Date.now().Projects created with create-mentra-miniapp also check background source and its imported shared files during bun run build. Common unsupported browser or Node APIs fail the build with the source location and a supported alternative.

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 in src/shared/channels.ts; both halves import it, so names and payload shapes are enforced at compile time.
src/shared/channels.ts
From the UI, use the injected mentra global:
From the background, use session.ui:
The two sides are intentionally asymmetric: 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

  1. Install: the host unzips the bundle, validates the manifest, spawns the background JS context, and runs your registerMiniapp handler. Background is alive.
  2. UI opens: the user taps your tile; the host spawns a WebView, injects the mentra shim, and loads ui/index.html. The WebView calls mentra.ready(), and session.ui.onOpen fires in the background.
  3. UI closes: the user navigates away; the host destroys the WebView. The background context stays alive and session.ui.onClose fires.
  4. 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.