Version Compatibility

How to manage plugin and dependency versions to ensure your Tauri plugin works across different environments and Tauri releases.

A Tauri plugin exists in two pieces that must stay in lockstep: a Rust crate (the backend logic) and a JavaScript package (the frontend API). If the version of the Rust crate your app depends on does not match the JS package version, you get silent failures at best and panics at worst — often with error messages that do not immediately point to a version mismatch. Version compatibility is the discipline of choosing version ranges and update policies that keep these pieces, plus the Tauri core itself, aligned. Installing Plugins is the matching install path.

Plugin Components and Version Coordination

Every Tauri v2 plugin is split across two package registries. The Rust side lives on crates.io as something like tauri-plugin-example. The JavaScript side lives on npm as @tauri-apps/plugin-example. The plugin author is expected to publish matching versions of both. When a consumer installs the plugin, they must ensure the two sides are compatible with each other and with the version of Tauri the app uses.

For official plugins, the convention is straightforward: the Rust crate version equals the npm package version. A tauri-plugin-store v2.1.0 on crates.io corresponds exactly to @tauri-apps/plugin-store v2.1.0 on npm. If you are developing your own plugin, adopt the same rule. It eliminates an entire category of debugging.

Mismatched Plugin Versions:

An app that pulls in @tauri-apps/plugin-store@2.0.0 but tauri-plugin-store@2.1.0 on the Rust side will compile, but the IPC serialization formats may have diverged. Calls from the frontend will reach Rust command handlers that expect a different payload shape, and the error will surface as a generic “command not allowed” or a deserialization panic. There is no automated check that catches this at build time.

Dependency Version Specification in Cargo.toml

The Rust side declares its version relationship with Tauri and with any third‑party crates through Cargo.toml. The most common specification for a plugin author is a semver‑compatible range bound to Tauri’s major version.

[dependencies]
tauri = "2"
tauri-plugin-example = "2"

Both "2" are shorthand for >=2.0.0, <3.0.0. This means the plugin will accept any Tauri 2.x release without manual intervention. For a plugin author consuming a peer plugin, the same pattern works: tauri-plugin-fs = "2" will resolve to the latest 2.x crate.

If your plugin exposes its own public API and you want to give consumers a tighter compatibility window, use the caret (^) or tilde (~) operators explicitly.

[dependencies]
tauri = "^2.0.0"
serde = "^1"
serde_json = "^1"

The caret allows any version that does not change the leftmost non‑zero digit, which is the same as >=2.0.0, <3.0.0 for Tauri. For serde with a minor of zero, ^1.0.0 means >=1.0.0, <2.0.0 — again, a reasonable bound.

Avoid Wildcard Versions:

Do not use * or unbounded ranges. A dependency like tauri = "*" will happily accept Tauri 3.0.0 when it is released, which will contain breaking plugin API changes and your plugin will fail to compile. The same holds for >=2.0.0 without an upper bound.

NPM Package Versioning

The JavaScript side follows standard package.json semver rules. The key dependency to pin is the Tauri API package, which provides the invoke function and event primitives your plugin’s frontend code calls.

{
  "name": "@tauri-apps/plugin-example",
  "version": "2.1.0",
  "peerDependencies": {
    "@tauri-apps/api": "^2.0.0"
  }
}

By listing @tauri-apps/api as a peer dependency, the package signals to the consumer’s bundler that the API version must be resolved from the host application. This prevents two copies of the API from being loaded, which would break the internal IPC bridge.

For a user adding your plugin to their app, the package.json looks like this:

{
  "dependencies": {
    "@tauri-apps/api": "^2.0.0",
    "@tauri-apps/plugin-example": "^2.1.0"
  }
}

Here the caret on both packages ensures they stay within the 2.x line. Because the plugin itself declares @tauri-apps/api as a peer dependency, npm will warn if the host app’s version does not satisfy it.

Matching Tauri Core Versions

The single most consequential version anchor is the tauri crate. Every plugin that calls tauri::plugin::Builder::new or implements the Plugin trait directly depends on it. If the consuming application uses a different semver‑incompatible tauri version, Cargo will refuse to unify the dependency graph and the build will fail with a version resolution error.

A plugin author should declare the Tauri dependency as broadly as practical while respecting the plugin’s actual tested range:

[dependencies]
tauri = ">=2.0.0, <3"

