Config
The app.zon manifest declares app metadata, permissions, security rules, window layout, and packaging inputs. It is read by the CLI and tooling at build, package, and validation time.
Example: native-rendered app
The manifest native init generates — identity, one shell window with a GPU surface view, and the minimal permission set:
.{
.id = "dev.native_sdk.my-app",
.name = "my-app",
.display_name = "My App",
.description = "A counter that lives in one native window.",
.version = "0.1.0",
.icons = .{"assets/icon.png"},
.platforms = .{"macos"},
.permissions = .{ "view", "command" },
.capabilities = .{ "native_views", "gpu_surfaces" },
.shell = .{
.windows = .{
.{
.label = "main",
.title = "My App",
.width = 480,
.height = 320,
.restore_state = false,
.restore_policy = "center_on_primary",
.views = .{
.{ .label = "main-canvas", .kind = "gpu_surface", .fill = true, .role = "Counter canvas", .accessibility_label = "Counter", .gpu_backend = "metal", .gpu_pixel_format = "bgra8_unorm", .gpu_present_mode = "timer", .gpu_alpha_mode = "opaque", .gpu_color_space = "srgb", .gpu_vsync = true },
},
},
},
},
.security = .{
.navigation = .{
.allowed_origins = .{ "zero://app", "zero://inline" },
.external_links = .{ .action = "deny" },
},
},
.web_engine = "system",
.cef = .{ .dir = "third_party/cef/macos", .auto_install = false },
}Example: app with web content, menus, and shortcuts
A fuller manifest for an app that also embeds web content and declares commands, shortcuts, menus, and packaging metadata:
.{
.id = "dev.native_sdk",
.name = "native-sdk",
.display_name = "native-sdk",
.version = "0.1.0",
.icons = .{"assets/icon.png"},
.platforms = .{ "macos" },
.permissions = .{ "command", "view", "dialog", "window", "clipboard", "credentials" },
.capabilities = .{ "webview", "js_bridge", "native_views", "gpu_surfaces", "menus", "shortcuts", "dialog", "clipboard", "credentials" },
.bridge = .{
.commands = .{
.{ .name = "native.ping", .origins = .{ "zero://app" } },
.{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.command.invoke", .permissions = .{ "command" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.view.list", .permissions = .{ "view" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.dialog.showMessage", .permissions = .{ "dialog" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.clipboard.readText", .permissions = .{ "clipboard" }, .origins = .{ "zero://app" } },
.{ .name = "native-sdk.credentials.get", .permissions = .{ "credentials" }, .origins = .{ "zero://app" } },
},
},
.security = .{
.navigation = .{
.allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" },
.external_links = .{ .action = "deny" },
},
},
.web_engine = "system",
.cef = .{ .dir = "third_party/cef/macos", .auto_install = false },
.windows = .{
.{ .label = "main", .title = "native-sdk", .width = 720, .height = 480, .restore_state = true },
},
.commands = .{
.{ .id = "command.palette", .title = "Command Palette" },
.{ .id = "app.reload", .title = "Reload" },
},
.shortcuts = .{
.{ .id = "command.palette", .key = "p", .modifiers = .{ "primary", "shift" } },
},
.menus = .{
.{
.title = "View",
.items = .{
.{ .label = "Command Palette", .command = "command.palette", .key = "p", .modifiers = .{ "primary", "shift" } },
.{ .separator = true },
.{ .label = "Reload", .command = "app.reload", .key = "r", .modifiers = .{ "primary" } },
},
},
},
.file_associations = .{
.{
.name = "Markdown Document",
.extensions = .{ "md", ".markdown" },
.mime_types = .{ "text/markdown" },
.icon = "assets/markdown.icns",
},
},
.url_schemes = .{
.{ .scheme = "native-sdk" },
},
}Fields
| Field | Description |
|---|---|
id | Reverse-DNS bundle identifier (e.g. com.example.myapp) |
name | Short machine name |
display_name | Human-readable app name — the application menu, Dock, app switcher, and About panel all use it, in dev runs and packaged bundles alike |
description | Optional one-line description shown in the About panel (max 256 bytes, single line) |
version | Semver version string — also the version the About panel shows |
icons | The app icon: one square .png (1:1, ideally 1024x1024) or .svg source that packaging turns into every platform's icon artifacts — the macOS .icns gets the platform's rounded-rect icon shape applied automatically. A prebuilt .icns/.ico entry ships untouched for its platform instead |
platforms | Target platforms: macos, linux, windows |
permissions | Runtime permissions (see Security) |
capabilities | Feature declarations (see Security) |
bridge | Bridge command policies (see Bridge) |
security | Navigation and external link policies (see Security) |
web_engine | system or chromium; Chromium is currently supported for macOS builds (see Web Engines) |
theme | Built-in theme pack: house (default) or geist; an unknown name is a build/check error (see Theming) |
cef | CEF runtime config for Chromium apps: dir and auto_install |
windows | Window definitions (see Windows) |
shell | Native-first window and view tree declarations |
commands | Shared command metadata for menus, shortcuts, native controls, tray items, and bridge calls |
shortcuts | Keyboard shortcuts delivered as shortcut events (see Keyboard Shortcuts) |
menus | Native menu declarations delivered through the command event path (see Menus) |
file_associations | File type registration metadata for packaging |
url_schemes | Custom URL scheme registration metadata for packaging |
frontend | Frontend build/dev config (see Frontend Projects) |
shell
The optional shell block declares native-first windows with explicit view trees. Existing windows entries remain the simple compatibility path; shell.windows is the richer contract for native chrome, native controls, WebViews, and GPU surfaces.
Tooling parses and validates the schema. Runtime code can return the same shape from App.scene_fn, materialize a parsed shell window with runtime.createShellWindow(...), or attach a shell view list to an existing window with runtime.createShellViews(...); platform hosts implement view kinds progressively, so unsupported native kinds fail at runtime with an explicit unsupported error until that backend grows support.
When an app uses both windows and shell.windows, labels must stay unique across both lists. Use windows for the simple compatibility path or shell.windows for native-first structure; do not define two window entries with the same label.
For a scene-first app — a UiApp passing its Zig scene (shell_scene) to the runner — the scene is authoritative at runtime: it re-applies size, title, and views when it loads. app.zon's .shell.windows[0] exists because the host creates the startup window before the scene loads, and create-time-only properties must come from the manifest: titlebar chrome, min_width/min_height floors, and the show mode (canvas-first windows are created hidden and shown after the first frame presents). The numbers appearing in both places is by design — edit the scene for anything that can change after create (size, title, views), and the manifest for create-time chrome and floors.
.shell = .{
.windows = .{
.{
.label = "main",
.title = "Acme",
.width = 1100,
.height = 760,
.views = .{
.{ .label = "toolbar", .kind = "toolbar", .edge = "top", .height = 44, .role = "toolbar" },
.{ .label = "body", .kind = "split", .fill = true, .axis = "row" },
.{ .label = "sidebar", .kind = "sidebar", .parent = "body", .width = 280, .min_width = 220, .max_width = 360 },
.{ .label = "sidebar-stack", .kind = "stack", .parent = "sidebar", .x = 16, .y = 16, .width = 220, .height = 120, .axis = "column" },
.{ .label = "sidebar-live", .kind = "checkbox", .parent = "sidebar-stack", .accessibility_label = "Toggle live updates", .text = "Live updates" },
.{ .label = "content", .kind = "webview", .parent = "body", .url = "zero://app/index.html", .fill = true },
.{ .label = "canvas", .kind = "gpu_surface", .parent = "body", .width = 480, .gpu_backend = "metal", .gpu_pixel_format = "bgra8_unorm", .gpu_present_mode = "timer", .gpu_alpha_mode = "opaque", .gpu_color_space = "srgb", .gpu_vsync = true },
.{ .label = "status", .kind = "statusbar", .edge = "bottom", .height = 24, .text = "Ready" },
},
},
},
},Each window takes a label plus optional title, width, height, x, y, resizable, restore_state, restore_policy (clamp_to_visible_screen or center_on_primary), min_width/min_height (a content min-size floor the window itself enforces — macOS contentMinSize; the first shell window's declaration threads through the startup create like titlebar, negative values are a manifest error, 0 means no floor), and titlebar (standard, hidden_inset, hidden_inset_tall — the tall variant centers macOS's traffic lights in the 52pt unified band for toolbar-height headers — or chromeless, the fully-skinned opt-in that removes all OS chrome including the system buttons; only for apps that draw their own working window controls, see examples/deck). titlebar = "hidden_inset" hides the titlebar and extends content under it (macOS keeps the traffic lights) — the first shell window's declaration threads through the STARTUP window create, so the main window's chrome is right from the first frame; the app's own header then takes over dragging and inset padding through the window-drag attribute and the on_chrome hook (see Native UI). Platforms without the concept keep standard chrome. The same titlebar field is accepted on top-level windows entries.
Supported kind values are webview, toolbar, titlebar_accessory, sidebar, statusbar, split, stack, button, icon_button, list_item, checkbox, toggle, segmented_control, text_field, search_field, label, spacer, gpu_surface, and progress_indicator.
Each view has a required label and kind. WebView views require url. Optional layout fields are parent, edge, axis, x, y, width, height, min_width, min_height, max_width, max_height, fill, and layer. Optional behavior and accessibility metadata are visible, enabled, role, accessibility_label, text, and command. gpu_surface views may also set gpu_backend, gpu_pixel_format, gpu_present_mode, gpu_alpha_mode, gpu_color_space, and gpu_vsync; those fields are rejected on non-GPU view kinds. The currently implemented macOS system-WebView backend uses metal, bgra8_unorm, timer, opaque, srgb, and gpu_vsync = true. gpu_backend also accepts software (the CPU reference-renderer path); on Linux and Windows system-WebView hosts any declared backend falls back to software presentation rather than erroring. The axis field accepts row or column on parent containers such as toolbar, sidebar, split, and stack; it defaults to row.
createShellWindow and createShellViews dock edge views against the remaining window content, let one or more top-level fill views use the final remaining rectangle, clamp resolved frames with any min/max size fields, and lay out parented controls such as toolbar buttons with small native defaults when explicit x, y, width, or height values are omitted. Parent containers flow omitted child positions horizontally with axis = "row" and vertically with axis = "column". split containers use the same axis without inner spacing, so fixed-size children can sit beside a fill child. The runtime keeps the shell view slice as a layout binding and reapplies it when the window is resized, so pass data that lives for the lifetime of the window.
commands
The optional commands list declares shared command metadata. The runtime still dispatches by command id, but this section gives menus, shortcuts, native controls, tray items, and bridge callers one manifest-level source of truth for user-facing labels and default state:
.commands = .{
.{ .id = "app.refresh", .title = "Refresh" },
.{ .id = "app.sidebar.toggle", .title = "Sidebar", .checked = true },
.{ .id = "app.sync", .title = "Sync", .enabled = false },
},id is required and must be unique. title defaults to an empty string. enabled defaults to true; checked defaults to false.
An app can define up to 256 commands. Command ids can be up to 128 bytes and titles can be up to 128 bytes.
Generated runners load manifest commands into RuntimeOptions.commands. Native code can read the active catalog with runtime.listCommands(...), and trusted WebView code can read it with window.zero.commands.list() when the built-in command bridge allows it. Use the catalog to keep menus, shortcuts, toolbar controls, tray items, and bridge callers aligned with the same command ids.
shortcuts
The optional shortcuts list defines app-level keyboard shortcuts. Generated runners load these automatically:
.shortcuts = .{
.{ .id = "command.palette", .key = "p", .modifiers = .{ "primary", "shift" } },
.{ .id = "reload", .key = "r", .modifiers = .{ "primary" } },
},id is the event identifier. key accepts letters, digits, the unshifted punctuation keys =, -, ,, ., /, ;, ', [, ], \, and `, or a named key. modifiers accepts primary, command, control, option, alt, and shift.
Single-character keys and text-entry keys (enter, tab, space, and backspace) require at least one modifier.
An app can define up to 64 shortcuts. Shortcut ids can be up to 64 bytes and keys can be up to 32 bytes.
Chromium builds are currently macOS-only; use the Linux system WebView backend when an app needs native shortcut events on Linux.
menus
The optional menus list defines native app menus. Generated runners load these automatically:
.menus = .{
.{
.title = "View",
.items = .{
.{ .label = "Refresh", .command = "app.refresh", .key = "r", .modifiers = .{ "primary" } },
.{ .separator = true },
.{ .label = "Sidebar", .command = "app.sidebar.toggle", .checked = true },
},
},
},Each menu has a title and optional items. Command-backed items require label and command. Optional fields are key, modifiers, separator, enabled, and checked. modifiers accepts the same values as shortcuts.
An app can define up to 16 menus and 128 total menu items. Menu titles can be up to 64 bytes, item labels up to 128 bytes, command ids up to 128 bytes, and keys up to 32 bytes.
File associations and URL schemes
The optional file_associations and url_schemes lists declare packaging metadata for document types and custom protocols:
.file_associations = .{
.{
.name = "Markdown Document",
.role = "editor",
.extensions = .{ "md", ".markdown" },
.mime_types = .{ "text/markdown" },
.icon = "assets/markdown.icns",
},
},
.url_schemes = .{
.{ .scheme = "acme-notes" },
},File associations require a name and at least one extensions or mime_types entry. Extensions may include a leading dot, but packaging tools treat the extension as the same value either way. icon is optional and must be a relative path.
URL schemes require a lowercase custom scheme. Reserved schemes such as http, https, and file are rejected.
role defaults to viewer and accepts viewer, editor, shell, or none. Package generation emits this metadata into macOS Info.plist document/protocol declarations, Linux desktop and shared MIME metadata, and a Windows per-user registration script.
frontend.dev
The optional frontend.dev block configures the managed dev server for native dev and zig build dev:
.frontend = .{
.dist = "frontend/dist",
.entry = "index.html",
.spa_fallback = true,
.dev = .{
.url = "http://127.0.0.1:5173/",
.command = .{ "npm", "--prefix", "frontend", "run", "dev" },
.ready_path = "/",
.timeout_ms = 30000,
},
},| Field | Description |
|---|---|
url | Dev server URL to load in the WebView during development |
command | Command to start the dev server (spawned as a child process) |
ready_path | HTTP path to poll until the dev server is ready (default /) |
timeout_ms | Milliseconds to wait for the dev server before failing (default 30000) |
Validation
native validate app.zon
native doctor --manifest app.zon --strict