Tauri v2 Development Skill
Build cross-platform desktop and mobile apps with web frontends and Rust backends.
Before You Start
This skill prevents 8+ common errors and saves ~60% tokens.
Metric Without Skill With Skill Setup Time ~2 hours ~30 min Common Errors 8+ 0 Token Usage High (exploration) Low (direct patterns) Known Issues This Skill Prevents Permission denied errors from missing capabilities IPC failures from unregistered commands in generate_handler! State management panics from type mismatches Mobile build failures from missing Rust targets White screen issues from misconfigured dev URLs Quick Start Step 1: Create a Tauri Command // src-tauri/src/lib.rs
[tauri::command]
fn greet(name: String) -> String { format!("Hello, {}!", name) }
pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
Why this matters: Commands not in generate_handler![] silently fail when invoked from frontend.
Step 2: Call from Frontend import { invoke } from '@tauri-apps/api/core';
const greeting = await invoke
Why this matters: Use @tauri-apps/api/core (not @tauri-apps/api/tauri - that's v1 API).
Step 3: Add Required Permissions // src-tauri/capabilities/default.json { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "windows": ["main"], "permissions": ["core:default"] }
Why this matters: Tauri v2 denies everything by default - explicit permissions required for all operations.
Critical Rules
Always Do
Register every command in tauri::generate_handler![cmd1, cmd2, ...]
Return Result
Wrong - Borrowed type in async:
[tauri::command]
async fn bad(name: &str) -> String { // Compile error! name.to_string() }
Correct - Owned type:
[tauri::command]
async fn good(name: String) -> String { name }
Why: Async commands cannot borrow data across await points; Tauri requires owned types for async command parameters.
Known Issues Prevention
Issue Root Cause Solution
"Command not found" Missing from generate_handler! Add command to handler macro
"Permission denied" Missing capability Add to capabilities/default.json
State panic on access Type mismatch in State
Key settings:
build.devUrl: Must match your frontend dev server port app.security.capabilities: Array of capability file identifiers Cargo.toml [ package ] name = "app" version = "0.1.0" edition = "2021"
[ lib ] name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"]
[ build-dependencies ] tauri-build = { version = "2", features = [] }
[ dependencies ] tauri = { version = "2", features = [] } serde = { version = "1", features = ["derive"] } serde_json = "1"
Key settings:
[lib] section: Required for mobile builds crate-type: Must include all three types for cross-platform Common Patterns Error Handling Pattern use thiserror::Error;
[derive(Debug, Error)]
enum AppError { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Not found: {0}")] NotFound(String), }
impl serde::Serialize for AppError {
fn serialize(&self, serializer: S) -> Result
[tauri::command]
fn risky_operation() -> Result
State Management Pattern use std::sync::Mutex; use tauri::State;
struct AppState { counter: u32, }
[tauri::command]
fn increment(state: State<'_, Mutex
// In builder: tauri::Builder::default() .manage(Mutex::new(AppState { counter: 0 }))
Event Emission Pattern use tauri::Emitter;
[tauri::command]
fn start_task(app: tauri::AppHandle) { std::thread::spawn(move || { app.emit("task-progress", 50).unwrap(); app.emit("task-complete", "done").unwrap(); }); }
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('task-progress', (e) => { console.log('Progress:', e.payload); }); // Call unlisten() when done
Channel Streaming Pattern use tauri::ipc::Channel;
[derive(Clone, serde::Serialize)]
[serde(tag = "event", content = "data")]
enum DownloadEvent { Progress { percent: u32 }, Complete { path: String }, }
[tauri::command]
async fn download(url: String, on_event: Channel
import { invoke, Channel } from '@tauri-apps/api/core';
const channel = new Channel
Bundled Resources References
Located in references/:
capabilities-reference.md - Permission patterns and examples ipc-patterns.md - Complete IPC examples
Note: For deep dives on specific topics, see the reference files above.
Dependencies Required Package Version Purpose @tauri-apps/cli ^2.0.0 CLI tooling @tauri-apps/api ^2.0.0 Frontend APIs tauri ^2.0.0 Rust core tauri-build ^2.0.0 Build scripts Optional (Plugins) Package Version Purpose tauri-plugin-fs ^2.0.0 File system access tauri-plugin-dialog ^2.0.0 Native dialogs tauri-plugin-shell ^2.0.0 Shell commands, open URLs tauri-plugin-http ^2.0.0 HTTP client tauri-plugin-store ^2.0.0 Key-value storage Official Documentation Tauri v2 Documentation Commands Reference Capabilities & Permissions Configuration Reference Troubleshooting White Screen on Launch
Symptoms: App launches but shows blank white screen
Solution:
Verify devUrl matches your frontend dev server port Check beforeDevCommand runs your dev server Open DevTools (Cmd+Option+I / Ctrl+Shift+I) to check for errors Command Returns Undefined
Symptoms: invoke() returns undefined instead of expected value
Solution:
Verify command is in generate_handler![] Check Rust command actually returns a value Ensure argument names match (camelCase in JS, snake_case in Rust by default) Mobile Build Failures
Symptoms: Android/iOS build fails with missing target
Solution:
Android targets
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
iOS targets (macOS only)
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
Setup Checklist
Before using this skill, verify:
npx tauri info shows correct Tauri v2 versions src-tauri/capabilities/default.json exists with at least core:default All commands registered in generate_handler![] lib.rs contains shared code (for mobile support) Required Rust targets installed for target platforms