Running WebAssembly (Wasm) Components From the Command Line
Wasmtime’s 33.0.0 release supports invoking Wasm component exports directly from the command line with the new --invoke
flag.
This article walks through building a Wasm component in Rust and using wasmtime run --invoke
to execute specific functions (enabling powerful workflows for scripting, testing, and integrating Wasm into modern development pipelines).
The Evolution of Wasmtime’s CLI
Wasmtime’s run
subcommand has traditionally supported running Wasm modules as well as invoking that module’s exported function. However, with the evolution of the Wasm Component Model, this article focuses on a newer capability; creating a component that exports a function and then demonstrating how to invoke that component’s exported function.
By the end of this article, you’ll be ready to create Wasm components and orchestrate their exported component functions to improve your workflow’s efficiency and promote reuse. Potential examples include:
- Shell Scripting: Embed Wasm logic directly into Bash or Python scripts for seamless automation.
- CI/CD Pipelines: Validate components in GitHub Actions, GitLab CI, or other automation tools without embedding them in host applications.
- Cross-Language Testing: Quickly verify that interfaces match across implementations in Rust, JavaScript, and Python.
- Debugging: Inspect exported functions during development with ease.
- Microservices: Chain components in serverless workflows, such as compress → encrypt → upload, leveraging Wasm’s modularity.
Tooling & Dependencies
If you want to follow along, please install:
- Rust (if you already have Rust installed, make sure you are on the latest version),
cargo
(if already installed, please make sure you are on the latest version),cargo component
(if already installed, please make sure you are on the latest version), andwasmtime
CLI (or use a precompiled binary). If already installed, ensure you are using v33.0.0 or newer.
You can check versions using the following commands:
$ rustc --version
$ cargo --version
$ cargo component --version
$ wasmtime --version
We must explicitly add
the wasm32-wasip2
target. This ensures that our component adheres to WASI’s system interface for non-browser environments (e.g., file system access, sockets, random etc.):
$ rustup target add wasm32-wasip2
Creating a New Wasm Component With Rust
Let’s start by creating a new Wasm library that we will later convert to a Wasm component using cargo component
and the wasm32-wasip2
target:
$ cargo component new --lib wasm_answer
$ cd wasm_answer
If you open the Cargo.toml
file, you will notice that the cargo component
command has automatically added some essential configurations.
The wit-bindgen-rt
dependency (with the ["bitflags"]
feature) under [dependencies]
, and the crate-type = ["cdylib"]
setting under the [lib]
section.
Your Cargo.toml
should now include these entries (as shown in the example below):
[package]
name = "wasm_answer"
version = "0.1.0"
edition = "2024"
[dependencies]
wit-bindgen-rt = { version = "0.41.0", features = ["bitflags"] }
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "component:wasm-answer"
[package.metadata.component.dependencies]
The directory structure of the wasm_answer
example is automatically scaffolded out for us by cargo component
:
$ tree wasm_answer
wasm_answer
├── Cargo.lock
├── Cargo.toml
├── src
│ ├── bindings.rs
│ └── lib.rs
└── wit
└── world.wit
WIT
If we open the wit/world.wit
file, that cargo component
created for us, we can see that cargo component
generates a minimal world.wit
that exports a raw function:
package component:wasm-answer;
/// An example world for the component to target.
world example {
export hello-world: func() -> string;
}
We can simply adjust the export
line (as shown below):
package component:wasm-answer;
/// An example world for the component to target.
world example {
export get-answer: func() -> u32;
}
But, instead, let’s use an interface to export our function!
While the above approach works, the recommended best practice is to wrap related functions inside an interface, which you then export from your world. This is more modular, extensible, and aligns with how the Wasm Interface Type (WIT) format is used in multi-function or real-world components. Let’s update the wit/world.wit
file as follows:
package component:wasm-answer;
interface answer {
get-answer: func() -> u32;
}
world example {
export answer;
}
Next, we update our src/lib.rs
file accordingly, by pasting in the following Rust code:
#[allow(warnings)]
mod bindings;
use bindings::exports::component::wasm_answer::answer::Guest;
struct Component;
impl Guest for Component {
fn get_answer() -> u32 {
42
}
}
bindings::export!(Component with_types_in bindings);
Now, let’s create the Wasm component with our exported get_answer()
function:
$ cargo component build --target wasm32-wasip2
Our newly generated .wasm
file now lives at the following location:
$ file target/wasm32-wasip2/debug/wasm_answer.wasm
target/wasm32-wasip2/debug/wasm_answer.wasm: WebAssembly (wasm) binary module version 0x1000d
We can also use the --release
option which optimises builds for production:
$ cargo component build --target wasm32-wasip2 --release
If we check the sizes of the debug
and release
, we see a difference of 2.1M
and 16K
, respectively.
Debug:
$ du -mh target/wasm32-wasip2/debug/wasm_answer.wasm
2.1M target/wasm32-wasip2/debug/wasm_answer.wasm
Release:
$ du -mh target/wasm32-wasip2/release/wasm_answer.wasm
16K target/wasm32-wasip2/release/wasm_answer.wasm
How Invoke Works: A Practical Example
The wasmtime run
command can take one positional argument and just run a .wasm
or .wat
file:
$ wasmtime run foo.wasm
$ wasmtime run foo.wat
Invoke: Wasm Modules
In the case of a Wasm module that exports a raw function directly, the run
command accepts an optional --invoke
argument, which is the name of an exported raw function (of the module) to run:
$ wasmtime run --invoke initialize foo.wasm
Invoke: Wasm Components
In the case of a Wasm component that uses typed interfaces (defined in WIT, in concert with the Component Model), the run
command now also accepts the optional --invoke
argument for calling an exported function of a component.
However, the calling of an exported function of a component uses WAVE(a human-oriented text encoding of Wasm Component Model values). For example:
$ wasmtime run --invoke 'initialize()' foo.wasm
You will notice the different syntax of
initialize
versus'initialize()'
when referring to a module versus a component, respectively.
Back to our get-answer()
example:
$ wasmtime run --invoke 'get-answer()' target/wasm32-wasip2/debug/wasm_answer.wasm
42
You will notice that the above get-answer()
function call does not pass in any arguments. Let’s discuss how to represent the arguments passed into function calls in a structured way (using WAVE).
Wasm Value Encoding (WAVE)
Transferring and invoking complex argument data via the command line is challenging, especially with Wasm components that use diverse value types. To simplify this, Wasm Value Encoding (WAVE) was introduced; offering a concise way to represent structured values directly in the CLI.
WAVE provides a standard way to encode function calls and/or results. WAVE is a human-oriented text encoding of Wasm Component Model values; designed to be consistent with the WIT IDL format.
Below are a few additional pointers for constructing your wasmtime run --invoke
commands using WAVE.
Quotes
As shown above, the component’s exported function name and mandatory parentheses are contained in one set of single quotes, i.e., 'get-answer()'
:
$ wasmtime run --invoke 'get-answer()' target/wasm32-wasip2/release/wasm_answer.wasm
The result from our correctly typed command above is as follows:
42
Parentheses
Parentheses after the exported function’s name are mandatory. The presence of the parenthesis ()
signifies function invocation, as opposed to the function name just being referenced. If your function takes a string argument, ensure that you contain your string in double quotes (inside the parentheses). For example:
$ wasmtime run --invoke 'initialize("hello")' foo.wasm
If your exported function takes more than one argument, ensure that each argument is separated using a single comma ,
as shown below:
$ wasmtime run --invoke 'initialize("Pi", 3.14)' foo.wasm
$ wasmtime run --invoke 'add(1, 2)' foo.wasm
Recap: Wasm Modules versus Wasm Components
Let’s wrap this article up with a recap to crystallize your knowledge.
Earlier Wasmtime Run Support for Modules
If we are not using the Component Model and just creating a module, we use a simple command like wasmtime run foo.wasm
(without WAVE syntax). This approach typically applies to modules, which export a _start
function, or reactor modules, which can optionally export the wasi:cli/run
interface—standardized to enable consistent execution semantics.
Example of running a Wasm module that exports a raw function directly:
$ wasmtime run --invoke initialize foo.wasm
Wasmtime Run Support for Components
As Wasm evolves with the Component Model, developers gain fine-grained control over component execution and composition. Components using WIT can now be run with wasmtime run
, using the optional --invoke
argument to call exported functions (with WAVE).
Example of running a Wasm component that exports a function:
$ wasmtime run --invoke 'add(1, 2)' foo.wasm
For more information, visit the cli-options section of the Wasmtime documentation.
Benefits and Usefulness
The addition of support for the run --invoke
feature for components allows users to specify and execute exported functions from a Wasm component. This enables greater flexibility for testing, debugging, and integration. We now have the ability to perform the execution of arbitrary exported functions directly from the command line, this feature opens up a world of possibilities for integrating Wasm into modern development pipelines.
This evolution from monolithic Wasm modules to composable, CLI-friendly components exemplifies the versatility and power of Wasm in real-world scenarios.