Skip to main content
Version: v2

Managing WIT Dependencies

Every component project depends on WIT interface definitions: the WASI and wasmCloud packages your component imports and exports, and any custom interfaces your components use to talk to each other. This page covers how those dependencies work and how to manage them with wash: where packages come from, how to lay out a project, how to point a dependency at a local directory, and how to publish interfaces of your own.

WIT packages are OCI artifacts

A WIT package is a set of interface definitions with a namespace and a name, like wasi:http or wasmcloud:messaging. Packages are encoded as WebAssembly binaries and distributed through OCI registries, the same infrastructure that stores your container images and components. There is no separate package manager infrastructure to stand up: if you have a registry, you can host WIT packages.

wasmCloud's first-party packages are published to ghcr.io/wasmcloud/interfaces/<name>:<version>, and the WASI packages resolve from the ecosystem's default registry mappings. When you run a fetch, wash resolves each package your world references and downloads it from the appropriate registry. See Registries for the full publishing details.

WIT dependency flow: wash wit fetch pulls packages from an OCI registry into the project&#39;s wit/ directory (world.wit, gitignored deps/, committed wkg.lock); bindings are generated by the language toolchain and built into the component; a wit.sources override can replace the registry with a local directory for one package, and wash wit build plus wash oci push publish the wit/ directory&#39;s package back to the registry

The tooling is built into wash

wash bundles the wkg WebAssembly package manager as a library, so there is no separate tool to install. The wash wit subcommands cover the lifecycle:

CommandWhat it does
wash wit fetchResolves the imports in wit/world.wit and downloads them to wit/deps/, writing wkg.lock
wash wit add wasi:keyvalue/storeAdds an interface import to your world, validating the reference first (a bare package name lists its interfaces)
wash wit remove wasi:keyvalueRemoves an interface or package from the world, drops it from wit/deps/, and re-fetches
wash wit updateUpdates dependencies to the latest compatible versions and reports what moved
wash wit buildBuilds your WIT package into a distributable Wasm binary
wash wit cleanRemoves fetched dependencies

wash build runs the fetch automatically, so day to day you rarely run these by hand.

Two conventions to follow in every project:

  • Commit wkg.lock. It pins the exact versions your build resolved, making builds reproducible.
  • Add wit/deps/ to .gitignore. These are fetched artifacts, like node_modules or target.

The wasi, wasmcloud, wrpc, and ba namespaces resolve automatically with no configuration. For other namespaces, map the package to a registry with the wit.sources project configuration; wit.registries supplies credentials when a registry is private.

Compatible with wkg

If you already use wkg directly, wash interoperates: both share the same underlying crate, honor the same configuration files, and produce the same wkg.lock. You do not need wkg installed to use wasmCloud, and mixing the two in one project is safe.

Project layout: one top-level wit/ directory

Keep all of a project's WIT in a single wit/ directory at the repository root, even when the project builds several components. This is the convention wasmCloud recommends:

my-project/
├── wit/
│   ├── world.wit        # Worlds for every component in the project
│   └── deps/            # Fetched dependencies (gitignored)
├── api/                 # Component: HTTP API
│   ├── src/
│   ├── wkg.lock         # Committed
│   └── .wash/
│       └── config.yaml  # wit.wit_dir: ../wit
└── worker/              # Component: background worker
    ├── src/
    ├── wkg.lock         # Committed
    └── .wash/
        └── config.yaml  # wit.wit_dir: ../wit

Each component is its own wash project, and each points its wit.wit_dir at the shared top-level directory. Interface definitions live in one place with no duplication between components: when the API imports what the worker exports, that interface is defined once and both worlds reference it. Define one world per component and select it in each component's bindings step (wit_bindgen::generate!(world: ...) in Rust, -wit-world with TinyGo).

The tradeoff to know about: a Rust crate published to crates.io only packages its own directory, so a published library crate is compiled from source on its consumers' machines, so its wit_bindgen::generate! invocation cannot rely on WIT that lives above the crate in the repository. For publishable library crates, either keep that crate's WIT inside the crate directory or commit the generated bindings instead of generating at build time. This affects published libraries, not applications.

Local file references

Dependencies do not have to come from a registry. The wit.sources map in project configuration overrides where a package resolves from, and it accepts absolute local paths as of wasmCloud 2.8.0 (alongside HTTP, Git, and OCI references):

yaml
wit:
  sources:
    "myorg:shared-api": "/home/dev/shared-api/wit"

With this override, wash wit fetch reads myorg:shared-api from the local directory instead of a registry. Local references are the right tool whenever the interface and its consumers are developed together:

  • Iterating on an interface and a component side by side. Change the WIT, re-fetch, rebuild. No publish step in the loop.
  • Multiple repositories sharing an unpublished interface. Point each consumer at a checkout of the interface repository until the package is published.
  • Testing interface changes before release. Override a published package with a local working copy to validate consumers against the next version.

The override changes only where the package comes from. The world syntax, the fetch workflow, and the lock file all work the same, so moving from a local path to a published package later is a one-line change.

Publishing your own interfaces

When an interface needs to be shared beyond a single repository, publish it as a package.

wash wit build encodes your WIT package as a Wasm binary named <package>-<version>.wasm:

shell
wash wit build

wash oci push publishes it to any OCI registry you can write to (see Registries for authentication).

shell
wash oci push ghcr.io/myorg/shared-api:0.1.0 ./shared-api-0.1.0.wasm

Push to a repository path ending in <namespace>/<package> (here myorg/shared-api), with the tag matching the package's declared version. Consumers then resolve it with a wit.sources entry mapping the package to the registry path, plus a wit.registries entry only if credentials are needed.

Version WIT packages like an API: publish a new version for breaking changes and keep old versions available, since deployed components reference the version they were built against.

Keep reading