# wasmtime-dotnet embeds a Wasmtime engine in .NET, but the version numbers are Wasmtime's

> wasmtime-dotnet is the Bytecode Alliance's.NET binding for the Wasmtime WebAssembly runtime, exposing engines, stores, linkers and modules to C#. Its package versions track the Wasmtime version they embed, so v48.0.2 means Wasmtime 48.

**bytecodealliance/wasmtime-dotnet** — .NET embedding of Wasmtime https://bytecodealliance.github.io/wasmtime-dotnet/

- Repository: https://github.com/bytecodealliance/wasmtime-dotnet
- Stars: 522 · Forks: 71
- Language: C#
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/bytecodealliance-wasmtime-dotnet

## The package version is the runtime version, which is the first thing to understand

wasmtime-dotnet is the .NET embedding of Wasmtime, and it is a Bytecode Alliance project. The package is on NuGet as Wasmtime and the documentation is published separately.

The single most important thing to understand about this project is that its version numbers are Wasmtime's. The recent releases are v48.0.2 on 2026-09-18, v44.0.0 on 2026-05-23 and v34.0.2 on 2025-08-05. A package called Wasmtime 48.0.2 is not a 48th release of a .NET wrapper, it is the .NET binding for Wasmtime 48.0.2, with the .0.2 being the wrapper's own patch level.

That design has an obvious benefit. The .NET API and the runtime it embeds are developed together and released together, so there is no cross-version compatibility question between the binding and the engine underneath it. A v48.0.2 package contains Wasmtime 48.0.2.

It also means the upgrade cadence is the runtime's, and the version jumps are large. From v34 to v44 is ten upstream releases in under a year, and v44 to v48 is four more. If you pin a version, you are pinning a specific WebAssembly runtime with all of its behaviour changes, not just a library API.

The mechanism is visible in the build files. To update the Wasmtime library used for a new release, you change WasmtimeVersion in Directory.Build.props:

```xml
<WasmtimeVersion Condition="'$(WasmtimeVersion)'==''">$VERSION</WasmtimeVersion>
```

The condition makes it overridable from the command line, and the default value is the upstream version string. A separate property, and a separate native component, are what a .NET binding of a C runtime needs: the managed API and the native library it P/Invokes into.

For publishing, GitHub Actions automatically publishes a package to NuGet when a tag is pushed, the tag format is v$VERSION where $VERSION matches the Wasmtime version used by the .NET package, and the tagged commit should match the last commit made for the release.

## The API mirrors the WebAssembly concepts rather than hiding them

The introduction in the README works through a small module and shows the shape of the API, which is worth reading in full because it tells you the object model.

The example module imports a hello function and exports a run function, in WebAssembly text format:

```text
(module
  (func $hello (import "" "hello"))
  (func (export "run") (call $hello))
)
```

The C# side then mirrors that structure object for object. An Engine is created and then a WebAssembly module is loaded from a string in WebAssembly text format. A Linker defines a function called hello that simply prints a hello message. The module is instantiated and the instance's run export is invoked.

The types map cleanly onto the WebAssembly specification. Module.FromText takes the engine, a name, and the text, and returns a module. Linker.Define takes a module name, a field name, and a function, and it is where host functions are exposed to the guest. Function.FromCallback binds a .NET delegate to a store so the runtime can call back into managed code. Store holds the instance state, Linker.Instantiate produces the instance, and instance.GetAction("run") returns a delegate you can invoke.

That last detail, GetAction, is the ergonomics of the design. The export is surfaced as a .NET action, so calling into WebAssembly reads like calling a method. The type names, Module, Linker, Store, Function, Engine, are the WebAssembly specification's own vocabulary, which makes the binding easy to learn for anyone who has read the spec and easy to map onto other WebAssembly tooling.

The repository's examples directory shows the range of WebAssembly features the binding covers: component, consumefuel, externref, funcref, global, hello, memory, storedata and table. That list is a good map of the specification surface, and it is where you would look to see whether a particular feature is supported.

