Skip to content

Quick Start

From zero to a live OTA patch. Machine setup is two commands you run once. Per-app setup is one commandpatchcli init registers the app, adds the SDK, and wires the startup code for you. Then you write normal Swift and release.

1 · Install the CLI + toolchain (once per machine)

Section titled “1 · Install the CLI + toolchain (once per machine)”

Patch compiles Swift to WebAssembly with the swift.org toolchain plus the WebAssembly Swift SDK. The Apple/Xcode toolchain cannot target WebAssembly.

install — zsh
# 1 · the Patch CLI
$ brew install patch-release/tap/patchcli
# 2 · the Swift→WebAssembly toolchain — ONE command installs + checksum-verifies
# the swift.org toolchain + WebAssembly SDK (Xcode's can't target wasm)
$ patchcli setup
(downloads the pinned swift.org toolchain + WASM SDK from swift.org)

From your app’s directory, run patchcli init. One command does the whole setup, skipping anything already done (it’s safe to re-run):

  1. Detects the Xcode project, build target, bundle id, and app icon.
  2. Registers the app — your browser opens app.patchrelease.com/cli-connect; sign in (or create a free account) and click Register app. The CLI receives the app key and a publish token automatically within seconds and writes app_key/publish_token/app_id/workspace_id into .Patch.yml — no copying credentials.
  3. Adds the PatchSDK Swift package to the Xcode project (exactly what Xcode’s “Add Package Dependencies…” would write; a backup is kept at project.pbxproj.patch-backup), then resolves packages.
  4. Proposes a diff inserting the Patch startup code into your @main App struct — applied only after you confirm (y/n).
  5. Makes your SwiftUI views patchable — marks each view body patchable and generates the dynamic-replacement thunks (the same mechanism Xcode Previews uses), so a future OTA patch re-renders your views with no PatchView wrapping and no changes to your views. This runs automatically on every patchcli release / push / build, so views you add later are picked up with no extra step. (You can still run patchcli prepare --check in CI to verify, or --no-prepare to opt out.)
~/MyApp — zsh
$ patchcli init
Patch init
==========
Detected Xcode project: MyApp.xcodeproj (target MyApp · com.example.myapp)
Opening browser to register the app… app key + publish token received
Wrote: /Users/you/MyApp/.Patch.yml
Adding PatchSDK package… resolved (backup: project.pbxproj.patch-backup)
Proposed change to MyApp.swift apply? (y/n) y ✓ startup code inserted
MyApp is set up build & run, then ship with: patchcli release

The startup code it inserts (the same integration you’d write by hand — configure once, start() activates the best cached module immediately, offline-safe, then checks for an update in the background). init bakes in your appID and the current native-shell fingerprint; with only the appKey the SDK (≥ 1.0.3) still works — the backend resolves the key, and updates are served best-effort until a fingerprint is reported:

MyApp.swift
import PatchSDK
@main
struct MyApp: App {
init() {
Patch.configure(.init(
appKey: "pak_live_…", // your key, from .Patch.yml
appID: "3f2b…-uuid", // baked in by patchcli init
fingerprint: "3f2b…")) // this build's native shell
Task { await Patch.shared.start() }
}
var body: some Scene {
WindowGroup { ContentView() }
}
}

Anything patchcli init can’t do safely — no bundle id detected, an unusual project layout, a UIKit AppDelegate app, a declined confirmation, or no network — falls back to printing exact manual steps. See Manual setup for the same steps, and the CLI reference for flags like --manual, --no-open, and --yes.

No annotations needed — just write pure logic. Patch detects that this function is WASM-safe (it only touches Decimal and value types) and lifts it into the module. The call site stays exactly the same.

Pricing.swift
import Foundation
enum Pricing {
// Pure logic → compiled to WASM, updatable over the air.
static func orderTotal(items: [LineItem], promo: String?) -> Decimal {
var subtotal = items.reduce(Decimal(0)) { $0 + $1.price * Decimal($1.qty) }
if promo == "LAUNCH20" { subtotal *= 0.80 }
var total = Decimal()
NSDecimalRound(&total, &subtotal, 2, .bankers)
return total
}
}

4 · Register your App Store build’s fingerprint

Section titled “4 · Register your App Store build’s fingerprint”

After your next App Store build ships, run this once. Pushes are gated on the native-shell fingerprint, so the backend only ever serves modules compatible with installed apps. Re-run it after each App Store release to re-baseline.

~/MyApp — zsh
$ patchcli fingerprint register
Registered native-shell fingerprint 3f2b… for MyApp

patchcli release builds the module and pushes it to the backend in one command (build → fingerprint gate → upload → activate). Edit Pricing.orderTotal, then release the fix to 10% of devices to start.

~/MyApp — zsh
$ patchcli release --rollout 10 --message "Fix LAUNCH20 promo math"
building… .Patch/build/module.wasm (38.2 KB)
Fingerprint: compatible
Pushed module 2026.06.03.142210
Rollout: 10%
Active: true

Re-launch the app: the SDK fetches, verifies, and hot-swaps the new module. Watch adoption with patchcli status, widen the rollout, or patchcli rollback if anything looks wrong. That’s the whole loop.

Everything patchcli init automates can be done by hand — and init prints these exact steps whenever it can’t do something safely (or when you run patchcli init --manual):

  1. Add the SDK package — in Xcode, File → Add Package Dependencies… with https://github.com/patch-release/patch-swift (product PatchSDK, from 1.0.0). See SDK reference for the Package.swift form.
  2. Create the app in the dashboard to get its pak_… app key.
  3. Paste the key — set app_key (and optionally app_id/workspace_id) in .Patch.yml.
  4. Get a publish token — run patchcli login, or create one in the dashboard and set publish_token in .Patch.yml. Without it, push/release cannot authenticate.
  5. Add the startup code — the import PatchSDK + Patch.configure / Patch.shared.start() snippet from step 2 above, in your @main App struct’s init().