# wasm-tools: the Bytecode Alliance CLI for inspecting and rewriting WebAssembly modules

> wasm-tools bundles validation, printing, parsing, component conversion and a small fuzzing toolset into one binary, and most of the interesting subcommands also exist as standalone Rust crates. Its conventions are uniform enough that the flags transfer between commands, and its version numbers track WebAssembly proposal support closely enough to read as a changelog of the standard.

**bytecodealliance/wasm-tools** —  CLI and Rust libraries for low-level manipulation of WebAssembly modules 

- Repository: https://github.com/bytecodealliance/wasm-tools
- Stars: 1,804 · Forks: 361
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/bytecodealliance-wasm-tools

## One binary, many crates, and the table that tells you which is which

wasm-tools is described as CLI and Rust libraries for low-level manipulation of WebAssembly modules, and it is a Bytecode Alliance project. The important structural fact is that the CLI is a front end over separately published crates, and the project documents the mapping in a table rather than leaving you to guess.

Validation runs on wasmparser. Translating text to binary uses the wat and wast crates, and binary to text uses wasmprinter. Component creation from a core module uses wit-component, composing components uses wasm-compose, and metadata display uses wasm-metadata. Test-oriented work has its own entries: wasm-smith generates a valid module from a seed, wasm-mutate turns an input module into a different valid one, and wasm-shrink reduces a file while preserving a predicate.

Several subcommands have no crate equivalent, which is itself useful information: dump for binary format debugging, objdump for section headers, strip for custom sections, demangle for Rust and C++ names in the name section, addr2line for DWARF offset translation, completion for shell completion scripts, and json-from-wast plus wast for working with script test files.

So the library surface is narrower than the command list. If you are embedding wasm-tools in a Rust service, you are depending on a handful of crates, not on the whole binary.

## Installing from Cargo or from the release artifacts

Precompiled artifacts built on CI are published for each release, and there are two routes to them. Building from source needs Rust installed for your platform, then Cargo:

```bash
cargo install --locked wasm-tools
```

The --locked flag matters more than it looks for a tool like this: it pins the dependency versions recorded in Cargo.lock, so the compiler you build matches what CI built.

If you use cargo-binstall, it fetches the precompiled artifacts instead of compiling:

```bash
cargo binstall wasm-tools
```

The repository carries the metadata that makes the second path work, including per-target overrides: macOS artifacts use a macos suffix on both x86_64 and aarch64, Windows targets are zip archives while other targets are tgz.

Confirm the install with the version flag and explore the command surface with help:

```bash
wasm-tools --version
wasm-tools help
```

Expect a large number to come back. Cargo.toml pins version 1.262.0, and the release history shows v1.261.0 and v1.258.3 both on 2026-10-02, so this tool ships often.

## Validation is where the proposal flags matter

The validate subcommand accepts both binary and text input, converting on the fly:

```bash
wasm-tools validate foo.wasm
wasm-tools validate foo.wat
```

Feature handling is the part to internalize. A file using a proposal that is off by default has to say so, and a file using something enabled by default can be turned off, so you can pin the exact feature set your pipeline assumes:

```bash
wasm-tools validate foo.wasm --features=exception-handling
wasm-tools validate foo.wasm --features=-simd
```

That is a better contract than a plain validator that silently accepts whatever the binary happens to permit. A CI job that validates with an explicit feature list is testing a specific proposal configuration rather than the tool's defaults, which can move between releases.

Converting between formats is the same tool:

```bash
wasm-tools print foo.wasm -o foo.wat
wasm-tools parse foo.wat -o foo.wasm
```

This round trip is the quickest sanity check available for a module someone else built.

## Piping works because the CLI conventions are uniform

The project documents a handful of conventions that every subcommand follows, and those conventions are why the examples read as pipelines rather than as a list of separate tools.

Input is read from stdin when no file is given. Output goes to stdout unless -o or --output is passed, with the specific guard that binary WebAssembly is not printed to a tty by default. Commands that emit WebAssembly binaries accept -t or --wat to emit the text format instead. Short help is -h and long help is --help. Verbosity is -v and can be repeated as in -vvv, color is on by default for tty output and configurable with --color.

Those rules are what make the mutation example possible as one line:

```bash
wasm-tools mutate foo.wasm --seed 192 | wasm-tools validate
```

Generate a mutated module from a chosen seed, then pipe it straight into the validator. The --seed is what makes this reproducible, which matters for a fuzzer or a differential test rather than for a one-off curiosity.

The demangle, strip and objdump chain shows the same idea applied to triage:

```bash
wasm-tools demangle foo.wasm | wasm-tools strip | wasm-tools objdump
```

Demangle Rust and C++ symbol names in the name section, drop every other custom section, and print the section headers that remain. Stripping before dumping is the right order, because the sections you are about to read are exactly the ones the first two commands removed clutter from.

## Components and WIT: the part with real prerequisites

The component subcommands work on the component model rather than on core modules, and the WIT subcommand extracts and converts interface definitions:

```bash
wasm-tools component wit component.wasm
wasm-tools component wit ./wit --json
wasm-tools component wit ./wit --wasm | wasm-tools component wit
```

That last one is a round trip through the binary encoded format, which is a cheap consistency check on a WIT package.

Creating a component from a core module has a prerequisite that trips people up:

```bash
wasm-tools component new my-core.wasm -o my-component.wasm
```

The README states this requires WIT metadata having previously been embedded in the core wasm module, which is what the component embed subcommand exists for. So the order is embed, then new, and if you skip the first step the failure is a missing metadata error rather than an obvious prompt.