## A first run is a console project and one file

Getting a module running is a short sequence, and the README's steps are the ones to follow.

Add the package reference with the .NET SDK:

```text
$ dotnet add package wasmtime
```

Then create a new console project:

```
$ mkdir wasmintro
$ cd wasmintro
$ dotnet new console
```

And add the Wasmtime package to it, which the README points at the NuGet listing for:

```
$ dotnet add package wasmtime
```

The C# goes into Program.cs, and the whole file is short:

```csharp
using System;
using Wasmtime;

using var engine = new Engine();

using var module = Module.FromText(
    engine,
    "hello",
    "(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))"
);

using var linker = new Linker(engine);
using var store = new Store(engine);

linker.Define(
    "",
    "hello",
    Function.FromCallback(store, () => Console.WriteLine("Hello from C#!"))
);

var instance = linker.Instantiate(store, module);
var run = instance.GetAction("run")!;
run();
```

Then run it with dotnet, and it should print Hello from C#!:

```
$ dotnet run
```

Two things about the code are worth flagging. The using var declarations mean the engine, module, linker and store are disposed at the end of the scope, which matters because these wrap native resources. And the module is embedded as a C# string literal rather than loaded from a file, so the example has no file dependency at all; the same API loads from a file when you need one.

The function is registered under an empty module name, which matches the empty string in the import declaration in the WebAssembly text. That correspondence between the import namespace in the guest and the Define call in the host is the one piece of WebAssembly knowledge you need to get this working.

## Building from source pulls in a development snapshot of Wasmtime

The contributor workflow is documented in three short sections, and the build step has a consequence you should know about.

Building the repository uses dotnet:

```
$ dotnet build Wasmtime.sln
```

And this will download the latest development snapshot of Wasmtime for your platform. That is the important sentence. A source build does not use a released Wasmtime, it fetches the current development snapshot, so the native library your build links against is whatever upstream was at the moment of the build. That is how the binding stays current with the runtime between releases, and it is also why a source build can behave differently from a NuGet package.

Testing is the same shape:

```
$ dotnet test Wasmtime.sln
```

Creating the NuGet package is:

```
$ cd src
$ dotnet pack Wasmtime.sln -c Release /p:Packing=true
```

And that will create a .nupkg file in src/bin/Release. By default, local builds will use a -dev suffix for the package to differentiate between official packages and development packages. That suffix is the visible marker of the difference between what a maintainer builds locally and what a user installs from NuGet.

The repository layout explains the rest. There are three solution files: Wasmtime.sln for the main library, Examples.sln for the examples, and Benchmarks.sln for the benchmarks, with matching benchmarks/, docs/, examples/, src/ and tests/ directories. A Directory.Build.props at the root is where the shared build properties live, including the WasmtimeVersion pin, and there is a .config/ directory for the .NET tool configuration.

The split into three solutions is what keeps the published package clean, since only the main library belongs in the NuGet package, while the examples and benchmarks are built and tested separately.

## Where wasmtime-dotnet is the wrong tool

The first wrong-tool case is a project that needs a stable.NET API surface. Because the package version is the runtime version, an upgrade from v44.0.0 to v48.0.2 is a WebAssembly runtime upgrade, and any behaviour change in Wasmtime between those releases is in your process. If your team needs a package you can upgrade without auditing runtime semantics, that coupling is the wrong shape.

The second case is a project that only needs to run simple WebAssembly and does not want a native runtime. There are managed WebAssembly runtimes for.NET that avoid shipping a native library, which matters in environments where native code loading is restricted, in some serverless platforms, or where you want a single-file publish. Wasmtime is a native runtime, and the binding brings it with you.

The third case is a project that needs the full WebAssembly component model or newer specification features, and needs them at a specific version. The examples directory lists a component example, so components are in scope, but which features land in which Wasmtime version is decided upstream, and the binding inherits that schedule rather than setting its own.

