# Packaging

> From ReleaseFast binaries to distributable app bundles: assets, build graph outputs, and packaging layout expectations.

- Repository: vercel-labs/native
- GitHub: https://github.com/vercel-labs/native
- Human docs: https://grok-wiki.com/public/docs/vercel-labs-native-8ccc3580636a
- Complete Markdown: https://grok-wiki.com/public/docs/vercel-labs-native-8ccc3580636a/llms-full.txt

## Source Files

- `docs/src/app/packaging/layout.tsx`
- `src/tooling/assets.zig`
- `src/tooling/buildgraph.zig`
- `packages/native-sdk/scripts/build-binaries.sh`
- `examples/calculator/README.md`

---

---
title: "Packaging"
description: "From ReleaseFast binaries to distributable app bundles: assets, build graph outputs, and packaging layout expectations."
---

`native build` installs a **ReleaseFast** executable at `zig-out/bin/<name>` (from `app.zon` metadata). `native package` then copies that binary, mirrors assets, generates icons and platform metadata, and writes a platform-specific artifact under `zig-out/package/`. Implementation lives in `src/tooling/package.zig` (`createPackage`); zero-config apps reach the same packager through a generated build graph in `.native/build/` without ejecting.

## Pipeline

```text
app.zon + src/ [+ assets/]
        │
        ▼
 native build  ──►  zig-out/bin/<name>     (ReleaseFast default)
        │              ▲
        │              │ --binary / auto-discover
        ▼              │
 native package ───────┘
        │
        ├── macos   → zig-out/package/<name>.app
        ├── linux   → zig-out/package/<name>-linux/
        ├── windows → zig-out/package/<name>-windows/
        ├── ios     → generated Xcode host project (+ Libraries/libnative-sdk.a)
        └── android → generated host project (+ optional <name>-debug.apk)
```

Zero-config apps do **not** require `native eject` to package. `native build` / `native package` synthesize `.native/build/{build.zig,build.zig.zon}` via `buildgraph.ensureGeneratedBuild` when no owned `build.zig` exists, point Zig at that graph with `--prefix` set to the app’s `zig-out/`, and keep the framework dependency relative to the generated build root.

Ejected or scaffolded apps that already own `build.zig` get an additional graph step: `zig build package` runs the `native` CLI artifact with `--binary` bound to the built executable and `--optimize` set to the app’s real mode (default **ReleaseFast** for the app module).

## Commands

### Build then package

```bash
native build
native package --target macos
```

Success signals:

- `native build: built zig-out/bin/<name> (ReleaseFast)`
- `info[package.binary]: using zig-out/bin/<name>` (when auto-discovering)
- diagnostic `package.created`: `created <target> artifact at <path>`

Override optimize only when the packaged binary was not built as ReleaseFast:

```bash
native package --target macos --binary zig-out/bin/myapp --optimize Debug
```

### CLI surface

```bash
native package \
  [--target macos|windows|linux|ios|android] \
  [--manifest app.zon] \
  [--output path] \
  [--binary path] \
  [--assets path] \
  [--optimize ReleaseFast] \
  [--web-engine system|chromium] \
  [--cef-dir path] \
  [--cef-auto-install] \
  [--signing none|adhoc|identity] \
  [--identity name] \
  [--entitlements path] \
  [--team-id id] \
  [--archive]
```

| Flag | Default | Role |
| --- | --- | --- |
| `--target` | `macos` | Package target (`PackageTarget`) |
| `--manifest` | `app.zon` | App metadata source |
| `--output` | `zig-out/package/<name>.app` (macOS) or `zig-out/package/<name>-<target>` | Artifact root |
| `--binary` | Desktop: `zig-out/bin/<name>[.exe]`; iOS/Android: builds embed lib | Executable or `libnative-sdk.a` |
| `--assets` | `frontend.dist` when set, else `assets` | Tree copied into the package |
| `--optimize` | `ReleaseFast` | Label on report/archive names; must match the binary |
| `--signing` | `none` | macOS signing mode |
| `--archive` | off | Create `.dmg` / `.zip` / `.tar.gz` after the package |

