# Using iroh on Wasm Browser Platforms: Complete Configuration Guide

> Configure iroh for Wasm browser platforms. This guide details its use with cfg(wasm_browser) for relay-only networking, disabling IP transports.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-02

---

**Iroh compiles to WebAssembly for browser targets using the `cfg(wasm_browser)` flag to disable IP transports and enable relay-only networking with wasm-compatible logging and testing infrastructure.**

The iroh networking library from n0-computer maintains a single codebase that targets both native operating systems and WebAssembly browsers. Using iroh on wasm_browser platforms requires understanding how conditional compilation modifies the transport stack, logging system, and test harnesses to accommodate browser sandbox constraints. This guide covers the specific implementation details found in the source code and provides practical examples for building and running iroh in `wasm32-unknown-unknown` environments.

## How the Transport Stack Adapts to wasm_browser

The core transport manager resides in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs), where the `Transports` struct conditionally exposes IP networking capabilities based on the `wasm_browser` configuration flag.

When compiling without the flag, the crate builds both IP (IPv4/IPv6) transports alongside relay and custom transports. However, when `cfg(wasm_browser)` is defined, the IP transport module is completely omitted using `#[cfg(not(wasm_browser))]`, which removes the `ip: IpTransports` field from the struct. This architectural decision reflects the browser security model, which prohibits direct socket access.

The remaining transport stack consists solely of relay-based connectivity and any custom transports that avoid OS-specific APIs. This means browser-based endpoints cannot open direct peer-to-peer connections and must route all traffic through relay nodes.

## Wasm-Compatible Logging Configuration

Standard `tracing_subscriber::fmt` logging crashes in wasm browser contexts because it attempts to write to stdout. Instead, iroh integrates the `wasm_tracing` crate for browser-compatible diagnostics.

In [`iroh/tests/integration.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/integration.rs), the helper function `setup_logging()` demonstrates the proper initialization pattern:

- Create a `WasmLayerConfig` instance
- Set the maximum log level using `set_max_level()`
- Install the layer as the global default via `wasm_tracing::set_as_global_default_with_config()`

This configuration routes diagnostic output to the browser console rather than standard streams, enabling debugging in web-based environments.

## Testing Infrastructure for Browser Targets

The integration test suite in [`iroh/tests/integration.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/integration.rs) supports dual-target compilation through conditional attributes. When `#[cfg(wasm_browser)]` is active, the test harness switches from `tokio::test` to `wasm_bindgen_test::wasm_bindgen_test`.

This switch allows the same async test code to execute in both native Tokio runtimes and browser JavaScript environments. The comment in the source file explains this "either-or" setup, ensuring CI pipelines can validate functionality across platforms without maintaining separate test files.

## Build Configuration for wasm32-unknown-unknown

The `wasm_browser` flag is not a Cargo feature but a compiler configuration supplied via `--cfg wasm_browser` when targeting `wasm32-unknown-unknown`. Cargo automatically defines `cfg(target_arch = "wasm32")`, and the repository adds the alias during the build process.

Standard build commands for browser deployment:

```bash
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --features=web

```

The `web` feature activates `wasm-bindgen` and `wasm-tracing` as dev-dependencies, pulling in the necessary glue code for browser interop. No changes to [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml) are required to enable the `wasm_browser` configuration itself.

## Runtime Behavior and Relay Connectivity

Because IP transports are unavailable in the browser sandbox, endpoints establish connectivity exclusively through relays. The relay implementation in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) provides both client and server logic for public relay nodes that bridge browser clients.

Endpoint discovery functions through the DNS-based PKARR resolver located in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs), which allows browsers to locate relay URLs without direct DNS socket access. Connections maintain state using QUIC via the `noq` library, with `RelayUrl` types from [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs) defining the addressing scheme throughout the wasm transport path.

Custom transports remain supported in wasm builds as long as they restrict themselves to Web APIs rather than native OS sockets.

## Practical Implementation Examples

Creating a browser-compatible endpoint requires initializing wasm-specific logging and binding through relay presets:

```rust
use iroh::{Endpoint, RelayMode};
use iroh::endpoint::presets;
use wasm_tracing::WasmLayerConfig;
use tracing::Level;

// Initialize browser console logging
fn init_logging() {
    let mut cfg = WasmLayerConfig::new();
    cfg.set_max_level(Level::TRACE);
    wasm_tracing::set_as_global_default_with_config(cfg)
        .expect("logging init");
}

// Build endpoint using only relay transport
async fn make_endpoint() -> Result<Endpoint, iroh::Error> {
    init_logging();
    
    Endpoint::builder(presets::N0)
        .relay_mode(RelayMode::Staging)
        .bind()
        .await
}

```

Testing bidirectional streams in the browser harness:

```rust
const ECHO_ALPN: &[u8] = b"echo";

#[wasm_bindgen_test::wasm_bindgen_test]
async fn wasm_echo_demo() {
    let client = make_endpoint().await.unwrap();
    let server = make_endpoint().await.unwrap();
    
    // Wait for relay connection establishment
    client.metrics()
        .wait_for_online()
        .await
        .expect("server online");
    
    let conn = client.connect(server.id(), ECHO_ALPN).await.unwrap();
    let (mut send, mut recv) = conn.open_bi().await.unwrap();
    
    send.write_all(b"Hello, wasm!").await.unwrap();
    send.finish().unwrap();
    
    let reply = recv.read_to_end(1024).await.unwrap();
    assert_eq!(reply, b"Hello, wasm!");
}

```

## Summary

- The `cfg(wasm_browser)` flag in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs) gates the `IpTransports` field, restricting browser builds to relay-only connectivity.
- Browser environments require `wasm_tracing` with `WasmLayerConfig` instead of standard `tracing_subscriber` loggers.
- Tests switch from `tokio::test` to `wasm_bindgen_test::wasm_bindgen_test` when the wasm_browser configuration is active.
- Build commands target `wasm32-unknown-unknown` with `--features=web` to include wasm-bindgen dependencies.
- Runtime connectivity depends on the relay implementation in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) and PKARR resolution in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs).

## Frequently Asked Questions

### How do I enable the wasm_browser configuration in my Cargo.toml?

You do not enable it through Cargo features. The `wasm_browser` flag is passed via compiler configuration when building for the wasm target. Use `cargo build --target wasm32-unknown-unknown --features=web` where the build script supplies `--cfg wasm_browser`. The `web` feature only pulls in necessary dependencies like `wasm-bindgen` and `wasm-tracing`.

### Why can't my wasm browser endpoint connect directly to IP addresses?

Browser WebAssembly runs inside a security sandbox that prohibits raw socket access. In [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs), the `#[cfg(not(wasm_browser))]` gate removes the `IpTransports` field entirely, forcing all traffic through relay nodes that use WebRTC or WebTransport protocols compatible with browser security models.

### Can I use custom transports with iroh in the browser?

Yes, custom transports compile for wasm targets provided they do not depend on OS-specific socket APIs. The transport manager in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs) supports pluggable transports alongside the default relay transport when `cfg(wasm_browser)` is defined.

### How do I run integration tests in a headless browser?

Compile and run tests using `cargo test --target wasm32-unknown-unknown --features=web`. This invokes `wasm_bindgen_test` which can run in Node.js or browsers via `wasm-pack`. The test harness automatically handles the asynchronous runtime differences between native Tokio and browser environments.