wasm-bindgen: JS Interop & vs wit-bindgen Guide 2026

Max WellsMax WellsFounder of Rustify

TL;DR: wasm-bindgen is a crate and CLI tool that generates the glue code needed for Rust WebAssembly modules to communicate with JavaScript. It lets you export Rust functions and structs to JS, import JS functions and browser APIs into Rust, and pass complex types (strings, objects, closures) across the Wasm boundary; which normally only supports integers and floats. Use it with wasm-pack to build and publish WASM packages.


What Does wasm-bindgen Do?

It generates JavaScript bindings so Rust WASM functions can accept and return strings, objects, and closures; not just numbers. As of 2026, wasm-bindgen 0.2.x remains the standard, with ongoing work toward the Component Model (wit-bindgen) for the next generation of Wasm interop.

WebAssembly's native ABI only supports i32, i64, f32, f64. Every other type; strings, arrays, structs, callbacks; needs manual memory management across the JS/Wasm boundary. wasm-bindgen automates this entirely.


How Do You Export a Rust Function to JavaScript?

Add #[wasm_bindgen] to any public function or struct you want accessible from JS.

use wasm_bindgen::prelude::*;
 
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}
 
#[wasm_bindgen]
pub struct Counter {
    value: u32,
}
 
#[wasm_bindgen]
impl Counter {
    pub fn new() -> Counter {
        Counter { value: 0 }
    }
    pub fn increment(&mut self) { self.value += 1; }
    pub fn get(&self) -> u32 { self.value }
}

After building with wasm-pack build, call from JavaScript:

import init, { greet, Counter } from './pkg/my_crate.js';
await init();
console.log(greet("world")); // "Hello, world!"
const c = Counter.new();
c.increment();
console.log(c.get()); // 1

How Do You Call JavaScript APIs From Rust?

Use #[wasm_bindgen] on extern "C" blocks to import JS functions and browser APIs.

use wasm_bindgen::prelude::*;
 
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
 
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}
 
#[wasm_bindgen]
pub fn show_alert(msg: &str) {
    alert(msg);
    log(&format!("Alert shown: {msg}"));
}

web-sys and js-sys crates provide pre-generated bindings for the entire browser API; you rarely need to write extern "C" blocks manually.


How Do You Build a WASM Project?

Use wasm-pack; it compiles Rust to WASM, runs wasm-bindgen, and outputs an npm-compatible package.

# Install
cargo install wasm-pack
 
# Build (outputs to pkg/)
wasm-pack build --target web
 
# Build for Node.js
wasm-pack build --target nodejs
 
# Build for bundlers (webpack/vite)
wasm-pack build --target bundler

The pkg/ directory contains the .wasm file, generated JS bindings, and a package.json ready for npm publish.


wasm-bindgen vs wit-bindgen in 2026

wasm-bindgen targets the browser JS ecosystem today; wit-bindgen targets the emerging WASM Component Model for cross-language interop beyond JS.

wasm-bindgenwit-bindgen
Target environmentBrowser / Node.js (JS host)Any WASM runtime (Wasmtime, WASI, etc.)
Binding languageJavaScriptLanguage-agnostic (WIT interface files)
MaturityStable, widely usedStable spec, tooling still maturing
MaintainerRustwasm Working GroupBytecode Alliance
Use with wasm-pack✅ Yes❌ No (separate toolchain)
Type richnessJS types + Web APIsFull interface types across languages
Best forFrontend Rust, Leptos, Yew, DioxusServerless WASM, plugin systems, WASI

If you are building a browser app or targeting npm with wasm-pack, use wasm-bindgen. If you are building a plugin system, a cloud WASM serverless function, or need Rust to interop with Go or Python WASM modules, look at wit-bindgen and the Component Model; it is the direction WASM is heading in 2026 and beyond.


Frequently Asked Questions

wasm-bindgen is the core binding mechanism. web-sys is a crate built on top of it that provides auto-generated bindings for all browser Web APIs (DOM, fetch, canvas, etc.).

Yes. Use js_sys::Array for JS arrays or Vec<u8> for byte arrays; wasm-bindgen handles the memory copying. For large data, use js_sys::Uint8Array with zero-copy views where possible.

For compute-heavy tasks (parsing, compression, cryptography), Rust WASM is typically 2–5× faster than equivalent JS. For DOM-heavy work, JS is faster because DOM calls cross the Wasm boundary.

No, but it is strongly recommended. wasm-pack orchestrates cargo build --target wasm32-unknown-unknown, runs wasm-bindgen-cli, and optionally runs wasm-opt for size optimization. Doing it manually is tedious and error-prone.

Yes. Use the wasm-bindgen-futures crate to bridge Rust futures and JavaScript Promises. spawn_local runs a future on the JS event loop, and JsFuture::from converts a JS Promise into a Rust Future.


Sources


  • wasm: WebAssembly overview and Rust's role in it
  • leptos: Full-stack Rust framework that targets WASM
  • yew: React-like Rust frontend framework built on wasm-bindgen
  • dioxus: Cross-platform UI framework that also compiles to WASM
  • tokio: Use wasm-bindgen-futures for async Rust in WASM
  • async-await: Rust async primitives that work with wasm-bindgen-futures
  • Topcoat: Experimental Rust web frameworks still rely on the same core Wasm interop primitives

Keep Reading

Ready to Land a $80-120k Rust Job?