Short answer: Yes — Swift compiles to WebAssembly officially. swift.org publishes WASM Swift SDKs you install with swiftly and build against using swift build --swift-sdk; Xcode's bundled toolchain cannot do it. A stdlib-only module lands near 5 MB, import Foundation takes it to roughly 57 MB, and Embedded Swift gets the same logic into kilobytes.

We compile other people's Swift to WASM for a living — it's how Patch's pipeline ships OTA updates to native iOS apps — which means we've hit most of the walls in this territory at least twice. Here's the map.

Can Swift compile to WebAssembly, and how do you set up the toolchain?

The first wall: Xcode's Swift cannot target WebAssembly. Apple builds its LLVM without the WebAssembly backend, so swiftc -target wasm32-unknown-wasip1 from an Xcode toolchain just errors out. You need three things (the official getting-started guide covers all of them):

  1. The swift.org toolchain, via swiftly: swiftly install 6.3.2

  2. The WASM Swift SDK matching it: swift sdk install <swift.org artifactbundle URL> --checksum <sha256>

  3. A build against it: swift build --swift-sdk swift-6.3.2-RELEASE_wasm

(If you used the community SwiftWasm toolchain in years past: as of Swift 6 the official swift.org SDKs replace it for most uses.)

Now you have two Swift toolchains on one machine, and which one swift resolves to is decided by your PATH. This bit us with a genuinely evil failure mode: our build pipeline silently selected the Xcode toolchain (because /usr/bin came first), and instead of an error we got "0 modules emitted" — everything "worked," nothing was produced. The fix is boring and absolute:

bash
# compiling to WASM — swiftly first:
export PATH="$HOME/.swiftly/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"; hash -r
# building/testing for the host — Xcode toolchain first:
export PATH="/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin"; hash -r

The hash -r matters; your shell caches the resolved path of swift. (PATH-order collisions bit us twice — we also had to rename our CLI over one.)

Target-wise you're compiling for WASI Preview 1 (wasm32-unknown-wasip1). One concept worth knowing early: a command module has a main, runs, and exits; a reactor is a passive library the host calls into. If you're embedding Swift-in-WASM inside another app (our case: an OTA module the SDK invokes), you want a reactor — built by passing -mexec-model=reactor to the linker (through SwiftPM: -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor).

And the good news, before the walls: it's really Swift. Generics, protocols, closures, value semantics, the whole standard library, Foundation via the corelibs port — the real compiler, compiling your real code. Even async/await compiles fine. Making it actually run inside a host with no event loop is a separate adventure.

How big is a Swift WebAssembly binary?

WASM binaries statically link everything — the Swift runtime, the stdlib, whatever corelibs you import. Our measurements with Swift 6.3, release builds:

What you compileModule size
Pure-logic module, stdlib only~5.3 MB
Same module + import Foundation~57 MB
Same logic, Embedded Swift~11 KB

The 57 MB isn't a bug: Foundation drags in ICU, and ICU means the Unicode Consortium's collation and normalization tables, statically, in every module. wasm-opt and stripping help at the margins; the order of magnitude stands.

The lever that changes the game is Embedded Swift (-enable-experimental-feature Embedded, whole-module, with the embedded WASM SDK variant): a subset of Swift designed to compile without the runtime's dynamic machinery. Same pure logic, ~11 KB — about 480× smaller than the stdlib build.

One real measurement from our pipeline, with the honest caveat that it's our best case (a pure-logic, embedded-clean closure), like-for-like at each step:

full-SDK module: 60.1 MB raw → Embedded Swift + wasm-opt: 23.9 KB raw (~2,500×) → brotli, on the wire: 8.8 KB

That last number is the difference between an OTA update costing megabytes or kilobytes on a user's cell connection. Code that genuinely needs Foundation doesn't get this: it ships at the tens-of-MB tier, which is why our Foundation-dependent modes are opt-in rather than default.

What do you give up with Embedded Swift?

Embedded Swift is a subset. Most notably you lose:

  • All of Foundation — no JSONDecoder, no Date/DateFormatter/Locale, no URL, no UUID, no Decimal, no NSRegularExpression.

  • Codable — it's standard library, not Foundation, but Embedded excludes it independently: it depends on runtime reflection metadata the subset removes.

  • Reflection (Mirror), unrestricted existentials, metatype dynamic casts; generics must be fully specializable at compile time.

And then there's the trap that looks like a toolchain bug and isn't.

Fixing wasm-ld: error: undefined symbol: _swift_stdlib_getNormData

Build an Embedded Swift WASM module with perfectly ordinary string code and you can hit this at link time:

code
wasm-ld: error: undefined symbol: _swift_stdlib_getNormData
wasm-ld: error: undefined symbol: _swift_stdlib_getComposition
wasm-ld: error: undefined symbol: _swift_stdlib_nfd_decompositions