Shortcuts (same packager, fixed defaults):

```bash
native package-windows [--output path] [--binary path]
native package-linux   [--output path] [--binary path]
native package-ios     [--output path] [--binary path]
native package-android [--output path] [--binary path]
```

Mobile shortcuts default output under `zig-out/mobile/{ios,android}` and still use ReleaseFast for the embed library when `--binary` is omitted.

### Asset-only bundling

```bash
native bundle-assets [app.zon] [assets-dir] [output-dir]
# defaults: app.zon → frontend.dist or assets → zig-out/assets
```

`src/tooling/assets.zig` walks the source tree, copies every file, and writes `asset-manifest.zon` with `id`, `bundle_path`, `source_path`, `byte_len`, SHA-256 `hash`, and optional `media_type`. A missing assets directory yields an empty manifest (not a hard failure).

## Build graph outputs

| Path | Producer | Contents |
| --- | --- | --- |
| `.native/build/build.zig` | `ensureGeneratedBuild` | Regenerated `native_sdk.addApp(...)` graph (`app_root = "../.."`) |
| `.native/build/build.zig.zon` | same | Relative `native_sdk` path dependency + package name |
| `zig-out/bin/<name>` | `native build` / `zig build` install | ReleaseFast app binary (desktop) |
| `zig-out/package/...` | `native package` | Platform artifact |
| `package-manifest.zon` | `writeReport` inside the artifact | `.artifact`, `.target`, `.version`, `.app_id`, `.executable`, `.optimize`, `.web_engine`, `.signing`, `.asset_count`, frontend/capabilities |
| `Contents/Resources/signing-plan.txt` | macOS only | Signing mode requested (written **before** codesign) |

Framework resolution for generated graphs (`NATIVE_SDK_PATH`, then CLI location → checkout / npm package / split `@native-sdk/cli` + platform binary) is the same path `native init|dev|build|test` use.

App modules default to **ReleaseFast** when no `-Doptimize` is set (`build/app.zig` `app_optimize`); tests stay Debug unless overridden. That split is why packaging stamps `ReleaseFast` by default even though `native dev` is a Debug loop.

## app.zon fields that drive packages

```zig
.{
    .id = "com.example.myapp",
    .name = "myapp",
    .display_name = "My App",
    .version = "1.0.0",
    .icons = .{"assets/icon.png"},
    .platforms = .{ "macos", "linux" },
    .file_associations = .{},
    .url_schemes = .{},
    .web_engine = "system",
    .cef = .{ .dir = "third_party/cef/macos", .auto_install = false },
    .frontend = .{ .dist = "dist", .entry = "index.html", .spa_fallback = true },
}
```

| Field | Packaging use |
| --- | --- |
| `id` | Bundle ID, desktop ID, log/state prefix; iOS normalizes `_` → `-` |
| `name` | Executable / artifact base name |
| `display_name` | Menu bar, Finder, desktop entry, DMG volume name |
| `version` | `CFBundleShortVersionString` / package report |
| `icons` | Single-source icon pipeline (see below) |
| `file_associations` / `url_schemes` | macOS plist types, Linux MIME/desktop, Windows registry script |
| `frontend` | Asset root becomes `frontend.dist`; macOS layout uses `Resources/<dist>` |
| `web_engine` / `cef` | System vs Chromium; Chromium packages CEF into the macOS bundle |

Validate before packaging:

```bash
native validate app.zon
native doctor --manifest app.zon --strict
```

## Assets layout expectations

Default non-frontend packaging:

1. Source tree: project-relative `assets/` (or `--assets`).
2. macOS host resolves relative runtime paths against `Contents/Resources`, so the packager mirrors the app-relative assets path into the bundle (default `Contents/Resources/assets/`). A path like `assets/music/track.mp3` names the same file in `native dev` and in the installed app.
3. Absolute or `..`-escaping `--assets` paths have no honest app-relative subpath and land flat under `Contents/Resources`.
4. Frontend apps: assets land under `Contents/Resources/<frontend.dist>` (and `resources/<dist>` on Linux/Windows) so `zero://app/` paths such as `/assets/app.js` resolve without `file://` URLs.
5. Everything under the assets directory ships; keep large optional data out of that tree if it should not be in the package.

