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.

  1. The Rust crate — added to src-tauri/Cargo.toml so the Rust compiler can find it.
  2. Registration in lib.rs — a .plugin() call inside the Tauri builder so the plugin's code actually runs.
  3. The JavaScript package — installed in your frontend so you can call plugin functions from React.
  4. 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-opener to src-tauri/Cargo.toml under [dependencies].
  • Inserts .plugin(tauri_plugin_opener::init()) into your lib.rs builder chain if it finds the Tauri builder pattern.
  • Installs @tauri-apps/plugin-opener into your frontend package.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.

1

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.

2

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.

src-tauri/src/lib.rs
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.

3

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.

4

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.

src-tauri/capabilities/default.json
{
  "$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.

src/App.tsx
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.