WebAssembly has two canonical representations: the binary .wasm format that runtimes execute, and the text .wat format that humans write and read. Moving between them, validating modules against the spec, and building tools that work with Wasm programmatically has long required reaching for C++ or Rust. For Go developers, that dependency chain has been an awkward fit.
watgo is Eli Bendersky’s answer to that gap: a library and CLI that provides what wabt gives you in C++ and wasm-tools gives you in Rust, written in pure zero-dependency Go. The project covers parsing WAT to an in-memory semantic IR, validating that IR against the official WebAssembly spec, encoding to binary WASM, and decoding binary WASM back to the IR. That is the complete round-trip for working with WebAssembly at the format level.
What the Existing Tooling Looks Like
The reference toolkit has been wabt (the WebAssembly Binary Toolkit), maintained by the WebAssembly community group and written in C++. It provides wat2wasm, wasm2wat, wasm-validate, wasm-objdump, and wasm-interp as separate command-line tools. wabt is solid and widely trusted; it is essentially the reference implementation for format-level Wasm work. Using it from Go, however, means shelling out, writing CGo bindings, or going through wabt.js in a browser context.
wasm-tools is the more recent entrant from the Bytecode Alliance, written in Rust. It covers similar ground to wabt but has stayed current with the WebAssembly Component Model and WASI previews, making it the natural choice for the Rust/Wasm/WASI ecosystem. It exposes both a CLI and a library crate, so Rust code can parse, transform, and emit Wasm in-process. The gap for Go has been that nothing at this level of completeness existed as a native library.
Partial solutions have existed. A handful of projects implement WAT parsing in Go, and the standard library’s encoding/binary is capable of reading WASM section headers if you implement the format yourself. But a full-stack toolkit with spec-compliant validation and a coherent semantic representation has been absent.
What watgo Provides
The project is structured around three layers. The CLI is the most immediately accessible:
go install github.com/eliben/watgo/cmd/watgo@latest
The interface is intentionally compatible with wasm-tools. Compiling a WAT source file to binary looks like:
watgo parse stack.wat -o stack.wasm
Bendersky has already switched his wasm-wat-samples project to use the watgo CLI rather than wasm-tools, which gives you a real-world reference for how it integrates into a WAT-based workflow.
The second layer is the Go API via the watgo package. The third, and most interesting, is wasmir: the semantic intermediate representation at the center of everything.
The Zero-Dependency Design Decision
Pure Go with zero external dependencies is not a default that happens by accident; it is a deliberate constraint with real consequences.
With no CGo and no native dependencies, watgo builds with a plain go build on any platform Go supports. That includes cross-compilation: a Linux binary from macOS, ARM from x86, without needing C++ or Rust toolchains present. For a library that might end up embedded in build pipelines, test harnesses, or developer tooling, that portability matters considerably.
The Go ecosystem has seen this pattern before in adjacent space. wazero, the pure-Go WebAssembly runtime, made the same bet. Before wazero, running Wasm from Go meant CGo bindings to Wasmtime or Wasmer. wazero changed that: import a library, call it like any other Go code, no native toolchain required. watgo applies the same logic to the tooling layer.
Zero dependencies also means the library is stable to import. Transitive dependencies are a persistent source of friction in Go: conflicting versions, unexpected C libraries, builds that fail in environments where a package author assumed a specific native toolchain. A library with an empty go.mod dependency list cannot cause those problems.
wasmir: Semantic Representation, Not Just Syntax
The name wasmir stands for WebAssembly semantic IR, and the distinction between a semantic representation and a syntax tree is worth being precise about.
A syntax tree for WAT would mirror the parse structure: nodes for modules, functions, instructions, reflecting the S-expression hierarchy. A semantic representation has passed through the validation rules from the WebAssembly specification and represents the module in terms of its actual semantics: resolved types, validated instruction sequences, fully formed import and export descriptors. You can query it for what the module exports, what types its functions carry, what linear memory it declares, without re-interpreting raw text.
This is what makes the Go API usable for programmatic work. A simplified version of the example from the announcement:
package main
import (
"fmt"
"github.com/eliben/watgo"
)
const wasmText = `
(module
(func (export "add") (param i32) (param i32) (result i32)
local.get 0
local.get 1
i32.add))`
func main() {
m, err := watgo.ParseString(wasmText)
if err != nil {
panic(err)
}
for _, exp := range m.Exports {
fmt.Println(exp.Name, exp.Descriptor)
}
}
The *wasmir.Module returned by ParseString is not a raw parse tree. It is a validated, typed representation that you can introspect and manipulate. That is the foundation for tools that generate or transform Wasm from Go: compilers targeting Wasm, linkers, instrumentation, test infrastructure that synthesizes modules with specific properties.
The Use Cases This Opens
For most Go developers, the immediate use case is the CLI: a drop-in wasm-tools-compatible alternative that installs via go install and requires nothing else. That alone simplifies toolchains that currently have a Rust or C++ dependency just for WAT compilation.
The library case is more interesting. Consider a Go-based compiler or code generator that targets WebAssembly. Rather than emitting binary format by hand, which requires careful implementation of every section type and LEB128 encoding, or shelling out to an external tool, the compiler can construct a wasmir.Module in memory, run the encoder, and get a spec-compliant binary back. The validation step catches errors before the output reaches a runtime.
Testing is another concrete case. Integration tests for Wasm-consuming Go code often need modules with specific properties: a module that exports exactly this set of functions, a module with a particular table layout, a module that imports a specific interface. Generating those programmatically from Go is more maintainable than managing a library of hand-written WAT files. With watgo’s API, that generation is straightforward.
The decode path matters for inspection use cases. A Go service that receives arbitrary .wasm binaries and needs to verify their exports, check type signatures, or extract custom sections can decode to wasmir and query the result without spawning any external process. This is particularly relevant in serverless or sandboxed environments where exec is restricted.
Where This Sits in the Broader Ecosystem
WebAssembly tooling has been heavily weighted toward C++ and Rust because that is where the most intensive Wasm development has happened. The Bytecode Alliance projects, the Component Model toolchain, and the major Wasm runtimes are predominantly in Rust. Go’s role has mostly been as a consumer: programs compiled to Wasm via TinyGo or GOARCH=wasm GOOS=wasip1, and Wasm hosts using wazero.
watgo extends Go’s presence further into the tooling layer, the part of the stack that has until now required stepping outside the Go ecosystem. Whether it grows toward the comprehensiveness of wasm-tools, with Component Model support and custom section manipulation, depends on how much the Go community pushes in that direction. For now, it covers the core operations cleanly, builds without external dependencies, and sits alongside wazero as a data point that a complete Go-native Wasm infrastructure is achievable.
The CLI is at github.com/eliben/watgo/cmd/watgo, the library at github.com/eliben/watgo, and the semantic IR at github.com/eliben/watgo/wasmir. For anyone building tools around WebAssembly in Go, it is the most complete starting point the ecosystem has had.