iOS uses a project-root `Assets/` folder (not `Resources/`) so the generated Xcode project does not look like a deep macOS-style bundle during `xcodebuild archive`. Android mirrors assets to `assets/native-sdk/` for first-launch install onto the device.

## Platform artifacts

### macOS (`.app`)

Default output: `zig-out/package/<name>.app`.

:::files
myapp.app/
  Contents/
    MacOS/<name>
    Info.plist
    PkgInfo
    Frameworks/                    # Chromium only
      Chromium Embedded Framework.framework/
    Resources/
      AppIcon.icns                 # or prebuilt .icns basename
      assets/…                     # or <frontend.dist>/
      package-manifest.zon
      signing-plan.txt
:::

- `LSMinimumSystemVersion` is `11.0`.
- `CFBundleName` / `CFBundleDisplayName` use the display name; the executable name stays `metadata.name`.
- File associations → `CFBundleDocumentTypes`; URL schemes → `CFBundleURLTypes`.
- Chromium: `copyMacosCefRuntime` copies CEF into `Contents/Frameworks/…` after `cef.ensureLayout`.
- Signing: `--signing none|adhoc|identity` (identity needs `--identity`; optional `--entitlements`). Full codesign/notarization workflow is on [Code signing](/code-signing).

`--archive` runs `hdiutil create -format UDZO` → sibling  
`<name>-<version>-macos-<optimize>.dmg`.

### Linux (install tree)

Default output: `zig-out/package/<name>-linux/`.

| Path | Role |
| --- | --- |
| `bin/<name>` | Executable |
| `resources/…` | Bundled assets (or frontend dist) |
| `share/applications/<name>.desktop` | Desktop entry |
| `share/icons/hicolor/<size>x<size>/apps/app-icon.png` | Generated sizes |
| `share/mime/packages/<name>.xml` | When file associations exist |
| `package-manifest.zon` | Package report |

`--archive` → `<name>-<version>-linux-<optimize>.tar.gz` via `tar czf`.

Installer formats (AppImage, Flatpak) are not generated; the README in the tree marks that as future work.

### Windows (directory artifact)

Default output: `zig-out/package/<name>-windows/`.

| Path | Role |
| --- | --- |
| `bin/<name>.exe` | Executable |
| `resources/…` | Assets |
| `app-icon.ico` | Multi-size generated or prebuilt |
| `install/register-file-types.ps1` | Per-user `HKCU\Software\Classes` when associations/schemes exist |
| `package-manifest.zon` | Package report |

Early support: directory layout only; MSI/installer generation is not implemented.  
`--archive` → `.zip` via `zip -r`.

### iOS (experimental host project)

`native package --target ios` builds the device-slice embed library when `--binary` is omitted (`info[package.ios]: building the embed static library …`), then emits a complete toolkit-owned Xcode project:

- `<name>.xcodeproj` + shared scheme (headless `xcodebuild` ready)
- `Host/` — UIKit host sources + generated `Info.plist`
- `Libraries/libnative-sdk.a` — app as embed static library
- `Assets.xcassets` + `Assets/` — icons and bundled assets
- `package-manifest.zon`, `README.md`

Code signing stays manual (`DEVELOPMENT_TEAM` / Xcode), analogous to notarization. Simulator loop is `native dev --target ios`, not this package output.

### Android (experimental host project + debug APK)

`native package --target android` builds `aarch64-linux-android` embed lib when needed, emits:

- `Host/` activity + JNI bridge
- `AndroidManifest.xml`, `res/` launcher icons
- `assets/native-sdk/`
- `Libraries/libnative-sdk.a`
- `<name>-debug.apk` when SDK/NDK + JDK are present (aapt2, javac, d8, zipalign, apksigner; debug keystore under `~/.native/android/`)

Missing toolchain prints a notice and still emits the project without the APK. Store signing remains manual.