This range is identical to "2". If your plugin uses an API introduced in Tauri 2.1.0 (for example, a method on AppHandle that did not exist in 2.0.0), adjust the lower bound:

tauri = ">=2.1.0, <3"

When the consumer’s app is on Tauri 2.0.x, Cargo will report that my-plugin requires tauri >=2.1.0 and the resolution fails. That error message is infinitely better than a runtime crash from an undefined symbol.

Using the build-time check:

In your plugin’s build script or in a const assertion, you can optionally embed a static check that compares the tauri version at compile time. The tauri::VERSION constant gives you the exact version string. Compare it with a minimum required version using the semver crate to emit a compile error with a clear message.

Handling Platform-Specific Dependencies

Some plugins wrap native system libraries — for example, a keyring plugin that calls libsecret on Linux, or a database plugin that links against libsqlite3. These system dependencies have their own version constraints that differ across distributions.

On Linux, Tauri v2 depends on webkit2gtk-4.1, which itself requires glib ≥ 2.70 and libsoup3. This raised the minimum supported distribution baseline compared to Tauri v1. If your plugin uses GTK or GLib bindings directly, you inherit that same constraint. Declare it in your plugin’s documentation with a clear “System Requirements” section.

For a plugin that needs a specific dynamic library version, check at build time with a build.rs script:

fn main() {
    // Verify that libfoo >= 1.2 is available
    pkg_config::Config::new()
        .atleast_version("1.2")
        .probe("foo")
        .expect("libfoo >= 1.2 not found. Install it with your package manager.");
    tauri_build::build();
}

If the library is missing or too old, the build halts with a message the developer can act on. This is far more helpful than a linker error halfway through compilation.

Verifying Compatibility

A plugin developer should verify compatibility against at least two versions of Tauri: the minimum declared version and the latest stable release. Use a combination of the Tauri CLI’s info command and Cargo’s resolution tools.

cargo tree -i tauri

This prints every crate in the dependency graph that depends on tauri, along with the resolved version. If you see multiple versions of tauri (for example, tauri 2.0.1 and tauri 2.1.0 both listed), Cargo managed to unify them but the duplication indicates a version constraint mismatch somewhere. Investigate which crate pulled in the older version and tighten its requirements.

Clean Dependency Tree:

When cargo tree -i tauri shows a single tauri 2.x.y and tauri info reports a consistent plugin version in the “Plugins” section, your version alignment is correct. This is the state you want before releasing a plugin or shipping an application.

Common Pitfalls

Forgetting to update the JS package when the Rust API changes

A common workflow during plugin development is to add a new Rust command, bump the crate version, and publish — while the JS wrapper still exposes the old function signature or lacks the new command entirely. The app then gets the updated Rust code but the old JS glue, causing command not allowed errors or TypeError: plugin.command is not a function.

Automate the publishing step so both sides get released together from a single script, and embed a version check in the JS bundle that compares against the expected plugin version returned by Rust.

Lockfile drift in CI

Continuous integration pipelines that cache Cargo.lock and package-lock.json can mask incompatibilities. If a contributor updates Cargo.toml but the lockfile is not regenerated, the CI passes with an old resolved version. Always run cargo update and npm ci (or equivalent) in a clean environment before running tests.

Linux baseline mismatches

A plugin that compiles on your Ubuntu 24.04 machine may fail on a CentOS 9 system because glib is too old for webkit2gtk-4.1. This is not a Rust version issue but a system library issue. If you intend to support older enterprise distributions, document that your plugin requires a containerized build or a Flatpak runtime that ships the necessary libraries.

Libsoup3 and glib 2.70:

Tauri v2 moved from webkit2gtk-4.0 (libsoup2) to webkit2gtk-4.1 (libsoup3). The latter needs glib ≥ 2.70. Distributions with older glib (RHEL 9 derivatives, Ubuntu 20.04) cannot run or build Tauri v2 applications natively without additional work. Plugins that interact with the WebKit stack are subject to the same constraint.

Summary

Version compatibility for a Tauri plugin reduces to one rule: keep the Rust crate, the JS package, and the consuming app’s Tauri core on the same semantic major version. Use caret ranges in Cargo.toml and package.json, avoid unbounded specifiers, and enforce minimum system library requirements in your build scripts. When the dependency tree is clean and both sides of the plugin carry identical version numbers, the runtime is predictable and the debugging surface shrinks to nearly nothing.