skip to content

What are the wasmJs() and wasmWasi() targets in a Kotlin Multiplatform project, and how do they differ?

level: juniorimportance: must knowfreq 55%

answer

  1. Two Wasm targets: wasmJs + wasmWasi
  2. wasmJs = browser/Node + JS interop
  3. wasmWasi = standalone runtime, WASI, no JS
  4. Both need WasmGC + exception-handling proposals
  5. @ExperimentalWasmDsl on the Gradle DSL

basics

~10 s

They are two Kotlin Multiplatform targets that compile code to WebAssembly. wasmJs() runs in the browser and can talk to JavaScript; wasmWasi() runs in standalone runtimes outside the browser using the WASI system interface.

solid answer

~40 s

Both are Kotlin/Wasm targets declared in the `kotlin { }` block of a Gradle build, producing WebAssembly modules instead of JVM bytecode or native binaries. `wasmJs()` targets WebAssembly running in a JavaScript host (browsers, Node.js) and supports interop with JS via `external` declarations and the `js("...")` function. `wasmWasi()` targets standalone Wasm runtimes (Wasmtime, Node with WASI, etc.) using the WASI (WebAssembly System Interface) for OS-like calls (clock, stdio); it has no JS interop. Both require the experimental GC and exception-handling Wasm proposals, so they need a recent runtime. You configure browser/nodejs execution under `wasmJs { browser(); nodejs() }`. Picking the target depends on the host: JS environment vs server-side/CLI Wasm.

code

kotlin · 7 lines
kotlin
kotlin {
    @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
    wasmJs { browser(); nodejs() }

    @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
    wasmWasi { nodejs() }
}

go deeper

for a junior

Knows there are two Wasm targets and that one is for browsers and one is standalone.

for a middle

Can declare both in Gradle, explain WASI vs JS host, and knows JS interop is wasmJs-only.

for a senior

Discusses the WasmGC/exception-handling proposal requirements, runtime support, and library-availability differences when choosing a target.

for a principal

Weighs wasmJs vs wasmWasi vs JS/Native for a product, factoring deployment runtimes, library ecosystem maturity, and experimental-status risk.

## What Kotlin/Wasm is **Kotlin/Wasm** is a compilation backend that turns Kotlin source into **WebAssembly (Wasm)** — a compact, portable binary instruction format that many runtimes can execute. It is distinct from Kotlin/JVM (bytecode), Kotlin/Native (machine code via LLVM), and Kotlin/JS (JavaScript). Kotlin/Wasm relies on two modern Wasm **proposals**: - **WasmGC** (garbage collection): lets the Wasm module use the host runtime's garbage collector for heap objects, so Kotlin doesn't ship its own GC. - **Exception handling**: native Wasm support for throwing/catching, used for Kotlin exceptions. Because of these, you need a recent runtime (modern Chrome/Firefox, current Node, Wasmtime). ## The two targets In a Multiplatform build's `kotlin { }` block you declare a target: ```kotlin kotlin { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) wasmJs { browser() // run in a browser nodejs() // run under Node.js } @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) wasmWasi { nodejs() } } ``` - **`wasmJs()`** — Wasm meant to run **inside a JavaScript host**. The Wasm module is loaded by a small JS glue layer, and Kotlin can call into JS (DOM, `fetch`, libraries) through `external` declarations and the `js("...")` intrinsic. This is the target you use for **web frontends** (e.g. Compose Multiplatform for Web). - **`wasmWasi()`** — Wasm meant to run in a **standalone runtime** with no JavaScript. It uses **WASI** (WebAssembly System Interface), a standardized set of host functions for system-like capabilities: standard output, clock, environment, etc. Use it for **server-side / CLI** Wasm where there is no browser or JS engine. ## Key differences - **Host**: `wasmJs` = JS environment; `wasmWasi` = JS-free runtime. - **Interop**: `wasmJs` has JS interop; `wasmWasi` has none (you only get WASI host calls). - **Libraries**: many KMP libraries support `wasmJs` but not `wasmWasi` (or vice versa) — availability differs. Both share the same Kotlin/Wasm code generator and both currently require the experimental Wasm proposals and the `@ExperimentalWasmDsl` opt-in on the Gradle DSL.

  • Why might a library support wasmJs but not wasmWasi?
    Because wasmWasi has no JS interop and only WASI host calls; libraries that depend on the DOM, fetch, or JS APIs simply can't run there, so authors often publish only the wasmJs artifact.
  • Can the same Kotlin code be shared by both targets?
    Common code can be, but anything using JS interop must live in the wasmJs source set; wasmWasi code can only use WASI-backed capabilities, so platform-specific code differs.

wasmJs is Wasm with a JavaScript co-pilot; wasmWasi is Wasm flying solo with only a small set of system instruments (WASI).

saying these in an interview costs you the question

  • Thinking wasmJs compiles to JavaScript (it compiles to Wasm; JS is only the glue/host)
  • Claiming wasmWasi can call DOM or fetch APIs
  • Saying Kotlin/Wasm ships its own GC inside the module
  • Confusing wasmJs with the separate Kotlin/JS (IR) target

context