## Icons

One square source in `.icons` (typically `assets/icon.png` 1:1, ideally 1024×1024, or `assets/icon.svg`) drives every platform:

| Platform | Output |
| --- | --- |
| macOS | Generated `AppIcon.icns` (platform mask/margins) |
| Windows | Multi-size `app-icon.ico` |
| Linux | hicolor PNG set (`app-icon.png`) |
| iOS / Android | Asset catalog / mipmap from the same pipeline |

Precedence: prebuilt `.icns` (macOS) or `.ico` (Windows) in `.icons` ships **untouched** under its own basename / as `app-icon.ico`. Missing or unusable sources fall back to the SDK default icon embedded in the packager so packages are never text placeholders. `native validate` and packaging share the same square/decodable checks.

Regenerate the SDK default family (maintainers):

```bash
zig build generate-icon
```

## Frontend and Chromium

Frontend apps set `.frontend` in `app.zon`. Packaging defaults `--assets` to `frontend.dist`. Build the frontend before package when dist is not already present.

Chromium on macOS:

```bash
native cef install --version <pinned-version>
# app.zon: .web_engine = "chromium", .cef = .{ .dir = "third_party/cef/macos", … }
native build
native package --target macos
```

CLI/build `-Dweb-engine` / `--web-engine` and `-Dcef-dir` / `--cef-dir` are temporary overrides. Chromium packaging validates targets: **macos** (and experimental ios/android paths) accept Chromium; **windows/linux** return `UnsupportedWebEngine` for Chromium packages.

Layout pin:

```bash
zig build test-package-cef-layout -Dplatform=macos
```

## Archives and reports

With `--archive` (desktop targets only):

| Target | Tool | Suffix |
| --- | --- | --- |
| macOS | `hdiutil` UDZO | `.dmg` |
| Windows | `zip -r` | `.zip` |
| Linux | `tar czf` | `.tar.gz` |

Archive basename: `<name>-<version>-<target>-<optimize><suffix>` next to the package. Failures print a warning and leave the package directory intact.

Every successful package writes `package-manifest.zon` with the optimize label, engine, signing mode, and asset count for CI and human inspection.

## Failure modes

| Symptom | Likely cause |
| --- | --- |
| `error: app.zon not found` | Not in app root; pass `--manifest` |
| `warning[package.no-binary]` | Run `native build` first or pass `--binary` |
| Empty/missing executable in package | Same as above (desktop still creates skeleton layout) |
| `InvalidIconSource` | Non-square PNG / unsupported SVG |
| `UnsupportedWebEngine` | Chromium on windows/linux package target |
| CEF launch/package gaps | Version/layout mismatch; run `native doctor` + `test-package-cef-layout` |
| Android project without APK | No SDK/NDK/JDK or no embed library |
| iOS archive missing bundle id | Do not rename generated `Assets/` → `Resources/` |

## SDK CLI binaries (not app packages)

`packages/native-sdk/scripts/build-binaries.sh` cross-compiles the **`native` CLI** (`zig build cli -Doptimize=ReleaseSmall`) into npm platform packages and flat `zig-out/release/native-sdk-*` assets with `CHECKSUMS.txt`. That path distributes the toolchain, not end-user apps. See [Updates and package distribution](/updates-distribution).

## Related pages

<CardGroup>
  <Card title="Code signing" href="/code-signing">
    Codesign modes, framework copy order, and macOS signing constraints after `native package`.
  </Card>
  <Card title="Updates and package distribution" href="/updates-distribution">
    npm multi-platform layout, version sync, and `@native-sdk` binary distribution.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    `native build`, `native package`, flags, and failure modes.
  </Card>
  <Card title="Frontend projects" href="/frontend">
    Managed frontend dist output that packaging mirrors into the bundle.
  </Card>
  <Card title="Web engines" href="/web-engines">
    System vs Chromium, CEF install, and when the package grows a Frameworks tree.
  </Card>
  <Card title="Embedded app" href="/embed">
    Embed ABI and host libraries that iOS/Android package projects link.
  </Card>
</CardGroup>
