Permission Errors in Tauri v2
Diagnose and resolve common permission errors in Tauri v2 applications, from missing capability entries to OS-level access denials.
Tauri v2 introduces a permission‑based security model that restricts what your frontend code can do. Instead of silently denying access, Tauri surfaces errors in the browser console or as rejected promises. These errors can be confusing if you’re not yet familiar with capabilities and scopes — especially when an API works in tauri dev but breaks after tauri build.
This guide dissects the most frequent permission errors, how to read the error messages, and exactly where to add the missing configuration. It assumes you’re using React with Vite, but the patterns apply to any frontend framework.
How Permission Errors Actually Happen
Every native API call from your React code travels through Tauri’s IPC layer. The backend checks the calling window’s capability set to decide whether the command is allowed and, if it uses scopes, whether the requested resource falls within the allowed paths or patterns. If the check fails, the command never reaches its Rust implementation. The frontend receives an error like "fs.write_text_file not allowed" or "command not found".
Not the same as OS permissions:
The word “Permission denied” in a Tauri app can come from two places: Tauri’s own ACL system, or the operating system. If the error includes a command name (like shell:open), it’s almost certainly a Tauri permission. If it’s a plain Operation not permitted (os error 13), the OS denied the action — often because of macOS sandboxing, file ownership, or missing entitlements.
Reading Error Messages Correctly
Tauri v2 error messages follow a predictable shape. A frontend call that lacks permission will produce something like:
[Error] Unhandled Promise Rejection: fs.write_text_file not allowed. Permissions associated with this command: fs:allow-app-write, fs:allow-app-write-recursive, ...
The message tells you:
- The exact command that was blocked (
write_text_file) - The permission identifiers that would enable it (e.g.,
fs:allow-app-writeor more specific ones)
If you see a list of permission identifiers, one of them must be added to your capability file. If the error says command not found, the command name might be wrong or the plugin that provides it isn’t initialized.
Permission Denied (OS Error 13) — OS-Level vs Tauri
A raw Permission denied (os error 13) often stumps developers because it looks like a Tauri issue but is usually an operating‑system restriction.
When the OS Is the Culprit
This occurs when your Rust code (or a Tauri plugin’s backend) tries to access a file or directory that the operating system’s own permission model rejects.
Common causes:
- macOS sandbox: reading or writing outside the app’s bundle or user‑approved directories without the required entitlements.
- File ownership: trying to open a file owned by another user with restrictive permissions.
- Read‑only files: attempting to overwrite a file that lacks write permission.
- Android: missing
MANAGE_EXTERNAL_STORAGEor similar platform permissions.
Don't confuse OS errors with Tauri scope errors:
If the error message does not mention a command name or permission identifier, it’s an OS error. Adding Tauri permissions will not fix it. Check the file’s Unix permissions, macOS entitlements, or Android manifest instead.
Diagnosing an OS Error
First, verify the exact file path and whether the current process user can access it. On macOS, you may need to grant “Full Disk Access” to the terminal or the built app. On Linux, check ls -l and chmod.
If you’re using the fs plugin and reading a user‑chosen file through a dialog, the OS security layer can still block the read if the file is in a protected directory. In such cases, adding the com.apple.security.temporary-exception.files.absolute-path.read-write entitlement (macOS) might be necessary for development, but for distribution you’ll need to handle this through proper sandbox scoping or a command‑based workaround.
Command Not Allowed — Missing Permissions
This is the most frequent error you’ll encounter. It happens when a capability file does not list the required permission for the command you’re calling.
Example Error
[Error] shell:open not allowed. Permissions associated with this command: shell:allow-open
Root Cause
The default capability (or the one assigned to the window) does not include shell:allow-open.
Fix
Open the capability file in src-tauri/capabilities/ (usually default.json) and add the missing identifier to the permissions array.
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open"
]
}
After adding the permission, rebuild the app. The error should disappear.
Works in dev but not in production?:
A common pitfall is that tauri dev uses a less restrictive permission set (or the dev server's origin has implicit permissions in some setups) while the production build strictly enforces the capability file. Always test permission changes with tauri build or at least with a production‑mode preview.
Understanding Permission Identifiers
Permissions follow the pattern <plugin>:<permission-name>. For official plugins, the prefix is just the short name: fs, shell, dialog, etc. For example, fs:allow-write-text-file is a permission for the file system plugin. The autogenerated permissions in the plugin source (or documentation) list all identifiers.
If you need to enable a command that has no pre‑defined permission, you can create a custom permission in your app’s src-tauri/permissions/ folder and reference it in the capability file.
Capability Not Found
A Capability '...' not found error means Tauri cannot locate the capability file you referenced, or the file has a syntax issue.
When It Happens
- The
identifierfield in the capability file does not match what’s expected (e.g., you rename it but don’t update the JSON). - The file is missing from
src-tauri/capabilities/. - The filename doesn’t end with
.jsonor.toml, or the content is invalid JSON.
How to Fix
- Check that the file exists in
src-tauri/capabilities/. - Confirm the
identifierfield matches what you intend to reference. The identifier is not tied to the filename, but it must be unique. - Validate the JSON/Toml syntax (commas, quotes, no trailing commas).
If you’re using a custom capability alongside the default, make sure each window is assigned the correct capability via the windows field.
Missing Plugin Permissions (Default Permissions Not Included)
When you add a Tauri plugin, it often ships with a default permission set that enables a minimal, safe set of commands. If you forget to include this default permission in your capability, many commands will be unavailable even though the plugin is initialized.
Example: File System Plugin
After adding tauri-plugin-fs and initializing it in lib.rs, you might still see:
[Error] fs:read_text_file not allowed.
The Fix
Add "fs:default" to the capability’s permissions array. This pulls in the plugin’s recommended defaults. From there you can add or restrict further.
{
"identifier": "default",
"permissions": [
"core:default",
"fs:default",
"fs:allow-write-text-file",
{
"identifier": "fs:allow-write-text-file",
"allow": [{ "path": "$HOME/test.txt" }]
}
]
}
Confirm the fix:
After adding fs:default and the specific write permission, run your app and try a write operation. If the error disappears and the file is created, the permission is correctly configured.
Diagnosing Permission Errors Quickly
Before digging into files, use these checks:
1. Read the Console Error Completely
The error message often lists the exact permission identifiers you need. Don’t just scan it — copy it and search it in the plugin’s permissions list (found in the documentation or source permissions/ directory).
2. Listen to tauri://error for Async API Failures
Some APIs, like WebviewWindow constructors, fail asynchronously. The browser console might not show the permission error clearly. Attach a listener:
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
const webview = new WebviewWindow('login', { url: '/' });
webview.once('tauri://error', (err) => {
console.error('Window creation failed:', err);
});
This catches permissions errors that would otherwise be swallowed.
3. Run with Rust Logging
Set RUST_LOG=debug before tauri dev to see IPC permission checks in the terminal. You’ll see lines like checking permission for command X followed by a deny reason. This is invaluable for custom commands.
4. Validate Capability Files Against the Schema
The $schema field points to a JSON schema. Most editors (like VS Code) will highlight errors. If you see a red underline, fix the formatting issue.
Fixing Common Permission Scenarios Step by Step
Scenario 1: Reading a File with the FS Plugin
Error:
fs:read_text_file not allowed. Permissions associated with this command: fs:allow-appread, fs:allow-appread-recursive, ...
Solution:
Add the permission fs:allow-read-text-file to the capability, and define a scope if you need to access a specific path outside $APPDATA.
{
"permissions": [
"fs:default",
"fs:allow-read-text-file",
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$HOME/documents/*" }]
}
]
}
import { readTextFile } from '@tauri-apps/plugin-fs';
async function readFile() {
try {
const content = await readTextFile('documents/notes.txt', { baseDir: BaseDirectory.Home });
console.log(content);
} catch (error) {
console.error(error);
}
}
Without the scope, the readTextFile would be allowed but only within the default app directories. With the scope, the home‑relative path becomes accessible.
Scenario 2: Opening a URL with the Shell Plugin
Error:
shell:open not allowed. Permissions associated with this command: shell:allow-open
Solution:
Add "shell:allow-open" to the permissions array. The shell plugin’s open command also needs a scope if you want to restrict which URLs can be opened; without a scope, all URLs are allowed.
{
"permissions": [
"shell:allow-open"
]
}
import { open } from '@tauri-apps/plugin-shell';
const openGitHub = () => {
open('https://github.com');
};
Scenario 3: Custom Rust Command Without Permission
If you define a custom Tauri command in Rust and call it from the frontend without setting up a permission, you’ll get a “command not found” error.
Error:
command my_custom_command not found
Fix: Create a permission file for your app’s command and reference it in the capability.
- Create
src-tauri/permissions/my-permission.toml:
[[permission]]
identifier = "my-custom-command"
description = "Allows my custom command"
commands.allow = ["my_custom_command"]
- In the capability file, add the permission using the format
"<app-identifier>:my-custom-command". The app identifier is your crate name (found inCargo.toml). For a crate namedmy-tauri-app, it’s"my-tauri-app:my-custom-command".
{
"permissions": [
"my-tauri-app:my-custom-command"
]
}
Now the command will be allowed.
Command name must match exactly:
The string in commands.allow must be the same as the #[tauri::command] function name. A typo here results in a “command not found” error that can be mistaken for a permission issue.
Best Practices to Avoid Permission Errors
- Include plugin defaults – always add
<plugin>:defaultwhen you add a plugin. - Test in production mode – run
tauri buildearly to catch permission gaps. - Use minimal scopes – broad scopes like
**/*or$HOME/*can be dangerous; tighten them to specific directories. - Leverage the schema – the
$schemareference in capability files gives you autocompletion and validation in editors that support it. - Check the documentation for each plugin – autogenerated permission lists are available on the Tauri website or in the plugin’s
permissions/autogeneratedfolder.
Permission Errors Versus Plugin Errors
It’s easy to confuse a permission error with a plugin‑initialization error. If a plugin isn’t registered with .plugin(...) in lib.rs, some errors may surface as “command not found” rather than a permission denial. Always verify the plugin is initialized first, then check permissions.
Summary
Permission errors in Tauri v2 are precise if you know how to read them. The system tells you which permission is missing; you just need to add it to the capability file. When an error spills over into OS‑level “Permission denied” territory, the fix lies outside Tauri’s configuration — in file permissions, macOS entitlements, or Android manifests. The most reliable workflow is to inspect the console error message, cross‑reference it with the plugin’s permission list, and add the required identifier. A habit of testing with tauri build and using minimal scopes will prevent most permission surprises before they reach users.