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.

Different snapshots, different artifacts A preview 1 build is a core module importing a flat namespace of POSIX-shaped syscalls. A preview 2 build is a component whose imports and exports are typed interfaces described in WIT, which core-module tooling cannot read. wasm32-wasip1 a core module imports "wasi_snapshot_preview1" flat list of syscall-shaped functions every runtime and tool understands it no networking, no typed resources wasm32-wasip2 a component imports typed interfaces described in WIT versioned worlds: cli, http, sockets newer tooling required end to end composable with other components Everything else — the instruction set, linear memory, the optimiser — is identical. The change is at the interface, not in the code.

Prerequisites

  • [ ] Rust 1.82+ if you want to try wasm32-wasip2 (rustup target add wasm32-wasip2)
  • [ ] wasmtime 20+, which runs both core modules and components
  • [ ] wasm-tools if 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.

The trade you are making Preview 1 has the widest runtime and tooling support with a narrower, untyped capability surface. Preview 2 has typed, versioned and composable interfaces including networking, with support concentrated in newer runtimes. runtime + tooling support p1 p2 — concentrated in newer runtimes interface capability p1 — files, clocks, streams; no sockets p2 Choose on the top row if the module has to run in someone else's environment; choose on the bottom row if you control the runtime. The gap narrows every release, so revisit the decision rather than treating it as settled.

Migrating a build

  1. Add the target and build:

    rustup target add wasm32-wasip2
    cargo build --release --target wasm32-wasip2
  2. 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
  3. Run it under a runtime that understands components:

    wasmtime run target/wasm32-wasip2/release/tool.wasm -- --input /data/sample.bin
  4. 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.

  5. 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.

Tooling splits along the artifact boundary Core-module tools such as wasm-objdump and wasm2wat read a preview 1 build and refuse a component. Component tools read the container and its interfaces. Runtimes increasingly accept both, which is why the error usually surfaces in a tool rather than at run time. core-module tools wasm-objdump · wasm2wat wasm-opt · twiggy preview 1 only component tools wasm-tools component jco · cargo-component preview 2 only runtimes wasmtime · wasmer recent versions read both so the error appears in a tool first Switching targets therefore breaks your inspection and optimisation steps before it breaks anything that runs.

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.

← Back to WASI Target Builds & Runtimes