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 package declaration, used by more than one component or host.
  • [ ] wasm-tools and 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.

Which WIT changes are compatible Adding a new function or a new interface to a package is compatible within a major version. Adding a field to a record, a case to a variant or a parameter to a function changes existing types and is breaking. Renaming anything is breaking. New items can be marked with since gates so older consumers ignore them. change compatible? notes add a function to an interface yes (minor bump) old callers ignore it add a new interface yes (minor bump) opt-in for consumers add a field to a record no (major bump) changes the type's layout add a case to a variant / enum no (major bump) old code cannot handle it change parameters or results no (major bump) signature differs rename anything no (major bump) names are identity

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.

An interface evolving across versions Version 1.0.0 ships read and write. Version 1.1.0 adds a list function. Version 1.2.0 adds exists with a since gate. Version 2.0.0 changes the error variant and is released as a new major version, while hosts keep implementing 1.x for existing components during a transition. 0 months 1.0.0: read, write 5 months 1.1.0: + list 9 months 1.2.0: + exists 16 months 2.0.0: new error variant 22 months 1.x retired

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.

Call overhead through a version adapter Microseconds per call for a guest calling the 2.0 interface directly and a guest built against 1.x calling through the host's adapter onto the 2.0 implementation. µs per call direct 2.0 call 0.4 µs 1.x call via adapter 0.6 µs

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.

← Back to Wasm Component Model & WIT Bindings