WASI Preview 1 vs Preview 2
This guide explains the practical differences between the two WASI snapshots you will meet in build commands and error messages, so you can pick a target deliberately rather than by copying whichever triple appeared in the first search result.
The headline is that preview 2 is not simply “preview 1 with more syscalls”. It changes the shape of the artifact: a preview 1 build is a core WebAssembly module, while a preview 2 build is a component, described by WIT interfaces and consumed by different tooling. Almost every confusing error people hit when switching traces back to that one distinction.
Prerequisites
- [ ] Rust 1.82+ if you want to try
wasm32-wasip2(rustup target add wasm32-wasip2) - [ ]
wasmtime20+, which runs both core modules and components - [ ]
wasm-toolsif you intend to inspect components (wasm-tools component wit) - [ ] An existing preview 1 build to compare against
What actually differs
The artifact
A preview 1 build is a core module: a single binary with a flat import list and a _start export. Any
tool that understands WebAssembly understands it. A preview 2 build is a component: a container format
that wraps one or more core modules plus a description of typed interfaces. wasm-objdump will refuse
it, because it is not a core module — and that refusal is the most common first symptom of an
accidental target switch.
The interface description
Preview 1’s interface is a fixed list of functions with integer parameters, documented but not
machine-described. Preview 2’s is written in WIT, a small interface definition language, and the
interfaces are versioned and grouped into worlds such as wasi:cli or wasi:http. That makes
bindings generatable rather than hand-written, and it is what enables composition — one component
importing an interface another exports.
The capability surface
Preview 1 covers files, clocks, random, environment, arguments and standard streams. It has no sockets. Preview 2 adds networking and a typed HTTP interface, and expresses resources — open files, sockets — as first-class typed handles rather than integer descriptors, which removes a class of confusion about which integers are valid where.
Runtime and ecosystem support
Preview 1 runs essentially everywhere WASI is supported. Preview 2 support is good in the runtimes that lead the standard and patchy elsewhere, and library support lags further. This is the deciding factor for most projects today: the newer target is better designed and less widely accepted.
Migrating a build
-
Add the target and build:
rustup target add wasm32-wasip2 cargo build --release --target wasm32-wasip2 -
Notice that the artifact is different:
wasm-objdump -x target/wasm32-wasip2/release/tool.wasm # error: not a valid Wasm module — it is a component wasm-tools component wit target/wasm32-wasip2/release/tool.wasm | head -
Run it under a runtime that understands components:
wasmtime run target/wasm32-wasip2/release/tool.wasm -- --input /data/sample.bin -
Expect the source to be unchanged. For a program using files, arguments and standard output, the Rust code compiles as-is; the standard library targets the new interfaces internally.
-
Check every consumer. Anything that loaded the old artifact — a Node script, a CI step, an embedding — needs to understand components or it will reject the file.
Gotchas
not a valid Wasm module from a tool that worked yesterday. You built a component. Either target
wasip1 or use wasm-tools and component-aware runtimes.
A dependency fails to build for wasip2. Crate support is thinner for the newer target,
particularly anything with C dependencies. This is the usual blocker in practice, and it is worth
checking before planning a migration.
A component will not instantiate in an older wasmtime. Component support and the interfaces
themselves are versioned; a runtime a few releases behind may know components and not the specific
world your build targets.
You expected sockets and got a compile error. Networking is a preview 2 interface. On preview 1 there is no socket API at all, and runtime-specific extensions are not portable.
Both targets are installed and you cannot tell which artifact is which. Put the triple in the output filename in your build script. The two files are otherwise indistinguishable at a glance and behave very differently.
Performance note
There is no meaningful execution-speed difference between the two snapshots: the same instructions run through the same compiler, and the interface layer sits at the boundary rather than in your loops. Where preview 2 can cost slightly more is at the edge, because the canonical ABI it uses to pass strings, lists and records does real work per call — the same lifting and lowering that generated glue does in a browser.
That cost is proportional to the data crossing the interface, so the guidance is the familiar one: coarse-grained calls with buffers, not fine-grained calls with records. Where a component’s interface turns out to be chatty, the fix is interface design rather than a snapshot change, and it is easier to apply before other components depend on the shape.
A migration that does not strand you
If you decide to move, the low-risk order is to change the artifact last rather than first. Start by
removing anything that depends on preview 1’s specifics: raw file descriptors handled as integers,
assumptions about the wasi_snapshot_preview1 namespace appearing in the import list, and any tooling
step that inspects the binary with a core-module tool. Each of those is a small change and none of
them require the new target.
Then add a second build alongside the existing one rather than replacing it:
cargo build --release --target wasm32-wasip1 # keeps shipping
cargo build --release --target wasm32-wasip2 # runs in CI, not yet shipped
Running both in CI surfaces the differences immediately — a dependency that will not compile, a behaviour that changed, a test that assumed the old artifact shape — while the shipped build is untouched. Once the preview 2 artifact passes the same suite, switching becomes a deployment decision rather than a development project.
The one thing worth checking early is dependency support, because it is the most common hard blocker and the one you cannot work around locally. A crate with C dependencies, or one that reaches for sockets, may simply not build for the newer target yet. Finding that out in an afternoon at the start is much better than discovering it after restructuring the code around interfaces.
What “component” changes about your mental model
Beyond tooling, there is a conceptual shift worth internalising. A core module is a bag of functions over numbers with a memory attached; the meaning of those numbers is a convention between you and the host. A component is a described interface with typed values, and the numbers are an implementation detail the ABI handles.
That changes where bugs live. With preview 1 the recurring failures are ABI mistakes — a length in the wrong units, a pointer used after a free, a struct layout the two sides disagree on. With preview 2 those largely disappear, replaced by a different set: version mismatches between interface packages, tooling that does not understand the artifact, and performance surprises from copies the ABI performs on your behalf.
Neither set is obviously worse than the other. The preview 2 failures tend to be loud and structural — a build error, a compose failure — while preview 1’s tend to be quiet and data-dependent. For a team that already has the ABI under control, that trade may not be compelling; for one building a plugin interface several people will implement, it clearly is.
Frequently Asked Questions
Which should I use today? Preview 1 unless you need networking, want component composition, or control every runtime that will load the artifact. It is the target with the fewest surprises and the broadest support, and moving to preview 2 later is a build-configuration change rather than a rewrite.
Is preview 1 going away? Not abruptly — the installed base is large and runtimes will keep supporting it for a long time. Treat preview 2 as where the standard is heading and preview 1 as where deployment currently is.
Can one codebase target both? Usually yes, if the code stays inside the capabilities both snapshots share. Keep networking and any component-specific interfaces behind a feature flag, and build both in CI so a change that breaks one target is caught immediately — the cross-platform build automation pattern extends to this cleanly.
Related
- WASI target builds & runtimes — the shared model behind both snapshots.
- Compiling Rust to wasm32-wasip1 — the target this page compares against.
- Wasm component model & WIT bindings — what a component actually is, from the interop side.
- Understanding the canonical ABI — how typed values cross a component boundary.
← Back to WASI Target Builds & Runtimes