dist/plugins/desktop-framework-tauri/skills/desktop-framework-tauri/SKILL.md
Tauri 2.x commands, IPC bridge, permission system, plugins, window management, system tray, packaging
npx skillsauth add agents-inc/skills desktop-framework-tauriInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Tauri 2.x uses system webviews (not bundled Chromium) with a Rust backend. Define Rust commands with
#[tauri::command], invoke from frontend viainvoke()from@tauri-apps/api/core. Every sensitive operation requires an explicit permission grant in a capability file. Plugins follow a dual-install pattern: Cargo crate + npm package. Tauri 2 supports desktop (Windows, macOS, Linux) and mobile (iOS, Android).Current version: Tauri 2.x (stable, 2024+). Tauri 1.x is legacy and uses a fundamentally different security model (allowlist vs capabilities).
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use the Tauri 2.x capability/permission system -- the v1 allowlist is removed)
(You MUST register every command in tauri::generate_handler![] -- unregistered commands silently fail on invoke)
(You MUST add plugin permissions to a capability file -- plugins with missing permissions throw runtime errors)
(You MUST use #[cfg_attr(mobile, tauri::mobile_entry_point)] on pub fn run() for mobile support)
(You MUST use @tauri-apps/api/core for invoke() -- not the removed @tauri-apps/api/tauri path from v1)
</critical_requirements>
Auto-detection: Tauri, tauri.conf.json, src-tauri, tauri::command, tauri::Builder, invoke, @tauri-apps/api, tauri-plugin, capabilities, #[tauri::command], generate_handler, AppHandle, WebviewWindow, TrayIconBuilder
When to use:
When NOT to use:
Key patterns covered:
app.manage() + tauri::State<T> (examples/core.md)Detailed resources:
Tauri is security-first, small, and native. It uses the OS system webview instead of bundling Chromium, producing binaries 10-100x smaller than alternatives. The Rust backend provides memory safety and native performance. The permission system enforces least-privilege access -- nothing is allowed unless explicitly granted.
Tauri vs alternatives -- when Tauri is the right choice:
When Tauri may NOT be the right choice:
Define commands in Rust with #[tauri::command], register them with generate_handler![], invoke from frontend. Commands support arguments, return values, async, and error handling.
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// Register in main.rs or lib.rs
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
import { invoke } from "@tauri-apps/api/core";
const greeting = await invoke<string>("greet", { name: "World" });
Key point: Arguments are passed as a single object. The Rust parameter names must match the object keys. Forgetting to register a command in generate_handler![] causes silent failure. See examples/core.md for async commands, error handling, and state access.
Every Tauri 2 app needs at least one capability file granting permissions. Without permissions, plugin and core API calls fail at runtime.
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"dialog:default",
{
"identifier": "fs:allow-write-text-file",
"allow": [{ "path": "$APPDATA/*" }]
}
]
}
Key point: Permissions are scoped to specific windows. Use path variables ($APPDATA, $HOME, etc.) to restrict filesystem access. The v1 allowlist is completely removed. See examples/security.md for the full permission model.
Share state between commands using app.manage() and tauri::State<T>. For mutable state, wrap in Mutex or RwLock.
use std::sync::Mutex;
struct AppState {
counter: Mutex<i32>,
}
#[tauri::command]
fn increment(state: tauri::State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
Key point: tauri::State<T> is injected automatically when declared as a command parameter. The type must implement Send + Sync. See examples/core.md for full patterns.
Bidirectional events between frontend and backend. Frontend uses emit()/listen(), backend uses app.emit()/app.listen().
import { listen } from "@tauri-apps/api/event";
const unlisten = await listen<string>("download-progress", (event) => {
console.log(`Progress: ${event.payload}`);
});
// Clean up when done
unlisten();
// Emit from backend to all windows
app.emit("download-progress", "50%").unwrap();
Key point: Always call the unlisten function to prevent memory leaks. Events are string-typed -- use consistent naming conventions. See examples/core.md for targeted window events and channels.
All official plugins follow a dual-install pattern: Rust crate + npm package. Each plugin needs permissions in a capability file.
# 1. Add Rust crate
cargo add tauri-plugin-store
# 2. Add JS bindings
npm add @tauri-apps/plugin-store
# 3. Register plugin in Rust
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
# 4. Add permission to capability file
# "store:allow-get", "store:allow-set"
Key point: Missing any of the four steps (crate, npm, registration, permission) causes runtime errors, not compile errors. See examples/plugins.md for the full plugin list.
Build system tray icons with menus and event handlers.
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
use tauri::menu::{MenuBuilder, MenuItemBuilder};
tauri::Builder::default()
.setup(|app| {
let toggle = MenuItemBuilder::with_id("toggle", "Toggle").build(app)?;
let menu = MenuBuilder::new(app).items(&[&toggle]).build()?;
TrayIconBuilder::new()
.menu(&menu)
.on_menu_event(move |_app, event| match event.id().as_ref() {
"toggle" => println!("toggle clicked"),
_ => (),
})
.on_tray_icon_event(|tray, event| {
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up, ..
} = event {
let app = tray.app_handle();
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
})
.build(app)?;
Ok(())
})
Key point: In Tauri 2, SystemTray is replaced by TrayIconBuilder. Menu events and tray icon events are handled separately. See examples/platform.md for full tray patterns.
<decision_framework>
Where does this logic belong?
|-- Pure UI rendering, user interaction?
| +-- Frontend (JavaScript/TypeScript in webview)
|-- File system, network, OS integration, heavy computation?
| +-- Rust backend (Tauri commands)
|-- Sensitive operation (file write, shell exec, HTTP)?
| +-- Rust command + explicit permission in capability file
+-- Shared state between commands?
+-- app.manage() with Mutex/RwLock wrapper
How to structure the command?
|-- Returns data synchronously?
| +-- Regular #[tauri::command] fn
|-- Needs I/O, network, or long computation?
| +-- async #[tauri::command] with Result<T, E>
|-- Needs to access managed state?
| +-- Add tauri::State<T> parameter
|-- Needs app handle (emit events, manage windows)?
| +-- Add app: tauri::AppHandle parameter
+-- Needs to stream data to frontend?
+-- Use tauri::ipc::Channel<T> parameter
Need OS integration?
|-- File system access?
| +-- tauri-plugin-fs
|-- File/folder picker dialog?
| +-- tauri-plugin-dialog
|-- HTTP requests from backend?
| +-- tauri-plugin-http
|-- Persistent key-value storage?
| +-- tauri-plugin-store
|-- System notifications?
| +-- tauri-plugin-notification
|-- Run external processes?
| +-- tauri-plugin-shell
|-- Auto-update?
| +-- tauri-plugin-updater
|-- Clipboard?
| +-- tauri-plugin-clipboard-manager
|-- Deep links (custom URL scheme)?
| +-- tauri-plugin-deep-link
+-- Launch on system startup?
+-- tauri-plugin-autostart
See reference.md for CLI commands and config reference.
</decision_framework>
<red_flags>
High Priority Issues:
@tauri-apps/api/tauri import path (removed in v2 -- use @tauri-apps/api/core)allowlist in tauri.conf.json (replaced by capability files in v2)generate_handler![] (silent failure, no compile error)SystemTray API (removed in v2 -- use TrayIconBuilder from tauri::tray)tauri::api::* (removed in v2 -- functionality moved to plugins)invoke() without awaiting (returns a Promise, not the value)#[cfg_attr(mobile, tauri::mobile_entry_point)] on run() (breaks mobile builds)Medium Priority Issues:
listen() without calling unlisten)unwrap() in commands instead of returning Result (crashes the command, no error to frontend)fs:allow-read-file without path scope)$APPDATA, $HOME, $RESOURCE)Mutex/RwLock for mutable managed state (data races)Common Mistakes:
window.__TAURI__ without setting app.withGlobalTauri: true in configResult error variant in async commands (unhandled promise rejection in frontend)Gotchas & Edge Cases:
windows$APPDATA, $RESOURCE, $HOME etc. are Tauri-specific, not environment variablesserde::Serialize/Deserialize -- complex types need derive macrostauri dev proxies your frontend dev server -- configure devUrl in tauri.conf.json, not in the frontend build tool</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use the Tauri 2.x capability/permission system -- the v1 allowlist is removed)
(You MUST register every command in tauri::generate_handler![] -- unregistered commands silently fail on invoke)
(You MUST add plugin permissions to a capability file -- plugins with missing permissions throw runtime errors)
(You MUST use #[cfg_attr(mobile, tauri::mobile_entry_point)] on pub fn run() for mobile support)
(You MUST use @tauri-apps/api/core for invoke() -- not the removed @tauri-apps/api/tauri path from v1)
Failure to follow these rules will cause silent command failures, runtime permission errors, or broken mobile builds.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety