Common Plugin Errors

How to diagnose and fix the most frequent issues when integrating Tauri v2 plugins in a React and Vite application

Tauri plugins add new capabilities to your app, but they require careful wiring across Rust, the frontend, and configuration files. When any piece of that wiring is missing, errors surface in ways that can be confusing. This page covers the most common plugin-related errors, explains exactly why they occur, and shows how to resolve them — with examples drawn from real Tauri v2 setups that use React and Vite. Related troubleshooting also lives under Plugin Errors.

Error: Plugin Commands Not Accessible

The frontend tries to call a plugin command but receives an error like Connection failed: plugin-name.command not allowed. Plugin not found. The command is defined in the plugin’s Rust crate, but Tauri’s runtime does not know it exists.

In Tauri v2 every plugin must declare its commands at compile time through the build.rs script. Without this registration, the frontend can never reach those commands — no matter how correctly the plugin’s init() function is called in lib.rs or how many permissions are granted.

Mandatory build.rs registration:

Skipping this step is the single most common cause of "Plugin not found" errors in custom plugins. No runtime configuration can substitute for it.

To fix it, open src-tauri/build.rs and use tauri_build::Attributes to register each command the plugin exposes. For a plugin named my-plugin with commands ping and status:

src-tauri/build.rs
fn main() {
    tauri_build::try_build(
        tauri_build::Attributes::new()
            .plugin(
                "my-plugin",
                tauri_build::InlinedPlugin::new()
                    .commands(&["ping", "status"]),
            ),
    )
    .expect("failed to run tauri-build");
}

The plugin name must match the string passed to Builder::new when creating the plugin in Rust. The command names are the literal identifiers used with #[tauri::command]. After adding this, rebuild the project — a full cargo clean is not required, but a cargo build will pick up the change.

For official plugins from the Tauri ecosystem, build.rs registration is handled automatically when you include the crate as a dependency. You only need to worry about this for plugins you create yourself or copy from outside the official workspace.

Error: Command Not Allowed Due to Missing Permissions

Even after a plugin is properly registered, the frontend may see command not allowed or a similar permission denial. Tauri v2 enforces an access control list (ACL) on every command, and no command is accessible unless explicitly permitted in a capability file.

This error usually appears as command not allowed: plugin-name.command in the developer console. It means the command exists but the current window’s capability does not grant access to it.

There are two ways to assign permissions — file-based capabilities or dynamic capabilities injected in Rust. You only need one approach for a given plugin, and both are shown here because the choice depends on how you manage configuration.

The capability JSON files live in src-tauri/capabilities/. For a plugin with a ping command, add a permission entry referencing the plugin’s identifier:

src-tauri/capabilities/default.json
{
  "identifier": "main-capability",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "my-plugin:allow-ping"
  ]
}

The permission string follows the pattern plugin-name:allow-command-name. Official plugins list their exact permission identifiers in their documentation.

After changing capabilities, restart the development server — capability files are read at startup. If the permission string is misspelled or references a command the plugin does not define, the error persists.

Permissions only apply to the windows listed:

A capability’s windows array limits which webview windows receive those permissions. If your app has multiple windows and the command is called from one not listed, it will be denied even if the permission entry is correct.

Error: Compilation Failures from Version Mismatch

Tauri plugins depend on the Tauri core crate internally. When the plugin expects a newer version of tauri than the one in your Cargo.toml, Rust compilation fails with errors like “no method named request_restart found for struct AppHandle” or similar missing symbols.

This happens because the plugin uses APIs that were introduced in a later Tauri release. The specific error message varies, but it always points to a method or trait that cannot be resolved.

The fix is to update the tauri crate in src-tauri/Cargo.toml to a version compatible with the plugin. Plugins usually document their minimum Tauri version, but a practical rule is to keep the core crate and all official plugins on the same minor version. For example:

src-tauri/Cargo.toml
[dependencies]
tauri = "2.5.0"
tauri-plugin-process = "2.2.1"

If you see compilation errors after adding a plugin, run cargo update to ensure the lockfile resolves consistently. In some cases you may need to delete Cargo.lock entirely and rebuild.

Delete the lockfile when downgrading is not an option:

A stale lockfile can pin an older version of the tauri crate even after you update Cargo.toml. If compilation errors persist after changing the version, delete Cargo.lock and run cargo build again. The lockfile will regenerate with the correct resolution.

Error: Plugin Not Initialized in the Rust Backend

Adding a plugin crate as a dependency is not enough — it must also be registered with the Tauri builder. When this step is skipped, the plugin’s commands and lifecycle hooks never become part of the application, and the frontend receives a “Plugin not found” error even if build.rs registration and permissions are correct.

In src-tauri/src/lib.rs, the .plugin() call wires the plugin into the runtime:

src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init()) // ← this line is essential
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

