WebAssembly Beyond the Browser with WASI
WebAssembly earned its reputation in the browser as a way to run C++ or Rust at near-native speed on a web page. That framing undersells where it's actually heading. The more interesting story is WebAssembly beyond the browser: a portable, sandboxed, microsecond-start runtime for server-side plugins, edge functions, and polyglot systems. The two pieces that unlock that are WASI, which gives Wasm controlled access to the outside world, and the component model, which lets modules from different languages compose safely.
I got interested in this from the plugin angle — wanting to run untrusted user-supplied logic inside a system without handing it the keys to the host. Wasm plus WASI is one of the few technologies that makes that both safe and fast. Here's the practical shape of it.
The properties that make Wasm compelling off the web
Strip away the hype and Wasm-outside-the-browser rests on four concrete properties:
- Sandboxed by default. A Wasm module has no ambient access to the filesystem, network, or environment. It can only touch what the host explicitly grants. That's the opposite of a normal process, and it's the whole reason Wasm is a good plugin host.
- Portable. One compiled
.wasmruns unchanged on x86, ARM, Linux, macOS, Windows — the runtime abstracts the machine. Compile once, run anywhere the runtime exists. - Fast startup. No container to boot, no VM to warm. A module instantiates in microseconds, which matters enormously for per-request or per-invocation workloads.
- Language-agnostic. Rust, Go, C, C++, and increasingly Python, JavaScript, and others compile to Wasm, so you can run a polyglot mix in one runtime.
Those combine into a niche nothing else fills cleanly: run arbitrary, possibly untrusted, code from any language, safely, with negligible startup cost.
WASI: giving Wasm the outside world, carefully
Pure Wasm can compute but can't do I/O — it can't read a file or open a socket, because the spec deliberately gives it no system access. WASI fills that gap with a standardized, capability-based interface. The key word is capability: the host doesn't grant "filesystem access," it grants "a handle to this specific directory." The module can't reach anything it wasn't explicitly handed.
That's a fundamentally better security model than the usual "the process runs as this user and can touch everything that user can." With WASI, least privilege is the default and access is enumerated. Running a .wasm module with Wasmtime and a scoped directory looks like:
# Grant the module access to ONLY ./data, nothing else on the filesystem
wasmtime run --dir=./data ./plugin.wasm
The evolution here matters: WASI Preview 1 was the early, file-and-clock-focused version; WASI 0.2 (Preview 2) is built on the component model and adds a properly typed, modular interface story including networking. If you're starting now, target the component-model-based world rather than the older Preview 1 shape.
The component model: polyglot without the footguns
Plain Wasm modules communicate through raw shared linear memory and integer function signatures — passing a string means agreeing on pointers and lengths by hand. That's fine for one language talking to itself and miserable across languages. The component model fixes this with typed interfaces defined in WIT (WebAssembly Interface Types):
// greet.wit — a language-agnostic interface contract
package example:greeter;
interface greet {
hello: func(name: string) -> string;
}
world greeter {
export greet;
}
A component that exports this interface can be written in Rust and consumed by a host written in Go, with strings, records, lists, and options marshaled correctly across the boundary — no shared-pointer arithmetic, no memory-safety hazards between components. This is what turns "Wasm can run many languages" into "Wasm can compose many languages," which is the genuinely new capability.
Where this is actually being used
This isn't speculative; it's shipping in a few concrete places:
| Use case | Why Wasm fits |
|---|---|
| Plugin systems | Run untrusted third-party extensions safely, any language |
| Edge functions | Microsecond cold starts, tiny footprint near the user |
| Serverless / FaaS | High density, fast start, strong isolation per tenant |
| Data pipeline UDFs | User-defined transforms sandboxed inside a query engine |
| Config / policy engines | Sandboxed rule evaluation without a full VM |
The edge case reinforces what I wrote about in running code at the edge: Wasm's near-instant instantiation and small footprint make it a natural fit for the every-request, near-user runtime, and it lets you push non-JavaScript workloads there. The plugin case is where I've found it most compelling — you can let customers ship logic into your platform without granting them anything you didn't intend.
The honest limitations
It's still maturing, and pretending otherwise wastes people's time:
- Language support is uneven. Rust and C/C++ have first-class stories. Go works but has caveats. Dynamic languages like Python run but often by bundling an interpreter compiled to Wasm, which is heavier.
- The ecosystem is young. Tooling, debugging, and libraries around WASI 0.2 and the component model are improving fast but aren't as smooth as native toolchains yet.
- Not always faster. Wasm is near-native, not native. For raw compute-bound work a native binary can still win; Wasm's advantage is isolation, portability, and startup, not always peak throughput.
- I/O-heavy workloads feel the cost of the WASI boundary more than compute-heavy ones.
Pick Wasm for what it's uniquely good at — safe, portable, fast-starting, polyglot execution — not as a blanket replacement for native services.
Getting started
The pragmatic entry point is Wasmtime (the reference runtime) plus a Rust or C toolchain targeting wasm32-wasip2. Build a tiny component, define its interface in WIT, and run it with a scoped set of capabilities. Once you've felt the "this module literally cannot touch anything I didn't grant" property, the plugin and multi-tenant use cases become obvious. It pairs naturally with the broader Rust web toolchain work happening across the ecosystem, since so much of the Wasm tooling is itself written in Rust.
WebAssembly beyond the browser isn't going to replace your services. It's carving out a specific, valuable role: the safe sandbox for other people's code, the portable unit that runs anywhere, the thing that starts fast enough to run per request. WASI and the component model are what make that role real, and they're worth understanding now while the ecosystem settles.
Resources
- WebAssembly official site
- WASI — the WebAssembly System Interface
- Wasmtime documentation
- The Component Model book
- Bytecode Alliance
- MDN: WebAssembly
Frequently asked questions
What is WASI in WebAssembly?
WASI (the WebAssembly System Interface) is a standardized set of APIs that lets WebAssembly modules access system resources — files, clocks, random, networking — outside the browser. It's what makes Wasm usable server-side, giving modules a portable, capability-based way to talk to the host OS.
Why run WebAssembly outside the browser?
For sandboxed, portable, fast-starting code. A Wasm module is isolated by default, runs on any CPU or OS without recompilation, and starts in microseconds. That makes it ideal for untrusted plugins, edge functions, and polyglot systems where you want to run code from multiple languages in one secure runtime.
What is the WebAssembly component model?
The component model is a standard for composing Wasm modules with typed, language-agnostic interfaces defined in WIT (WebAssembly Interface Types). It lets a component written in Rust call one written in Go or Python through a shared interface, without shared memory hazards, enabling true polyglot composition.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →