Dependency Management in Cargo.toml
Learn how to handle Rust crate dependencies, feature flags, optional crates, and workspace settings inside the Cargo.toml manifest of a Tauri v2 project.
Every Tauri project ships a src-tauri/Cargo.toml file — the manifest that tells Cargo (Rust’s build system and package manager) which crates your backend depends on and how those crates should be compiled. The frontend lives in package.json; the backend lives here. Getting the dependency declarations right means your Rust code compiles, your plugins register correctly, and your final binary contains exactly the code it needs.
Cargo.toml vs tauri.conf.json:
The Cargo.toml file controls the Rust side (crates, features, compilation profiles). The tauri.conf.json file controls the Tauri runtime (window settings, bundle config, security). Both are essential, but they govern different layers.
Adding Dependencies
A dependency is any external crate your Rust code needs — a Tauri plugin, a serialisation library, a random number generator. In Tauri v2, the minimum you will always see are tauri and tauri-build. Adding others follows normal Cargo conventions.
You can add a dependency either with the cargo add command or by editing the file directly. The result is the same; pick whichever you prefer.
From the src-tauri directory, run the command. Cargo will update your Cargo.toml automatically.
cargo add serde --features derive
cargo add tauri-plugin-fs@2
After any manual edit, you must run cargo update inside src-tauri so Cargo resolves the new dependency and updates the lockfile. The lockfile (Cargo.lock) is the single source of truth for exact versions — commit it to version control.
Version mismatches break builds:
The tauri crate and the @tauri-apps/cli npm package must be on the same minor version. If you use Tauri plugins (like tauri-plugin-fs), the Rust crate and the matching JavaScript package must be exactly the same version. A mismatch often produces opaque runtime errors.
A version string like "2" means “any semver-compatible version in the 2.x.y range.” Cargo will resolve the latest patch. If you need an exact version — for reproducible CI builds — prefix it with =:
tauri-build = { version = "=2.0.0" }
Features
Rust crates can expose feature flags that enable or disable optional functionality. The tauri crate itself is feature-gated: you only compile the code for the capabilities your app actually uses. Features also control which system libraries get linked, affecting binary size and build time.
Common Tauri features include:
custom-protocol– serves frontend assets from a custom URI scheme instead of a localhost dev servertray-icon– enables the system tray APIcompression– compresses webview IPC messages (enabled by default)isolation– runs the webview in a separate iframe for security isolation
To enable a feature, list it in the tauri dependency’s features array:
[dependencies]
tauri = { version = "2", features = ["custom-protocol", "tray-icon"] }
The CLI can auto-manage some features:
When you run tauri dev or tauri build, the CLI inspects your tauri.conf.json and automatically enables certain features that your configuration requires — for example, the tray icon feature if you define a system tray. You still need to declare features for optional plugins or advanced capabilities, but the common ones are often handled for you.
A feature flag that you forget to enable but your code depends on will cause a compile error. For instance, calling tray API functions without the tray-icon feature produces a “function not found” message. Always check a plugin’s documentation for its required Cargo features.
Optional Dependencies
An optional dependency is a crate that is only compiled when a corresponding feature flag is turned on. This keeps your build lean: the crate is not downloaded or compiled unless needed, and you can conditionally compile your Rust code with #[cfg(feature = "...")].
This pattern is common for plugins that you want to enable per build or per platform. You declare the dependency with optional = true and then create a feature that pulls it in.
[dependencies]
tauri-plugin-deep-link = { version = "2", optional = true }
[features]
deep-link = ["tauri-plugin-deep-link"]
Now the plugin is only part of the build when the deep-link feature is requested. In your Rust source, you gate the registration:
fn main() {
tauri::Builder::default()
.setup(|app| {
#[cfg(feature = "deep-link")]
{
app.handle().plugin(tauri_plugin_deep_link::init())?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
To build with the feature, include it on the command line or in your default feature set:
cargo tauri build --features deep-link
Optional does not mean platform-conditional:
Optional dependencies are controlled by Cargo features, not by #[cfg(target_os)]. If you need a dependency only on a specific operating system, use platform-specific dependency sections: [target.'cfg(windows)'.dependencies]. You can combine both — an optional dependency that is also platform-gated — but the two mechanisms are distinct.
Workspace Configuration
When a Tauri app is part of a larger Rust project — for example, a monorepo with a shared utility crate — you define a Cargo workspace. The workspace lets multiple crates share dependency versions and compile together, avoiding duplicate crates in the target directory.
Setting up a workspace involves a root Cargo.toml and one or more member crates (your src-tauri and any library crates). Here is how you create one from scratch around an existing Tauri app.
Step 1: Create the root workspace manifest
In the project root (where your src-tauri folder lives), add a Cargo.toml that declares the workspace and its members.
[workspace]
members = ["src-tauri", "common-utils"]
resolver = "2"
The resolver = "2" is required for Tauri v2’s feature resolution.
Step 2: Add the shared library crate
Create a new library crate alongside src-tauri.
cargo new common-utils --lib
This crate can contain shared types, helpers, or database models. It will be a workspace member automatically because you listed it in the root manifest.
Step 3: Declare the dependency in the Tauri crate
In src-tauri/Cargo.toml, add the library crate as a dependency using a relative path. Cargo resolves it from the workspace.
[dependencies]
common-utils = { path = "../common-utils" }
You can now use common_utils::... in your Tauri backend code. The workspace ensures both crates are built with the same version of any shared transitive dependency.
You can also centralise version numbers so every workspace member inherits them. In the root manifest, define a [workspace.dependencies] section and reference it in members with version.workspace = true.
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
Then in src-tauri/Cargo.toml:
[dependencies]
serde = { workspace = true }
This keeps version updates in one place, reducing drift across crates.
Do not nest workspace members inside each other:
The Tauri crate (src-tauri) should be a direct member of the workspace, not a subdirectory of another member. Nesting leads to build confusion and duplicated compilation artifacts.
Managing and Updating Dependencies
Cargo resolves dependencies according to the version constraints in Cargo.toml and records exact versions in Cargo.lock. Running cargo update inside src-tauri refreshes the lockfile with the latest compatible versions.
To check for outdated crates, install and run cargo-outdated:
cargo install cargo-outdated
cargo outdated
When upgrading the tauri and tauri-build crates, change the version string in Cargo.toml and then run cargo update. The Tauri CLI and the Rust crates must stay in sync; a mismatch between @tauri-apps/cli (npm) and the tauri crate produces errors like “plugin not found” or “mismatched protocol”.
Commit Cargo.lock for applications:
For binaries (like a Tauri app), commit the lockfile. It guarantees every developer and CI build gets the exact same dependency tree. For libraries, you can omit it — but Tauri apps are always binaries.
Common Mistakes
A few recurring problems catch newcomers:
- Mixing major or minor versions. Tauri v2 plugins and the core crate release versions together. If
tauriis2.0.0andtauri-plugin-fsis2.1.0, that is usually fine. Buttauri2.x and a plugin still on 1.x will not work. - Leaving out required features. A plugin’s README will list the Cargo features it needs. Skipping one gives you a compile error like “cannot find function
init”. - Forgetting
cargo updateafter editing manually. Cargo only reads the lockfile; a new dependency line without a subsequent update leaves the crate unresolvable. - Using
pathdependencies without adding the member to the workspace. If you reference a local crate by path but it is not a workspace member, Cargo treats it as an external path dependency, which can cause feature resolution issues.
The silent build break:
If tauri-build is not present as a build dependency, your app may fail to compile with obscure errors about missing environment variables or code generation. The scaffolded src-tauri/Cargo.toml always includes it; never remove it unless you know exactly why.
Summary
Dependency management in Cargo.toml is the lever that controls what Rust code ends up in your final Tauri binary. The patterns you have seen — versioning, feature flags, optional crates, and workspace inheritance — all serve the same goal: include exactly what you need, no more, and keep versions coherent across the entire project.
A dependency declared here affects compile time, binary size, and the capabilities your backend can offer to the frontend.