The init() function returns a TauriPlugin instance. Some plugins accept configuration via a builder pattern — for example, tauri_plugin_localhost::Builder::new(port).build() — but the principle is identical: the result must be passed to .plugin().

A simple way to verify that a plugin is active is to check whether its JavaScript bindings can import without error. For instance, after initializing the opener plugin, this should work in a React component:

src/App.jsx
import { openUrl } from '@tauri-apps/plugin-opener';
function App() {
    const handleClick = () => openUrl('https://tauri.app');
    return <button onClick={handleClick}>Open Tauri Website</button>;
}
export default App;

No import error means the plugin is initialized:

If the JavaScript import resolves without a runtime error (and the browser console shows no “module not found” message), the plugin’s guest bindings are correctly installed. You can verify full functionality by calling a command and checking the network tab or Tauri logs.

Error: Window Duplication with the Localhost Plugin

The localhost plugin (tauri-plugin-localhost) is commonly used during development to serve the Vite dev server through a localhost URL instead of loading files from disk. A frequent mistake is keeping the "main" window defined in tauri.conf.json while also creating it programmatically in Rust. The result is a “a webview with label ‘main’ already exists” error and the application fails to start.

Tauri creates the window declared in tauri.conf.json automatically. If your Rust code then attempts to build another window with the same label, the runtime detects the conflict and stops.

The solution is to remove the window from the configuration and let Rust create it, because the localhost plugin needs a custom URL that cannot be expressed in static JSON alone. Remove the windows array from tauri.conf.json or rename the window in the Rust code to something other than "main":

src-tauri/tauri.conf.json
{
  "build": { "devUrl": "http://localhost:5173", "frontendDist": "../dist" },
  "app": { "security": { "csp": null }, "windows": [] },
  "plugins": {}
}

Then in lib.rs, create the window with the localhost URL and configure remote access:

src-tauri/src/lib.rs
use tauri::{Manager, WindowBuilder, WindowUrl};
use tauri::ipc::RemoteDomainAccessScope;
pub fn run() {
    let port = 5173;
    tauri::Builder::default()
        .plugin(tauri_plugin_localhost::Builder::new(port).build())
        .setup(move |app| {
            app.ipc_scope()
                .configure_remote_access(
                    RemoteDomainAccessScope::new("localhost")
                        .add_window("main"),
                );
            let url = format!("http://localhost:{}", port).parse().unwrap();
            WindowBuilder::new(app, "main", WindowUrl::External(url))
                .title("My App")
                .build()?;
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Now there is no conflict — Tauri does not auto-create a window, and the Rust code creates exactly one.

Error: JavaScript API Bindings Not Found

The frontend tries to import @tauri-apps/plugin-<name> but the bundler cannot resolve the package. This indicates that the npm package for the plugin was never installed. The Rust crate and the JavaScript bindings are distributed separately; adding the Rust dependency does not automatically make the frontend package available.

Install the corresponding npm package for each plugin you use:

npm install @tauri-apps/plugin-opener

For plugins you build yourself, link the local guest-js package or publish it to a registry. The JavaScript side of a custom plugin is often published under an npm scope, like @my-scope/plugin-my-plugin. The import path in React must match that package name exactly.

Diagnosing Plugin Errors

When a plugin does not behave as expected, following a structured sequence prevents guesswork. Each step rules out a specific category of misconfiguration, and together they cover the entire wiring chain from Rust registration to frontend consumption.

1

Check that the plugin is registered in build.rs

Open src-tauri/build.rs and confirm that every command the plugin exposes is listed in the InlinedPlugin::commands array. For official plugins this is automatic, but for custom or local plugins this is mandatory. Rebuild the project after changes.

2

Verify the plugin is added to the Tauri builder

In src-tauri/src/lib.rs, ensure .plugin(your_plugin::init()) appears in the builder chain. Look for typos in the init call — some plugins use Builder::new(...).build() instead of init().

3

Confirm version compatibility

Compare the Tauri core crate version in Cargo.toml with the plugin’s required minimum version (check the plugin’s README or Cargo.toml). If they are mismatched, align them and run cargo update. Delete Cargo.lock if the issue persists.

4

Inspect capability files

For every command the frontend calls, there must be a corresponding plugin-name:allow-command-name entry in a capability file whose windows array includes the calling window. If you are using dynamic capabilities, verify that register_capability is called in setup and that the permission strings match.

5

Check the JavaScript package installation

Run npm list @tauri-apps/plugin-<name> to confirm the frontend bindings are installed. The version should roughly match the Rust crate version. In a React + Vite project, restart the dev server after installing new packages to avoid stale module resolution.

6

Read the Rust compiler output carefully

If the build fails, the error message often names the exact missing method or type. Search for that symbol in the plugin’s documentation. Frequently the answer is a version update or a missing feature flag.


Most plugin errors fall into one of these categories, and the stepper above isolates them in order of likelihood. Once a plugin command executes successfully from the frontend and returns the expected data, all the wiring is correct.