How to Fix: unknown import: `wasi_snapshot_preview1::fd_write` has not been defined
This error means your WebAssembly module is trying to import wasi_snapshot_preview1::fd_write, but the runtime loading it does not provide the WASI API. In practice, the module was compiled for a WASI environment, then executed in a non-WASI host such as a plain browser runtime, a JS loader expecting bare wasm, or a custom engine that never wires up WASI imports.
Understanding the Root Cause
The missing import fd_write belongs to the WASI snapshot preview1 interface. It is commonly used for stdout and stderr writes, which often get pulled in indirectly when Rust code uses macros like println!, eprintln!, logging crates, panic output, or any standard library path that assumes a system-like runtime.
When Rust code is built for a target such as wasm32-wasi or wasm32-wasip1, the generated wasm expects the host to provide functions under the import namespace wasi_snapshot_preview1. One of those functions is fd_write. If the host does not implement WASI, instantiation fails with an error like unknown import: wasi_snapshot_preview1::fd_write has not been defined.
This usually happens for one of four reasons:
- The project was compiled for the wrong wasm target.
- The wasm is being executed in a browser or JS environment without a WASI shim.
- The code or one of its dependencies uses std I/O, causing Rust to emit WASI imports.
- A build pipeline mixed artifacts from different targets, so the loader is receiving a WASI binary instead of a browser-compatible one.
If your goal is to run wasm in the browser or inside a host that only supports plain imports, you usually want wasm32-unknown-unknown, not a WASI target. If your goal is to run the module in a WASI-capable engine, then the fix is to provide a proper WASI runtime instead of changing the build target.
Step-by-Step Solution
The correct fix depends on where the module is supposed to run.
1. Identify the intended runtime
First, decide whether your wasm is meant for:
- Browser
- Node.js with a WASI adapter
- Wasmtime / Wasmer / another WASI runtime
- A custom host application
If the module is meant for the browser or a non-WASI embedding, rebuilding for a non-WASI target is usually the fix.
2. Check the current Rust target
Inspect your build commands, CI pipeline, or Cargo configuration. Look for a target like wasm32-wasi or wasm32-wasip1.
rustup target list --installed
Build commands that often cause this issue:
cargo build --target wasm32-wasi --release
cargo build --target wasm32-wasip1 --release
If you are shipping wasm to a browser-oriented loader, switch to:
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
3. Remove or reduce WASI-triggering standard I/O usage
Even after switching targets, your code should avoid patterns that assume OS-level stdout/stderr when targeting browser-style wasm. Common examples include:
- println!
- eprintln!
- Logging crates configured for native output only
- Panic handlers that write to stderr
Replace debugging output with host-compatible logging. For browser-oriented Rust wasm, prefer web-sys, js-sys, or wasm-bindgen console bindings.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
pub fn run() {
log("wasm module initialized");
}
If the issue comes from panic output, add a panic hook instead of relying on native stderr:
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
}
Add the dependency:
[dependencies]
wasm-bindgen = "0.2"
console_error_panic_hook = "0.1"
4. If you need browser wasm, use the right toolchain packaging
For browser-compatible modules, package with wasm-bindgen if your project exports JS-facing APIs.
cargo install wasm-bindgen-cli
cargo build --target wasm32-unknown-unknown --release
wasm-bindgen --target web --out-dir pkg ./target/wasm32-unknown-unknown/release/your_crate.wasm
This ensures the generated glue code matches browser expectations and avoids accidental reliance on WASI imports.
5. If you actually need WASI, run the module in a WASI-capable host
If your Rust code intentionally uses filesystem access, stdout, environment variables, or other WASI features, then do not rebuild for wasm32-unknown-unknown. Instead, execute the module with a host that supports WASI.
Example with Wasmtime:
cargo build --target wasm32-wasip1 --release
wasmtime ./target/wasm32-wasip1/release/your_crate.wasm
For Node.js, use its WASI support or a compatible adapter rather than directly instantiating the raw module without imports.
6. Inspect the compiled wasm imports to confirm the diagnosis
If you want to verify the binary imports before changing code, inspect the module with wasm tooling.
wasm-tools print your_module.wasm | grep wasi_snapshot_preview1
Or:
wasm2wat your_module.wasm | grep fd_write
If you see imports like the following, the module requires WASI:
(import "wasi_snapshot_preview1" "fd_write" ...)
7. Clean old artifacts before rebuilding
A very common source of confusion is stale build output from a previous target.
cargo clean
cargo build --target wasm32-unknown-unknown --release
If your CI, bundler, or deployment script copies files from a target directory, verify that it is using the correct artifact path.
8. Example fix for a Rust wasm library intended for browser execution
If the issue came from a Rust file using println! and a wasm artifact loaded into a browser-oriented host, a safe migration looks like this:
// Before
pub fn transform() {
println!("running transform");
}
// After
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn transform() {
log("running transform");
}
Then rebuild for the non-WASI target:
rustup target add wasm32-unknown-unknown
cargo clean
cargo build --target wasm32-unknown-unknown --release
Common Edge Cases
- A dependency pulls in std I/O: Even if your own code does not call println!, another crate may. Audit dependencies, especially logging, panic, CLI, and filesystem-related crates.
- Mixed target artifacts: A bundler may accidentally package a stale wasm32-wasi build instead of the latest wasm32-unknown-unknown artifact.
- Tests pass locally but fail in production: Local testing may use a WASI-capable tool, while production loads wasm in a browser or restricted host.
- Wrong runtime assumptions in JavaScript: Using WebAssembly.instantiate directly without providing imports will fail for any WASI module. The JS loader must either provide those imports or load a non-WASI binary.
- Panic paths still require output hooks: Removing println! is not always enough. Panic messages can still behave differently unless you configure a browser-friendly panic hook.
- Old docs mention wasm32-wasi: Depending on your toolchain and ecosystem, you may encounter naming differences such as wasm32-wasi and wasm32-wasip1. The underlying issue is the same: the binary expects WASI imports.
FAQ
Can I just stub out fd_write to silence the error?
You can, but it is usually the wrong fix. A stub may suppress stdout writes, yet other WASI imports may still be required. More importantly, stubbing hides the fact that the module was compiled for the wrong runtime. It is better to either rebuild for wasm32-unknown-unknown or run inside a real WASI host.
Why does using println! cause a WebAssembly import problem?
In Rust, println! writes to standard output. In a WASI target, standard output is implemented via the host function wasi_snapshot_preview1::fd_write. If the host does not expose that function, module instantiation fails.
How do I know whether my wasm should target WASI or unknown-unknown?
Use WASI if the module needs system-like capabilities such as stdout, files, or environment access and will run in a WASI-capable runtime. Use wasm32-unknown-unknown if the module is meant for the browser or a lightweight embedding where imports are explicitly controlled and no WASI host is present.
The shortest path to fixing this issue is to align all three layers: Rust target, runtime host, and code dependencies. If any one of them assumes WASI while the others do not, fd_write is often the first missing import that exposes the mismatch.