Nothing is wrong with your SDK. Swift's String is grapheme-cluster-correct, and correctness needs data: Unicode normalization tables the embedded stdlib doesn't link by default. Innocuous-looking operations reach for them:

You wroteIt needs
"value: \(x)" (interpolation)Unicode normalization tables
if s == "ok" / switch on a Stringsame
s.prefix(5), grapheme/Character opssame, plus extended-pictographic tables
Double("3.14")_swift_stdlib_strtod_clocale

The documented fix is to link the tables back in — libswiftUnicodeDataTables.a from the toolchain's resource directory — at a few hundred KB of size cost. If you're chasing kilobyte-class modules, the cheaper route is to not need the tables at all:

Instead ofUse
interpolation+ concatenation, String(Int) / String(Double) — all link-clean
String ==compare Array(s.utf8) byte-wise *
.prefix(n)truncate UTF-8 bytes, backing off continuation bytes (0x80...0xBF) *
Double(String)a ~20-line hand-rolled decimal scan

* One honest caveat: byte-wise equality is not Unicode canonical equivalence — precomposed and decomposed "é" compare unequal. It's valid when you control both sides of the comparison (ASCII literals, known-normalized data), which in a wire-protocol context you usually do. That's a semantics tradeoff you're choosing, not a free lunch.

Also safe, for the record: String(decoding: bytes, as: UTF8.self) and iterating s.utf8 or s.unicodeScalars. The pattern underneath: anything that interprets Unicode (normalizing, grapheme-breaking, locale-aware parsing) needs the tables; anything that treats strings as UTF-8 bytes doesn't.

In our pipeline we don't ask developers to write this dialect — the engine detects code that isn't embedded-clean and automatically falls back to the full SDK for it. But if you're hand-writing Embedded Swift for WASM, the table above is a few days of head-scratching, prepaid.

How do you run a Swift WASM module, on a Mac and on iOS?

On a dev machine, wasmtime runs WASI modules directly and is the fastest feedback loop. For embedding inside a Swift app there's WasmKit — a WebAssembly runtime written in pure Swift (it also ships as the wasmkit CLI inside swift.org toolchains since Swift 6.2; the embedding side of Patch's SDK is public at github.com/patch-release/patch-swift). Two properties make it more than a curiosity: it's an interpreter, no JIT — meaning it runs on iOS, where third-party apps get no runtime code generation — and it speaks WASI, so Foundation-linked modules work (in our builds, a Foundation module imports ~34 wasi_snapshot_preview1 functions; even minimal ones want around 14, all provided by WasmKitWASI).

That interpreter-on-iOS property is what makes "compiled Swift, downloaded and executed on an iPhone, with zero runtime code generation" a sentence that parses. Whether and how you may ship code that way is a separate, policy question — we wrote up Apple's actual rules here; the compliance is yours to own either way.

Is Swift on WebAssembly worth using in 2026?

If you're evaluating Swift-on-WASM in 2026: it's real, it's officially supported, the language coverage is excellent — and the engineering lives in the size/capability tradeoff. Full-SDK builds give you all of Swift at tens of megabytes; Embedded gives you kilobytes at the cost of Foundation and some string ergonomics. Our answer is to compile each piece of code for the smallest tier it fits and fall back automatically when it doesn't fit — yours might be simpler. Either way, now you know where the walls are.

Frequently asked questions

Can Swift compile to WebAssembly?

Yes, officially. swift.org publishes WebAssembly Swift SDKs: install a swift.org toolchain with swiftly, install the matching WASM SDK, then build with swift build --swift-sdk. Xcode's bundled Swift cannot target WebAssembly, because Apple builds its LLVM without the WebAssembly backend. The target is WASI Preview 1 (wasm32-unknown-wasip1), and embedded hosts usually want a reactor module rather than a command.

Does Swift WebAssembly run on iOS?

Yes, through an interpreter. Third-party iOS apps get no JIT, so a WebAssembly runtime on iOS must interpret. WasmKit is a WebAssembly runtime written in pure Swift that does exactly that and embeds via SwiftPM, which is how Patch executes compiled-Swift WebAssembly modules inside a signed iOS app. Whether you may ship code that way is a separate policy question.

How big is a Swift WASM module?

It depends on what you link. Measured with Swift 6.3 release builds: a pure-logic, stdlib-only module is about 5.3 MB, the same module with import Foundation is about 57 MB, and the same logic built as Embedded Swift is about 11 KB. One best-case module in Patch's pipeline went from 60.1 MB to 23.9 KB after Embedded Swift plus wasm-opt, and 8.8 KB brotli-compressed on the wire.


Patch compiles changed Swift to WebAssembly and ships it OTA to native iOS apps — patchrelease.com, docs.