For modules that use WASI, an adapter is supplied explicitly:

```bash
wasm-tools component new my-core.wasm -o my-component.wasm --adapt wasi_snapshot_preview1.reactor.wasm
```

An older route, wasm-tools compose, is listed in the tool table and marked deprecated. Deprecation in a table like this is a signal to check the release notes for what replaces it before you build a script around it.

## Limits: no runtime, and a version that moves quickly

The first boundary is scope. wasm-tools manipulates modules; it does not run them. There is no execution, no profiling and no runtime state inspection here, so if your question is what a module does at runtime rather than what it contains, this is the wrong tool and a runtime project is what you need.

The second is the release cadence. The listed releases include v1.262.0 on 2026-10-13, v1.261.0 on 2026-10-02 and v1.258.3 on 2026-10-02. The last push was on 2026-10-15 and the repository is not archived. For a CLI that is fine. For a pinned CI dependency it means you should decide deliberately whether to float or to pin, and know that proposal support and default feature sets can change under a minor version bump.

The third is the size of the surface. This is one binary with many subcommands and a large help text each, and learning it means picking a subset. Learning the tool means focusing on the subcommands relevant to your work.

The fourth is that the workspace denies unsafe code, as the lint configuration sets unsafe_code to deny at the workspace level. That is a good sign for a tool that parses untrusted binaries, and it also means the Rust crates are stricter than some equivalents you might reach for instead.

## How wabt and the standalone crates differ

The other alternative is writing against the crates directly, which is where wasmparser, wasmprinter, wat and wit-component sit. The difference is control rather than capability: embedding wasmparser gives you structured validation with custom error reporting and no process spawn, and costs you a dependency graph to track at versions that move weekly.

There is also a hint in the repository itself. Cargo.toml excludes tests/wabt, tests/testsuite and tests/snapshots from the package, and the repository includes a fuzz/ directory plus fuzz-stats and wasm-mutate-stats crates.

## Licence, security and what to check

The repository carries LICENSE-APACHE, LICENSE-MIT and LICENSE-Apache-2.0_WITH_LLVM-exception, so the licence choice is yours between a standard permissive pair and the Apache 2.0 grant carrying the LLVM exception. There is also a SECURITY.md, a CODE_OF_CONDUCT.md, an ORG_CODE_OF_CONDUCT.md, a CONTRIBUTING.md and an AGENTS.md at the root, which together describe how the project handles reports and contributions. This is a licence note rather than legal advice.

Practically, for an adopter there are three checks. Pin the version, and record why, since default feature sets move between releases. Make your CI validation use explicit --features flags rather than defaults, so a new default cannot turn a previously failing module into a passing one. And if you rely on addr2line, confirm your modules carry DWARF, since the mapping from wasm offsets to filename and line numbers depends on it.

For component work, verify that the WIT metadata is embedded before you run component new, and confirm your adapter path for WASI modules matches the wasi_snapshot_preview1 reactor the example uses.

## Conclusion

Adopt wasm-tools as the default inspection tool for WebAssembly in your build and CI pipeline, because validate, print, objdump, addr2line and the component subcommands cover the debugging questions that otherwise need a mix of separate tools, and because the crates behind them let you embed the same checks in Rust code. Do not adopt it as a runtime: it is an offline manipulation toolchain with no execution engine, and wasm-tools compose is marked deprecated in the command table, so avoid that subcommand in new automation. Verify first which version you have with wasm-tools --version before comparing output against a proposal specification, since releases are frequent, v1.262.0 shipped on 2026-10-13, and feature validation defaults are off: a module using an off by default proposal needs --features=exception-handling or similar or validation will reject it.

## FAQ

### How do I install wasm-tools?

Build it from source with cargo install --locked wasm-tools after installing Rust, or fetch the precompiled CI artifacts with cargo binstall wasm-tools. Confirm with wasm-tools --version and explore subcommands with wasm-tools help.

### How do I validate a WebAssembly module that uses an off-by-default proposal?

Pass the feature explicitly, for example wasm-tools validate foo.wasm --features=exception-handling. You can also disable a default feature with a minus sign, as in wasm-tools validate foo.wasm --features=-simd, which pins the exact proposal configuration your pipeline accepts.

### Why does wasm-tools component new fail on my core module?

The README states this conversion requires WIT metadata having previously been embedded in the core wasm module. For a module using WASI you also pass an adapter, as in wasm-tools component new my-core.wasm -o my-component.wasm --adapt wasi_snapshot_preview1.reactor.wasm.

### Which Rust crates sit behind the wasm-tools subcommands?

Validation uses wasmparser, text to binary uses wat and wast, binary to text uses wasmprinter, component creation uses wit-component, composing uses wasm-compose and metadata display uses wasm-metadata. dump, objdump, strip, demangle and addr2line are CLI only.

### Is wasm-tools compose deprecated?

The tool table in the README marks wasm-tools compose as deprecated. Its crate equivalent is wasm-compose, and newer component work is oriented around wasm-tools component new and the wit subcommands instead.

## Sources

- [bytecodealliance/wasm-tools on GitHub](https://github.com/bytecodealliance/wasm-tools)
- [Issues](https://github.com/bytecodealliance/wasm-tools/issues)
- [License: Apache-2.0](https://github.com/bytecodealliance/wasm-tools/blob/main/LICENSE)
- [README](https://github.com/bytecodealliance/wasm-tools/blob/main/README.md)
- [Releases](https://github.com/bytecodealliance/wasm-tools/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bytecodealliance-wasm-tools
