Skip to main content
Support

Browse by category

All categories
← All posts
ExplainerJun 16, 2026 · 7 min read

The WebAssembly Component Model, explained without the jargon

How WIT, the canonical ABI, and worlds let WebAssembly modules in different languages share strings and records instead of just numbers.

By Khine1,436 wordsExtractable lead
The WebAssembly Component Model, explained without the jargon — hero illustration

WebAssembly has a quiet limitation that surprises people the first time they hit it. Once you have compiled some Rust or C or Go down to a .wasm module, the boundary of that module speaks only in numbers. A function can take and return 32- or 64-bit integers and floats, and that is the whole vocabulary. There are no strings at the boundary, no lists, no structs. If you want to pass a string from one module to another, you pass two integers — a pointer into linear memory and a length — and trust that the code on the other side knows what to do with them.

For a single program that compiles to a single module, this is fine. The trouble starts when you want two modules, written in two languages, to call each other. That is the gap the Component Model is meant to close, and it is worth understanding what the gap actually is before looking at the fix.

What core WebAssembly does and doesn’t give you

Core WebAssembly — the 1.0 specification that browsers have shipped for years — defines a module: a unit of code with imports and exports, some functions, a chunk of linear memory, maybe a table. The Bytecode Alliance’s own documentation puts the constraint plainly: functions are “restricted, essentially, to using integer (i32 or i64) or floating-point (f32 or f64) types.”

So passing a string is not a single operation. You write the bytes into memory, hand over the offset and the length, and then read the result back out of memory afterward. Both sides have to agree on the encoding, on who allocates, on who frees. And here is the part that bites: data representations are usually specific to each language. A string in C is laid out differently from a string in Rust, which is different again from a string in JavaScript. There is no shared notion of “string” at the module boundary, so every cross-language pairing needs its own hand-written glue, and the glue is exactly the kind of code that is easy to get subtly wrong.

This is not a defect, really. Core Wasm was designed as a compact, fast, sandboxed instruction format. Keeping the type system minimal was a reasonable choice for that goal. It just means core Wasm alone is a poor lingua franca for composing software out of pieces written by different people in different languages.

What a component is

A component is the layer built on top of core modules to fix this. The mental split that helped me: a core module is a bag of functions that trade in numbers; a component is a core module (or several) wrapped in a typed description of what it imports and exports, where the types are the high-level ones you actually care about.

Two pieces make that work.

The first is a richer way of describing function signatures. Instead of “this takes two i32s,” a component’s interface can say “this takes a string and returns a list of records.” These descriptions are called interfaces, and the type language they are written in is WIT.

The second is the canonical ABI — the application binary interface. The interface says what the types are; the canonical ABI says how those high-level types are encoded down into the numbers and linear-memory operations that core Wasm can actually execute. It is the agreed-upon, language-neutral lowering. Because the lowering is specified once and shared, two components can interoperate without either one knowing what language the other was written in. The glue that you used to write by hand is now generated from a single rulebook.

WIT, in practice

WIT (the Wasm Interface Type language) is an interface definition language, and the docs are careful to say what it is not: “WIT isn’t a general-purpose programming language and doesn’t define behaviour; it defines only contracts between components.” You don’t write logic in it. You write the shape of the boundary.

The value types are the ones a working programmer reaches for. There are primitives, and then the compound types that matter:

  • records — a set of named fields, like a struct
  • variants — a value that is one of several named cases, like a tagged union or a Rust enum with payloads
  • listslist<T>, an ordered sequence of some type T
  • strings — actual Unicode strings, not pointer-length pairs
  • resultsresult<T, E>, holding either a success value T or an error E

That last one is quietly important. result<T, E> gives cross-language calls a typed way to say “this failed, and here is the typed reason,” instead of smuggling an error through a sentinel integer or a side channel. Errors stop being a convention and become part of the contract.

WIT groups types and functions into interfaces (a named set of types and functions), and then groups interfaces into a world. A world is the contract of a whole component: everything it imports and everything it exports, in one named bundle. If an interface is a single phone line, a world is the full description of every line running into and out of the building. When you build a component, you build it to a world. Tooling can then read the world and generate type-safe bindings in your language — so a Rust author and a Python author targeting the same world get bindings that line up, because they were both generated from the same description.

Where WASI fits

This is where the Component Model stops being abstract. WASI, the WebAssembly System Interface, is how a Wasm program reaches the outside world — files, clocks, sockets, randomness. WASI 0.2, also called Preview 2, is defined as a collection of WIT worlds and interfaces.

On 25 January 2024 the WASI Subgroup voted to launch WASI 0.2 and declared its APIs stable. The headline of that release, in the Bytecode Alliance’s own words, is that “WASI is now officially based on the Wasm component model, which makes it cross-language and virtualizable.” Two worlds shipped: wasi-cli, a command-line world that roughly corresponds to POSIX (files, sockets, clocks, random numbers), and wasi-http, an HTTP proxy world built around requests and responses.

“Virtualizable” is the word doing real work there. Because a host capability like the filesystem is just an interface a component imports, you can satisfy that import with a real filesystem — or with another component that pretends to be one. That makes sandboxing and testing a property of the type system rather than a bolt-on.

The honest status

Here is where the calm version has to be plain: this is not finished. The Component Model is, per its own specification repository, “being incrementally developed and stabilized as part of WASI Preview 2.” WASI 0.2 itself is stable and pinnable — you can target a release >= 0.2.0 and rely on it, and there have been point releases since — but the Component Model spec underneath is still moving through the W3C WebAssembly Community Group’s standardization process. It is not yet a finished, ratified standard the way core Wasm 1.0 is.

The next milestone, WASI 0.3 (Preview 3), is mostly concerned with adding native asynchronous I/O and concurrency to the model. As of this writing that work is in progress rather than shipped, and I will admit I am not entirely sure how the async story settles in day-to-day code yet — the design has shifted more than once, and I would rather say that than pretend the picture is fully clear. Browser support is its own separate question; core Wasm is everywhere, but the Component Model is not something you assume a browser runs natively today. The strong support right now lives in standalone runtimes and the toolchains around them.

None of that makes the idea less worth understanding. The Component Model is a real answer to a real problem — letting separately compiled, differently authored pieces of software share strings and records and typed errors instead of raw integers, with the encoding specified once and the glue generated rather than written. If you have ever hand-marshalled a string across a Wasm boundary, you already know why that matters. The remaining work is turning a promising, mostly-working design into a settled standard, and that part is genuinely still underway.

Sources

References

  1. Why the Component Model? — Bytecode Alliance (accessed 2026-05-29)
  2. WIT (the WebAssembly Interface Type language) — Bytecode Alliance (accessed 2026-05-29)
  3. component-model (design and specification repository) — WebAssembly Community Group (accessed 2026-05-29)
  4. WASI 0.2 Launched — Bytecode Alliance (accessed 2026-05-29)
  5. WASI — versions and previews — WebAssembly System Interface Subgroup (accessed 2026-05-29)