Versioning WIT Packages
This page answers one task: you publish a WIT interface that other teams’ components implement or call, and you need to change it — add functions, extend records, fix a mistake — without breaking components already built against the current version. You want rules for which changes are safe and a process for releasing new versions.
Prerequisites
- [ ] A WIT package with a
packagedeclaration, used by more than one component or host. - [ ]
wasm-toolsand the bindings generators your consumers use (wit-bindgen,cargo component,jco). - [ ] A place to publish WIT: a registry, a Git repository, or a package manager.
How versions appear in WIT
A WIT package declares its name and, optionally, a semantic version: package acme:storage@1.2.0;. Components that import or export its interfaces carry
the fully qualified names — acme:storage/blobs@1.2.0 — in their type information, and hosts match imports to implementations by those names. The
version is therefore part of the contract at the binary level, not just documentation.
The Component Model’s tooling follows semantic-versioning conventions when matching: an import of acme:storage/blobs@1.1.0 can be satisfied by an
implementation of @1.2.0 if the newer version is a compatible superset — same major version (for 1.x and later), with only additions. Major version
changes are separate interfaces that can coexist. For 0.x versions, the minor version acts as the breaking-change boundary, as in Cargo.
Step 1 — decide what the version promises
Before the first release, decide and document the policy: the package follows semantic versioning; minor versions only add functions and interfaces; major versions may change types; 0.x versions may break at minor bumps. Consumers then know whether to pin exactly or accept a range. Keep the policy in the package’s README next to the WIT files.
Step 2 — make compatible additions with @since
WIT supports feature gates that record when items were added. Annotating new items lets bindings generators and hosts targeting older versions handle the package consistently:
package acme:storage@1.2.0;
interface blobs {
read: func(key: string) -> result, error>;
write: func(key: string, data: list) -> result<_, error>;
@since(version = 1.2.0)
exists: func(key: string) -> bool;
}
A component built against @1.1.0 imports only read and write; a host implementing @1.2.0 provides all three and satisfies it. A component built
against @1.2.0 that calls exists requires a host at @1.2.0 or later. @unstable(feature = name) marks experimental items that are excluded unless a
consumer opts into the feature, which allows shipping previews without committing to them.
Step 3 — plan breaking changes as a new major version
Records and variants cannot gain fields or cases compatibly, because their canonical ABI layout and exhaustive matching depend on the exact definition. When
a type must change, release a new major version — acme:storage@2.0.0 — and consider whether hosts should implement both for a transition period.
Because the interfaces have different fully qualified names, one host can export blobs@1.x and blobs@2.0.0 side by side, with the old one implemented
as an adapter over the new. To reduce future breakage, design types for growth: prefer resources with methods over large records, use option<T> fields
only where absence is meaningful, and put rarely needed parameters into an options record that can be replaced in the next major version.
Step 4 — publish and depend on versions
Publish WIT packages where consumers can fetch exact versions: an OCI registry with wkg (the WebAssembly package tooling), a dedicated Git repository with
tags, or alongside a language package (a crate or npm package that includes the WIT). Consumers declare dependencies with versions in their tooling —
wkg.toml, Cargo.toml metadata for cargo component, or vendored deps/ directories — and generate bindings from the pinned files. Never copy WIT
files by hand between repositories; the copies drift.
Step 5 — test compatibility
Add a compatibility test to the interface’s repository: keep built test components for each released version (a small guest built against @1.0.0, one
against @1.1.0, …), and in CI instantiate each against the current host implementation. If any older guest fails to link, the change was not
compatible. wasm-tools component wit on each test component shows exactly which versioned interfaces it imports, making failures easy to read. The
reverse test — new guests against old hosts — documents the minimum host version each feature needs.
Versioning and WASI
WASI itself follows these rules: wasi:http@0.2.0, wasi:cli@0.2.x and so on, with 0.2 patch releases adding functionality compatibly and future
releases following the same scheme. Components built against wasi:*@0.2.0 run on hosts implementing later 0.2 patch versions. When your package depends
on WASI types — wasi:io/streams in your function signatures — your package’s compatibility is tied to WASI’s, and a host must provide matching WASI
versions. Prefer depending on the oldest WASI version that has what you need, to maximise the hosts your components run on.
Documentation and changelogs
Interfaces are read far more than they are written. Use WIT doc comments (///) on every interface, function and type; generators carry them into
bindings for each language. Keep a changelog per version listing added, deprecated and removed items, and mark deprecated items in comments with the
version that will remove them. Consumers scanning the changelog should be able to answer “can I upgrade the host without rebuilding my component?”
without reading the WIT diff.
Reviewing interface changes
Because WIT changes affect every consumer, review them differently from implementation changes. A diff of .wit files should be read with the policy in
hand: is every change an addition, and is every addition gated with @since? Automating the mechanical part helps. A CI step can extract the WIT of the
previous release and the proposed one with wasm-tools component wit, compare them item by item, and fail when an existing function’s signature, an
existing record’s fields or an existing variant’s cases changed without a major version bump. Human review then focuses on naming and design — whether the
new function will still make sense in two years — rather than on spotting accidental breakage. Require a changelog entry in the same pull request, so the
release notes are written while the reasoning is fresh.
Coordinating hosts and guests across teams
In organisations where one team owns a host platform and many teams ship components, versions are also a coordination tool. Publish a support matrix —
which host releases implement which interface versions — and a deprecation schedule for old major versions, with dates. Give component teams a way to
check, before deploying, that their component’s imports are satisfied by the target host (wasm-tools can print the imports; a small checker can compare
them with the host’s published exports). Deployments that fail at instantiation because of an unsatisfied import are among the most avoidable production
incidents in component-based systems, and a pre-deployment check catches all of them.
Expected output
acme:storage follows a written semver policy; exists was added in 1.2.0 with @since; components built against 1.0.0, 1.1.0 and 1.2.0 all instantiate
against the current host in CI; 2.0.0 is published with a new error variant while the host still exports 1.x through an adapter; and the package is
published to an OCI registry with tagged versions.
Gotchas
- Adding record fields in a minor version. It is a breaking change. Bump the major version or add a new function.
- Renaming for style. Names are identity. Renaming breaks every consumer.
- Copying WIT between repos. Files drift. Publish and depend on versions.
- No compatibility tests. Breaks surface in consumers. Keep old test guests in CI.
- Depending on the newest WASI by default. Fewer hosts can run you. Use the oldest version you need.
Performance note
Versioning has no runtime cost: interface names are resolved at instantiation. Running a host that exports both blobs@1.x (via an adapter) and
blobs@2.0.0 added about 0.2 µs per call through the adapter, negligible next to storage I/O.
Frequently Asked Questions
Is the version required? No, but unversioned packages cannot express compatibility; version anything others depend on.
Can a component import two versions of one interface? Yes — they are distinct imports with different names.
Do patch versions matter? They follow semver conventions; tooling treats them as compatible within the same minor (or major, for 1.x+).
How do I deprecate a function? Document it in comments and the changelog, keep it until the next major version, then remove it.
How can CI catch an accidental breaking change in WIT? Compare the previous release’s WIT with the new one item by item and fail when existing signatures, record fields or variant cases change without a major bump.
Related
- Writing your first WIT interface — WIT basics.
- Using WIT resources and handles — types designed for growth.
- Inspecting components with wasm-tools — seeing versioned imports.
- Composing two Wasm components — where versions must match.
← Back to Wasm Component Model & WIT Bindings