Compiling with GOOS=wasip1 GOARCH=wasm
Go's WASI target turns ordinary packages into portable .wasm modules.
Search across all documentation pages
Go's WASI target turns ordinary packages into portable .wasm modules.
The compile command mirrors cross-compilation to Linux or Windows - set GOOS and GOARCH, then go build.
Understanding which flags matter, and which files (wasm_exec.js) belong to a different target, keeps your build scripts and CI matrices correct.
GOOS=wasip1 with GOARCH=wasm selects the WASI Preview 1 syscall surface.
The Go linker emits a module importing wasi_snapshot_preview1 functions instead of platform-specific syscalls.
You execute the artifact with an external WASM host (wasmtime, wazero, Spin) rather than the go command.
Release builds commonly pass -ldflags="-s -w" to strip debug metadata.
Browser-target notes about wasm_exec.js do not apply to wasip1 output.
Quick-reference recipe card - copy-paste ready.
GOOS=wasip1 GOARCH=wasm go build -ldflags="-s -w" -o app.wasm .
wasmtime run app.wasmWhen to reach for this:
.wasm artifactpackage main
import (
"encoding/json"
"fmt"
"os"
)
type config struct {
Name string `json:"name"`
}
func main() {
raw := os.Getenv("CONFIG_JSON")
if raw == "" {
fmt.Fprintln(os.Stderr, "CONFIG_JSON required")
os.Exit(1)
}
var cfg config
if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("hello, %s\n", cfg.Name)
}go mod init example.com/wasigreet
# save main.go, then:
GOOS=wasip1 GOARCH=wasm go build -ldflags="-s -w" -o greet.wasm .
wasmtime run --env CONFIG_JSON='{"name":"Ada"}' greet.wasmWhat this demonstrates:
encoding/json, os) compile for WASI without tags.--env in wasmtime).os.Stderr maps to host stderr streams.wasip1 maps os file operations to WASI fd APIs the host must implement._start export is the entry the host calls after instantiating linear memory.go mod and vendoring work the same as native builds; cgo is not available on this target.| Variable / Flag | Purpose |
|---|---|
GOOS=wasip1 | Select WASI Preview 1 syscall ABI |
GOARCH=wasm | Select WebAssembly architecture |
-ldflags="-s -w" | Strip symbol/debug tables for smaller binaries |
GOWASM=... | Historically toggled WASM features; signext/satconv ignored in Go 1.26 |
GOFLAGS=-trimpath | Reproducible builds without host paths embedded |
| Artifact | Target | How to run |
|---|---|---|
app.wasm | wasip1/wasm | wasmtime run, wazero embed, Spin deploy |
main.wasm + wasm_exec.js | js/wasm | Browser or Node with JS runtime shim |
Do not copy GOROOT/misc/wasm/wasm_exec.js into WASI deployment bundles.
// Separate browser entry files - never compile these into wasip1 builds.
//go:build js && wasm
package browseronly# Makefile pattern
build-wasi:
GOOS=wasip1 GOARCH=wasm go build -ldflags="-s -w" -o bin/app.wasm ./cmd/appAssuming go run works - There is no WASI emulator inside go run; you must execute with a WASM host. Fix: wrap wasmtime run or wazero in go:generate/Makefile targets.
Filesystem writes without pre-open - os.Create("/tmp/x") fails if the host did not map /tmp. Fix: pass wasmtime run --dir /host/tmp::/tmp or configure wazero FSConfig.
Mixing js/wasm files into WASI packages - Accidental syscall/js imports break wasip1 compilation. Fix: isolate browser code behind js && wasm build tags.
Expecting full networking - WASI preview 1 does not guarantee TCP APIs on every runtime. Fix: confirm socket support or use Spin HTTP triggers.
Huge binaries by surprise - Full Go runtime adds megabytes even for fmt.Println. Fix: measure size early; consider TinyGo or split hot paths.
Stale GOOS=wasi - Older docs reference wasi; modern toolchains use wasip1. Fix: update scripts to wasip1 for Go 1.21+.
| Alternative | Use When | Don't Use When |
|---|---|---|
GOOS=js GOARCH=wasm | DOM/fetch interop in browsers | Headless servers or wazero embedding |
TinyGo -target=wasm | Strict size budgets | You need full reflect/generics-heavy libs |
Native GOOS=linux | Maximum performance and syscall coverage | You need sandboxed portable plugins |
Rust wasm32-wasi | Minimum binary size is paramount | Team standard is Go and stdlib coverage matters |
Go 1.21 stabilized GOOS=wasip1 as the WASM server target.
Go 1.26 further optimizes WASM heap chunking and WASM instruction lowering.
No. Disable cgo (CGO_ENABLED=0, the default for cross builds) and keep dependencies pure Go.
WASI maps args to os.Args.
wasmtime run app.wasm arg1 arg2 populates them like a normal process.
Yes. Build tags still split files; use wasip1-specific files when you need target-only code paths.
Keep a parallel GOOS=linux build for Delve, and use wasmtime run with logging for integration issues.
Unstripped DWARF builds are larger but can help advanced WASM debuggers.
WASM dynamic linking is host-specific.
Most teams compile one module per plugin and expose a narrow ABI (for example JSON over stdin/stdout).
That is the browser target, not WASI.
It produces different imports and requires wasm_exec.js.
Yes under the full Go compiler.
TinyGo also supports goroutines with a smaller scheduler, subject to its limits.
Start with -ldflags="-s -w", audit dependencies, and evaluate TinyGo.
Go 1.26 reduces runtime memory, not necessarily file size on disk.
go test does not execute WASI tests natively.
Compile test binaries to WASM and run under wasmtime, or keep logic tests on linux/amd64 and WASM-smoke in CI.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026