Brewser Docs
Publishing

Manifest Reference

Every field in a Brewser app manifest, and how it's generated

Every Brewser app ships a manifest.json describing its identity, the capabilities it needs, and how it should behave on the console. This page is the field-by-field reference.

Brewser generates the manifest for you

You never author the published manifest by hand. You fill in the submission form on brewser.io, and Brewser builds a validated manifest.json from your answers. The manifest below is what comes out of that process and ships inside your app on the SD card. It's useful to understand, but the form is where you set these values.

The submission side validates every field against a strict schema (unknown fields are rejected), so a malformed manifest can't reach the catalogue.

A real manifest, annotated

This is the generated manifest for the Save Demo app (com.natureglass.savedemo), lightly annotated:

{
  "id": "com.natureglass.savedemo",   // reverse-domain id: com.<publisher>.<app>
  "name": "Save Demo",                 // shown in the catalogue
  "version": "1.0.0",                  // semver
  "description": "<p>...</p>",          // rich text (a small HTML allowlist)
  "summary": "A hands-on test harness for the Brewser save & leaderboard APIs.",
  "logo": "assets/appbanner.jpg",      // relative path inside your bundle
  "entry": "index.html",               // the first file Brewser opens
  "categories": ["Apps", "Demos"],     // one or more catalogue categories
  "permissions": [],                    // declared capabilities (see below)
  "compatibility": ["switch", "web"],  // where the app is meant to run
  "allowed_origins": [],                // external hosts the app may reach
  "developer": "@natureglass",      // your publisher display name
  "license": "MIT",
  "tags": ["Leaderboards", "Save Game"],
  "exitGame": "PLUS",                  // controller button that exits the app
  "fullscreen": true,                   // app owns the whole screen
  "hideMouseDocked": false,             // software-cursor behaviour, docked
  "hideMouseUndocked": false,           //   ... and handheld
  "publishedAt": "2026-08-06T10:20:30Z" // set once, on first publish
}

Field reference

FieldRequiredTypeNotes
idYesstringcom.<publisher>.<app>, lowercase. Stable, so don't change it between versions.
nameYesstringUser-facing name. Don't impersonate other products.
versionYesstringSemantic version, e.g. 1.2.0.
descriptionYesstringRich text with a small HTML allowlist (p, br, strong, em, ul/ol/li, code, a).
summaryNostringShort one-liner (≤100 chars) for cards.
logoYesstringRelative path to your icon/banner inside the bundle.
entryYesstringThe first file Brewser opens. Defaults to index.html; must exist in the bundle.
categoriesYesstring[]One or more catalogue categories.
permissionsYesstring[]Declared capabilities; see below. May be empty.
compatibilityYesstring[]Where the app runs: switch, web, mobile.
allowed_originsYesstring[]External https?:// hosts the app may fetch/XHR/WebSocket. Media and image element loads are exempt — see below. Empty if it makes no network calls.
user_originsNobooleanSet when the server address is chosen by the user at runtime. Undeclared origins then raise a one-time on-console approval instead of being denied — see below.
developerYesstringYour publisher display name.
licenseYesstringAn SPDX-style license id (MIT, Apache-2.0, GPL-3.0, …).
exitGameYesstringThe controller button that exits the app (see Controls).
fullscreenYesbooleanWhether the app takes over the whole screen.
hideMouseDocked / hideMouseUndockedNobooleanSoftware-cursor visibility per dock state.
freshProcessOnExitNobooleanAsk the runtime for a clean context on exit.
publishedAtYesstringISO timestamp, set once at first publish and carried forward.
tagsNostring[]Free-form tags for discovery.
genre / featuresNostring[]Curated genre and feature labels.
buttonMappingNoobjectMaps keyboard keys to Switch controls; see Controls.

Field values like categories, license, genre and features are validated against curated lists on the platform, so the form offers you the valid choices rather than free text.

Permissions

permissions is a list of the capabilities your app declares up front. Users see them before launching, and the runtime uses them to decide what your app may touch: network access, local storage, device info, account access, and so on. Requesting a capability your app doesn't use only adds friction, so declare the minimum you need. Apps that request nothing skip the permission prompt entirely, so users can launch them directly.

Hardware access (USB, Bluetooth, and the other device APIs covered in Hardware APIs) is declared here too. Declaring a peripheral says that the app talks to hardware of a given kind. Choosing a specific device (filtering by USB vendor/product id, or by Bluetooth service UUID) happens in your app's own requestDevice() call, exactly as it does in Chrome. Those filters live in your JavaScript, not in the manifest.

The exact set of permission names is a curated list maintained by Brewser, so it can grow over time. If your app needs a capability you don't see offered, mention it in your submission.

Network needs two things

If your app reaches the internet, it needs both a network capability and every external host listed in allowed_origins:

"allowed_origins": [
  "https://api.example.com",
  "https://cdn.example.com"
]

The security scanner cross-checks the hosts your code actually contacts against this list; an undeclared host is flagged. See Networking in Apps for what the runtime can reach, and Security Review for how the check works.

WebSocket hosts count too. An app using Multiplayer declares the room service alongside its other hosts:

"allowed_origins": [
  "wss://ws.brewser.io"
]

Media and images are exempt. The list applies to what your code fetches — fetch, XMLHttpRequest, WebSocket, and <script> / <link> / <iframe> loads. It does not apply to <video>, <audio>, <source> (HLS included), <img> or CSS background-image. Those still need the network capability, but they can load from any host.

That's there so a streaming app can pin what it knows without having to guess what it can't. A Twitch client declares its two API hosts and plays from whichever CDN edge the API hands back:

"permissions": ["network"],
"allowed_origins": ["https://gql.twitch.tv", "https://usher.ttvnw.net"]

The split follows the API that issues the request, not the kind of bytes that come back. If you fetch() an HLS playlist to parse it yourself, that host must be declared; only the URL you hand to <video> is exempt.

Brewser's own *.brewser.io services (Saves, Leaderboards, the wss://ws.brewser.io relay) are exempt too, over TLS only. You never declare those.

When the user picks the server: user_origins

Some apps can't declare anything, because the address is typed by the person using them — a Jellyfin server, a Home Assistant box, a NAS. Set user_origins:

{
  "permissions": ["local_network", "storage"],
  "allowed_origins": [],
  "user_origins": true
}

Brewser then asks once, on the console, before your app reaches a host that isn't in allowed_origins, and remembers the answer for that app. The prompt is drawn by the shell, not by your page, and offers Allow (remembered), Just once, or Deny.

This is tighter than leaving allowed_origins empty, not looser. An empty list on its own means no origin restriction at all; with user_origins set, an empty list means every origin needs approval. That's why the security scanner stops flagging network with an empty list once you opt in.

Nothing else changes: media and image element loads still never prompt, *.brewser.io still never prompts, and a redirect to a host the user didn't approve is refused rather than re-asked.

What doesn't need declaring

Platform features like Saves & Leaderboards need nothing in the manifest; they're not hardware. They work through the brewser.js SDK and only depend on the user being signed in for their cloud parts.

On this page