From wasm
Guides writing WIT files for WebAssembly Component Model: defining packages, interfaces, worlds, types; mapping to Rust, Go, Python, JS; using cargo-component, wit-bindgen.
npx claudepluginhub vinnie357/claude-skills --plugin wasmThis skill uses the workspace's default tool permissions.
WIT is the Interface Definition Language (IDL) for the WebAssembly Component Model. It defines typed contracts between components in a language-agnostic way, enabling interoperability across programming languages at the wasm boundary.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
WIT is the Interface Definition Language (IDL) for the WebAssembly Component Model. It defines typed contracts between components in a language-agnostic way, enabling interoperability across programming languages at the wasm boundary.
See also: wasmtime skill for runtime embedding and compilation details.
Activate when:
.wit files to define component interfacescargo-component, wit-bindgen, or wasm-toolsA WIT file contains one or more of: package declaration, interfaces, worlds, and use declarations.
package namespace:package-name@1.0.0;
use wasi:io/streams@0.2.0.{input-stream, output-stream};
interface my-interface {
// types and functions
}
world my-world {
import my-interface;
export my-interface;
}
Every WIT file belongs to a package. The package declaration names the namespace, package, and optional semver version.
package my-org:my-lib@0.1.0;
namespace: organization or project identifier (kebab-case)package: library name (kebab-case)@version: optional semver stringPackage names use kebab-case identifiers. Dots are not allowed in identifiers; use hyphens.
WIT identifiers use kebab-case. Reserved words may be used as identifiers when prefixed with %:
interface example {
// 'type' is reserved — prefix with %
get-type: func() -> %type;
type %type = string;
}
An interface groups related types and functions into a named, reusable unit.
interface geometry {
record point {
x: f64,
y: f64,
}
distance: func(a: point, b: point) -> f64;
translate: func(p: point, dx: f64, dy: f64) -> point;
}
Interfaces can be imported by worlds or by other interfaces using use.
A world defines a complete component contract: what it imports (needs) and what it exports (provides). Worlds serve a dual role — they describe a component's requirements AND define the hosting environment that runs it. The only ways a component can interact with anything outside itself are by having its exports called or by calling its imports. A component cannot access resources it does not explicitly import, providing strong sandboxing boundaries.
world image-processor {
// Imports: capabilities the component requires from the host
import wasi:filesystem/preopens@0.2.0;
import log: func(msg: string);
// Exports: capabilities the component provides to callers
export process: func(input: list<u8>) -> result<list<u8>, string>;
export geometry;
}
| Item | Syntax | Purpose |
|---|---|---|
| Import interface | import name: interface { ... } | Inline imported interface |
| Import named | import wasi:io/streams@0.2.0; | Import by package path |
| Import function | import log: func(msg: string); | Single function import |
| Import type | import wasi:io/streams.{input-stream}; | Import specific types |
| Export interface | export my-interface; | Export a named interface |
| Export function | export run: func(); | Export a single function |
| Include world | include other-world; | Inherit another world's items |
Worlds can include other worlds to inherit their imports and exports:
world base {
import wasi:cli/environment@0.2.0;
}
world extended {
include base;
export my-app: func();
}
The with clause renames included items to avoid conflicts:
world combined {
include world-a with { run as run-a };
include world-b with { run as run-b };
}
Full reference: syntax-reference.md
| Type | Description |
|---|---|
bool | Boolean |
u8, u16, u32, u64 | Unsigned integers |
s8, s16, s32, s64 | Signed integers |
f32, f64 | IEEE 754 floats |
char | Unicode scalar value |
string | UTF-8 string |
interface types {
// List: variable-length sequence
type byte-array = list<u8>;
// Option: nullable value
type maybe-string = option<string>;
// Result: success or error
type parse-result = result<u32, string>;
type io-result = result<_, string>; // ok with no payload
type check-result = result; // both ok and err have no payload
// Tuple: fixed-length heterogeneous sequence
type pair = tuple<string, u32>;
}
Named field structs — equivalent to a struct in most languages.
record http-request {
method: string,
url: string,
headers: list<tuple<string, string>>,
body: option<list<u8>>,
}
Tagged unions where each case may carry a payload.
variant ip-address {
ipv4(tuple<u8, u8, u8, u8>),
ipv6(string),
}
variant error-kind {
not-found,
permission-denied(string),
timeout(u32),
unknown,
}
Variants without payloads — a simple discriminant.
enum color {
red,
green,
blue,
}
enum log-level {
trace,
debug,
info,
warn,
error,
}
Bit-flag sets where multiple values can be active simultaneously.
flags permissions {
read,
write,
execute,
}
// Usage: a value can hold any combination of these flags
type bytes = list<u8>;
type error-message = string;
Functions are defined in interfaces with named parameters and return types.
interface math {
// No return value
reset: func();
// Single return
add: func(a: s32, b: s32) -> s32;
// Named returns (multiple values)
div-rem: func(num: s32, denom: s32) -> (quotient: s32, remainder: s32);
// Result return
parse-int: func(s: string) -> result<s32, string>;
// Option return
find: func(haystack: list<string>, needle: string) -> option<u32>;
}
Named return values appear as a named tuple: -> (name: type, ...).
Resources represent opaque handles to objects with identity — equivalent to objects or handles in host languages.
resource file-handle {
// Constructor: creates a new resource instance
constructor(path: string, mode: open-mode);
// Regular methods: take `self` implicitly
read: func(max-bytes: u32) -> result<list<u8>, io-error>;
write: func(data: list<u8>) -> result<u32, io-error>;
flush: func() -> result<_, io-error>;
// Static method: no implicit self
exists: static func(path: string) -> bool;
}
Resource instances are automatically dropped when the handle goes out of scope in the host language. The Component Model tracks ownership.
Resources can also be used as plain types in interfaces:
interface storage {
resource blob {
constructor(data: list<u8>);
size: func() -> u64;
slice: func(start: u64, end: u64) -> blob;
}
store: func(key: string, value: borrow<blob>) -> result<_, string>;
load: func(key: string) -> result<blob, string>;
}
borrow<T> passes a resource by borrowed reference (no ownership transfer). Without borrow, the resource is moved (owned transfer).
Import types or interfaces from other packages or from within the same package.
// Import specific types from another package
use wasi:io/streams@0.2.0.{input-stream, output-stream};
// Import an entire interface
use wasi:filesystem/types@0.2.0;
// Alias an imported type
use wasi:clocks/wall-clock@0.2.0.{datetime as wall-datetime};
Use declarations appear at the top of an interface or world, before other items.
// Single-line comment
/*
Multi-line comment
*/
/// Documentation comment (attached to the next item)
/// Appears in generated bindings as doc comments.
interface documented {
/// Returns the current UTC timestamp in seconds.
now: func() -> u64;
}
Build Rust components targeting the Component Model:
# Install
cargo install cargo-component
# Create a new component project
cargo component new my-component --lib
# Build
cargo component build
# Build for release
cargo component build --release
The generated Cargo.toml references WIT files via the [package.metadata.component] section:
[package.metadata.component]
package = "my-org:my-component"
Generate language bindings from WIT files:
# Install CLI
cargo install wit-bindgen-cli
# Generate Rust bindings
wit-bindgen rust wit/ --out-dir src/bindings
# Generate C bindings
wit-bindgen c wit/ --out-dir include/
In Rust guest code, use the macro:
wit_bindgen::generate!({
world: "my-world",
path: "wit/",
});
Inspect and manipulate WIT and wasm binaries:
# Validate a WIT package
wasm-tools component wit wit/
# Extract WIT from a compiled component
wasm-tools component wit component.wasm
# Compose components
wasm-tools compose -d dependency.wasm main.wasm -o composed.wasm
# Validate a component
wasm-tools validate --features component-model component.wasm
package my-app:plugin@0.1.0;
world plugin {
// Host provides these to the plugin
import log: func(level: string, msg: string);
import config: func(key: string) -> option<string>;
// Plugin must provide these
export init: func() -> result<_, string>;
export process: func(input: list<u8>) -> result<list<u8>, string>;
export shutdown: func();
}
package my-org:database@1.0.0;
interface db {
resource connection {
constructor(url: string) -> result<connection, string>;
query: func(sql: string, params: list<string>) -> result<list<list<string>>, string>;
close: func();
}
resource transaction {
begin: static func(conn: borrow<connection>) -> result<transaction, string>;
commit: func() -> result<_, string>;
rollback: func() -> result<_, string>;
}
}
world database-client {
import db;
}
package my-org:types@0.1.0;
interface common {
record timestamp {
seconds: u64,
nanos: u32,
}
variant status {
ok,
error(string),
pending,
}
}
Then in another package:
package my-org:service@0.1.0;
use my-org:types/common@0.1.0.{timestamp, status};
interface service {
get-status: func() -> status;
last-updated: func() -> timestamp;
}
my-function, not myFunction or my_functionwasi:io/streams, not wasi.io.streams; dots are not valid in namesresult<_, E> for ok-unit, result<T, _> for err-unit, result for both unitborrow<resource-type> when not transferring ownership; plain resource-type transfers ownership@0.2.0 for all WASI imports — this is the first stable WASI release and the recommended target for new components@version when referencing external packages to ensure deterministic resolutionimport wasi:io/streams@0.2.0 imports the streams interface from the wasi:io packagetype bytes = list<u8>;