Tauri v2 Development Skill
Build cross-platform desktop and mobile apps with web frontends and Rust backends using Tauri v2.
Core Principles
- Architecture: Use Rust for the backend (performance, native APIs) and any web framework for the frontend (UI, routing).
- Communication: Use Tauri's IPC (
invoke, events, channels) to bridge the Rust backend and the web frontend.
- Security-First: Everything is denied by default. Explicitly configure permissions in
src-tauri/capabilities/ to allow frontend access to backend commands and plugins.
- Cross-Platform: Write once, compile to Windows, macOS, Linux, Android, and iOS. Use
lib.rs for shared logic.
Quick Setup Checklist
Before making changes to a Tauri project, verify:
src-tauri/tauri.conf.json has build.devUrl and build.frontendDist configured.
src-tauri/capabilities/default.json exists and includes necessary permissions.
- All custom Rust commands are registered in
tauri::generate_handler![] within lib.rs or main.rs.
lib.rs contains shared code, essential for mobile builds.
Primary Workflows
Creating and Using Commands
- Define the command in Rust with
#[tauri::command]:// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> Result<String, String> {
Ok(format!("Hello, {}!", name))
}
- Register the command in
tauri::Builder:tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
// ...
- Invoke the command from the frontend:
import { invoke } from '@tauri-apps/api/core';
const response = await invoke<string>('greet', { name: 'World' });
For more detailed IPC patterns (async, error handling, events, channels), see ipc.md.
Managing Application State
- Define state struct with thread-safe types (e.g.,
Mutex):use std::sync::Mutex;
struct AppState { counter: Mutex<u32> }
- Register state in the builder:
tauri::Builder::default()
.setup(|app| {
app.manage(AppState { counter: Mutex::new(0) });
Ok(())
})
- Access state in commands:
#[tauri::command]
fn increment(state: tauri::State<'_, AppState>) -> Result<u32, String> {
let mut count = state.counter.lock().map_err(|e| e.to_string())?;
*count += 1;
Ok(*count)
}
Configuring Security and Capabilities
Tauri v2 requires explicit capabilities for all IPC commands, including custom ones and core plugins.
For a complete guide to capabilities, see capabilities.md.
Configuration and Build Settings
Manage project settings, icons, plugins, and build commands in tauri.conf.json.
For tauri.conf.json and Cargo.toml configurations, see config.md.
Critical Rules
- Always register commands: Commands not in
generate_handler![] fail silently on the frontend.
- Never use borrowed types in async commands: Use owned types (
String, not &str). Async commands cannot borrow data across await points.
- Never block the main thread: Use
async or std::thread::spawn for heavy I/O operations.
- Always handle errors explicitly: Return
Result<T, AppError> from commands where AppError implements serde::Serialize.
- Always use
@tauri-apps/api/core: The @tauri-apps/api/tauri path is deprecated from v1.
Troubleshooting
- "Command not found" / Promise hangs: Check if the command is added to
generate_handler![]. Ensure frontend parameter names are camelCase and Rust parameters are snake_case.
- "Permission denied": The command or plugin lacks a capability in
src-tauri/capabilities/.
- White screen on launch: Ensure the frontend dev server is running and matches
build.devUrl in tauri.conf.json. Check beforeDevCommand.
- Mobile build fails: Ensure Rust targets are installed (
rustup target add aarch64-linux-android ...) and that the app logic is in lib.rs rather than main.rs.
1---2name: tauri3description: Tauri v2 development: build cross-platform desktop/mobile apps with Rust backends and web frontends. Use when configuring tauri.conf.json, implementing Rust commands (#[tauri::command]), setting up IPC patterns (invoke, emit, channels), managing state, configuring permissions/capabilities, troubleshooting build issues, or deploying. Triggers on Tauri, src-tauri, tauri.conf.json, capabilities.4---56# Tauri v2 Development Skill78> Build cross-platform desktop and mobile apps with web frontends and Rust backends using Tauri v2.910## Core Principles1112- **Architecture:** Use Rust for the backend (performance, native APIs) and any web framework for the frontend (UI, routing).13- **Communication:** Use Tauri's IPC (`invoke`, `events`, `channels`) to bridge the Rust backend and the web frontend.14- **Security-First:** Everything is denied by default. Explicitly configure permissions in `src-tauri/capabilities/` to allow frontend access to backend commands and plugins.15- **Cross-Platform:** Write once, compile to Windows, macOS, Linux, Android, and iOS. Use `lib.rs` for shared logic.1617## Quick Setup Checklist1819Before making changes to a Tauri project, verify:20- `src-tauri/tauri.conf.json` has `build.devUrl` and `build.frontendDist` configured.21- `src-tauri/capabilities/default.json` exists and includes necessary permissions.22- All custom Rust commands are registered in `tauri::generate_handler![]` within `lib.rs` or `main.rs`.23- `lib.rs` contains shared code, essential for mobile builds.2425## Primary Workflows2627### Creating and Using Commands28291. **Define the command** in Rust with `#[tauri::command]`:30 ```rust31 // src-tauri/src/lib.rs32 #[tauri::command]33 fn greet(name: String) -> Result<String, String> {34 Ok(format!("Hello, {}!", name))35 }36 ```372. **Register the command** in `tauri::Builder`:38 ```rust39 tauri::Builder::default()40 .invoke_handler(tauri::generate_handler![greet])41 // ...42 ```433. **Invoke the command** from the frontend:44 ```typescript45 import { invoke } from '@tauri-apps/api/core';46 const response = await invoke<string>('greet', { name: 'World' });47 ```4849**For more detailed IPC patterns (async, error handling, events, channels), see [ipc.md](references/ipc.md).**5051### Managing Application State52531. **Define state struct** with thread-safe types (e.g., `Mutex`):54 ```rust55 use std::sync::Mutex;56 struct AppState { counter: Mutex<u32> }57 ```582. **Register state** in the builder:59 ```rust60 tauri::Builder::default()61 .setup(|app| {62 app.manage(AppState { counter: Mutex::new(0) });63 Ok(())64 })65 ```663. **Access state** in commands:67 ```rust68 #[tauri::command]69 fn increment(state: tauri::State<'_, AppState>) -> Result<u32, String> {70 let mut count = state.counter.lock().map_err(|e| e.to_string())?;71 *count += 1;72 Ok(*count)73 }74 ```7576### Configuring Security and Capabilities7778Tauri v2 requires explicit capabilities for all IPC commands, including custom ones and core plugins.7980**For a complete guide to capabilities, see [capabilities.md](references/capabilities.md).**8182### Configuration and Build Settings8384Manage project settings, icons, plugins, and build commands in `tauri.conf.json`.8586**For `tauri.conf.json` and `Cargo.toml` configurations, see [config.md](references/config.md).**8788## Critical Rules8990- **Always register commands:** Commands not in `generate_handler![]` fail silently on the frontend.91- **Never use borrowed types in async commands:** Use owned types (`String`, not `&str`). Async commands cannot borrow data across await points.92- **Never block the main thread:** Use `async` or `std::thread::spawn` for heavy I/O operations.93- **Always handle errors explicitly:** Return `Result<T, AppError>` from commands where `AppError` implements `serde::Serialize`.94- **Always use `@tauri-apps/api/core`:** The `@tauri-apps/api/tauri` path is deprecated from v1.9596## Troubleshooting9798- **"Command not found" / Promise hangs:** Check if the command is added to `generate_handler![]`. Ensure frontend parameter names are `camelCase` and Rust parameters are `snake_case`.99- **"Permission denied":** The command or plugin lacks a capability in `src-tauri/capabilities/`.100- **White screen on launch:** Ensure the frontend dev server is running and matches `build.devUrl` in `tauri.conf.json`. Check `beforeDevCommand`.101- **Mobile build fails:** Ensure Rust targets are installed (`rustup target add aarch64-linux-android ...`) and that the app logic is in `lib.rs` rather than `main.rs`.