Installing Plugins
Step-by-step guide to add official and community Tauri v2 plugins to your React + Vite project, covering Rust dependencies, JavaScript bindings, permissions, version sync, and safe updates
Tauri plugins ship in two pieces: a Rust crate that runs native code and an npm package that exposes typed JavaScript functions to your React frontend. Installing a plugin means bringing both halves into your project, registering them in the right places, and granting the permissions each command needs to run.
If you skip any one of these pieces, the plugin will either fail to compile, fail to start, or silently refuse to work at runtime. The good news is that the whole process is now wrapped into a single CLI command for most official plugins — but knowing exactly what that command does behind the scenes will keep you out of hours of debugging when something goes wrong.
What you will learn:
By the end of this guide, you will know how to install a Tauri plugin automatically with the CLI, perform each step manually, keep Rust and JavaScript versions aligned, and safely upgrade plugins across releases.
The four things every plugin needs
Regardless of which plugin you choose, getting it into your app always comes down to the same four steps. Missing any one will break the plugin, often with an error message that doesn't immediately point to the root cause.
- The Rust crate — added to
src-tauri/Cargo.tomlso the Rust compiler can find it. - Registration in
lib.rs— a.plugin()call inside the Tauri builder so the plugin's code actually runs. - The JavaScript package — installed in your frontend so you can call plugin functions from React.
- Permission declarations — entries in
src-tauri/capabilities/that tell Tauri which commands the webview is allowed to invoke. The plugin permissions page is the configuration-side companion to this step.
A Tauri v2 app will never let JavaScript call a plugin command that hasn't been explicitly permitted. This is the single most common source of "plugin not working" reports.
Permission errors are blocking, not advisory:
If a command lacks a matching permission in the active capability file, Tauri rejects the call before it ever reaches Rust. You will see an error like dialog.open not allowed. Permissions associated with this command: dialog:allow-open in the browser console. The fix is always to add the right permission string, not to restart or rebuild anything else.
Automatic installation with the Tauri CLI
The recommended path for any official plugin is a single command run from the project root. It detects your package manager, adds the Rust crate, installs the npm bindings, and often updates the plugin registration in lib.rs.
npm run tauri add opener
What this command does:
- Adds
tauri-plugin-openertosrc-tauri/Cargo.tomlunder[dependencies]. - Inserts
.plugin(tauri_plugin_opener::init())into yourlib.rsbuilder chain if it finds the Tauri builder pattern. - Installs
@tauri-apps/plugin-openerinto your frontendpackage.json. - Does not modify capability files — you must still add permissions manually.
After the command finishes, you still need to open src-tauri/capabilities/default.json (or whichever capability file targets your main window) and declare what the plugin is allowed to do. This is not optional. Configuring Plugins covers the tauri.conf.json side of the same setup.
Manual installation step by step
If the CLI add command doesn't fit your workflow, or you're troubleshooting a misbehaving plugin, you can perform each step by hand. The following procedure uses the opener plugin as a concrete example, but the pattern is identical for any official or community plugin.
Step 1: Add the Rust crate
In the src-tauri directory, add the plugin crate using Cargo:
cargo add tauri-plugin-opener
This inserts a line like tauri-plugin-opener = "2" into src-tauri/Cargo.toml. The version will default to the latest stable release matching your Tauri major version.
Tauri v2 plugins use major version 2:
All official plugins that work with Tauri v2 have a major version of 2.x. A crate named tauri-plugin-* with version 1.x targets Tauri v1 and will cause compilation errors in a v2 project.
Step 2: Register the plugin in lib.rs
Open src-tauri/src/lib.rs. Locate the tauri::Builder::default() chain and call .plugin() with the plugin's init function. If your project was created from the standard Tauri template, the builder already lives inside a run() function.
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) // ← add this line
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
If lib.rs doesn't exist because your project uses a single main.rs, place the plugin call there instead. But the default React + Vite template always uses lib.rs.
Don't register in both lib.rs and main.rs:
The default Tauri v2 template has a thin main.rs that calls lib::run(). All plugin registrations belong in lib.rs. Adding them to main.rs duplicates initialization and can cause subtle state conflicts.
Step 3: Install the JavaScript bindings
From the project root (where package.json lives), install the npm package that pairs with the Rust crate:
npm install @tauri-apps/plugin-opener
This gives your React components access to typed functions like openPath() and openUrl() that communicate with the Rust backend through Tauri's IPC layer.
Step 4: Grant permissions in the capability file
Open src-tauri/capabilities/default.json. Inside the "permissions" array, add the permission identifiers for the commands you intend to use. The opener plugin offers opener:allow-open-path, opener:allow-open-url, and opener:allow-reveal-item-in-dir, among others.
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:allow-open-path",
"opener:allow-open-url"
]
}
Each permission can optionally restrict what paths or URLs the command may act on, which is covered in depth in the plugin's own documentation. For initial setup, adding the bare permission identifier is enough to unblock the command.
No permissions = silent failure at runtime:
Leaving this file untouched is the number one reason a freshly installed plugin appears to do nothing. The frontend will throw an unhandled promise rejection, and the Rust side will never receive the command. Always check the browser console for permission-related error messages.
Synchronizing versions between Rust and JavaScript
Every official Tauri plugin publishes its Rust crate and its npm package in lockstep. When you install them independently, they can drift. A mismatch — Rust side on 2.1.0 and JavaScript side on 2.0.0 — usually manifests as broken serialization, missing command names, or confusing runtime errors that don't look version-related. Version Compatibility goes deeper on keeping those ranges aligned.
The simplest way to verify alignment is to list the installed plugin versions from both ecosystems and compare them:
# Check Rust crate versions
cargo tree -p tauri-plugin-opener --depth 0
# Check npm package version
npm list @tauri-apps/plugin-opener
Both should report the same minor and patch version. The Tauri CLI can also give you a full picture of your plugin stack:
npm run tauri info
The output includes a Plugins section listing every registered Rust plugin and its version. Cross-reference this with your package.json to catch drift early.
Tauri and plugin versions must share the same major:
All v2 plugins require Tauri v2.x. If you see a plugin at version 1.x or you're getting compilation errors about missing traits, check that your tauri crate and the plugin both use major version 2. Downgrading a plugin to match an older Tauri version will not work — upgrade Tauri instead.
Updating plugins
Plugins receive bug fixes, new features, and breaking changes just like any other dependency. Updating them safely means moving both halves at the same time.
To bump a single plugin to its latest compatible version:
# Rust side
cargo update -p tauri-plugin-opener
# JavaScript side
npm update @tauri-apps/plugin-opener
After updating, always rebuild completely and run the app in development mode to smoke-test the commands you rely on. If the plugin's changelog mentions new permissions or breaking API changes, revisit your capability file and your React code before shipping.
For a full project-wide update that bumps all Tauri crates and plugins together, you can use:
cargo update
npm update
But this is best done intentionally and paired with a review of each plugin's release notes — a batch update can silently pull in a breaking change that only reveals itself when a user tries a specific feature.
Confirm everything works after an update:
After any plugin update, run npm run tauri dev and exercise at least one command from each installed plugin. If the command completes without a permission rejection in the console and produces the expected output, your update was clean. Writing a quick smoke test in your React app — even a button that calls a plugin function — pays for itself the first time a broken update would have reached production.
Verifying the installation end-to-end
Once all four pieces are in place, you can confirm the plugin is operational with a minimal React component. This test opens a URL in the default browser — a good sanity check because it exercises the full chain: permission check, Rust command execution, and OS integration.
import { openUrl } from "@tauri-apps/plugin-opener";
function App() {
const handleOpen = async () => {
try {
await openUrl("https://v2.tauri.app");
console.log("Opener plugin is working correctly");
} catch (error) {
console.error("Opener plugin failed:", error);
}
};
return (
<div>
<h1>Tauri Plugin Test</h1>
<button onClick={handleOpen}>Open Tauri Docs</button>
</div>
);
}
export default App;
When you click the button and the URL opens in your default browser, you've confirmed that the Rust crate is linked, the JavaScript package is callable, and the permission is correctly set. If nothing happens, check the browser console: a permission rejection prints the exact identifier you're missing.
The same verification pattern applies to any plugin: import its function, call it inside a try-catch, and observe the result. Doing this for every plugin immediately after installation saves you from discovering a broken setup days later in a more complex feature.