For the alternative approach, the difference in approach is between a native runtime embedded in the host and a managed interpreter or a hosted runtime. Wasmtime is a native runtime, embedded into your.NET process as a native library, and the.NET API is a set of P/Invoke bindings over it with a managed object model on top. A managed WebAssembly runtime for.NET interprets or compiles the module in managed code, which means no native dependency and a different performance and memory profile, and which also means a different set of specification features supported.

The tradeoff in one line: if you need the runtime's exact semantics and its performance characteristics, embed it; if you need a self-contained managed deployment and your modules are simple, use a managed runtime. The other option worth naming is not running WebAssembly at all, if the boundary you are crossing is a plain function call.

## Apache-2.0, CI-published packages, and a native component you inherit

The licence is Apache-2.0, the standard choice for Bytecode Alliance projects, and the repository carries a LICENSE file at the top level.

The release mechanics are worth understanding because they explain the version numbers. GitHub Actions is used to automatically publish a package to NuGet when a tag is pushed to the repository. To publish a new release you create a release in GitHub and add the relevant release notes, use a tag of the format v$VERSION where $VERSION matches the Wasmtime version used by the .NET package, and ensure the tagged commit matches the last commit made for the release. When the release is published on GitHub, an action should automatically start to build and publish the package to NuGet.

The README also carries a CI status badge for a workflow named main.yml and a documentation badge pointing at the hosted docs site, so the build and the docs are both continuously checked.

The maintenance picture is healthy. The last push to the repository was on 2026-10-16, and the recent releases show a steady cadence through 2025 and 2026: v34.0.2 on 2025-08-05, v44.0.0 on 2026-05-23 and v48.0.2 on 2026-09-18.

The one ongoing cost to plan for is the native dependency. Your .NET application ends up carrying a native Wasmtime library, which means platform-specific builds, a native library to ship and to keep up to date, and a runtime that can crash the process rather than throw a managed exception. That is inherent to embedding Wasmtime rather than something this project could design away, and it is the main thing to weigh against the benefit of the real runtime.

The default branch is main, and the project is developed in the open with a standard contribution path, since the README's contributing section is exactly the build, test and package instructions above.

## Conclusion

wasmtime-dotnet suits a .NET team that needs to run WebAssembly modules in-process with a real runtime rather than a managed reimplementation, and who can track the Wasmtime version their package embeds. It does not suit a project that needs a stable .NET-only API, since the package version follows the upstream runtime. Before adopting, check which Wasmtime version the package you install embeds, because the WasmtimeVersion property in Directory.Build.props is what pins it, and confirm that the examples you need, such as component or externref, exist for that version.

## FAQ

### How do I add Wasmtime to a .NET project?

Run `dotnet add package wasmtime`, or add a package reference to the Wasmtime package with the .NET SDK. The package is published on NuGet by the Bytecode Alliance.

### What do the wasmtime-dotnet version numbers mean?

They track the embedded Wasmtime version, not the .NET wrapper's own history. v48.0.2 is the binding for Wasmtime 48.0.2, and the version is set through the WasmtimeVersion property in Directory.Build.props.

### How do I call a WebAssembly export from C#?

Instantiate the module with linker.Instantiate(store, module), then get the export with instance.GetAction("run") and invoke it. The README's example prints a message and the export is surfaced as a .NET action.

### What does building wasmtime-dotnet from source download?

The latest development snapshot of Wasmtime for your platform, rather than a released version. That is why local builds use a -dev suffix on the package, to tell them apart from official packages.

## Sources

- [bytecodealliance/wasmtime-dotnet on GitHub](https://github.com/bytecodealliance/wasmtime-dotnet)
- [Issues](https://github.com/bytecodealliance/wasmtime-dotnet/issues)
- [License: Apache-2.0](https://github.com/bytecodealliance/wasmtime-dotnet/blob/main/LICENSE)
- [README](https://github.com/bytecodealliance/wasmtime-dotnet/blob/main/README.md)
- [Releases](https://github.com/bytecodealliance/wasmtime-dotnet/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bytecodealliance-wasmtime-dotnet
