tauri-v2
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<string>('greet', { name: 'World' });
console.log(greeting); // "Hello, World!"
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<T, E>from commands for proper error handling - Use
Mutex<T>for shared state accessed from multiple commands - Add capabilities before using any plugin features
- Use
lib.rsfor shared code (required for mobile builds)
Never Do
- Never use borrowed types (
&str) in async commands - use owned types - Never block the main thread - use async for I/O operations
- Never hardcode paths - use Tauri path APIs (
app.path()) - Never skip capability setup - even "safe" operations need permissions
Common Mistakes
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<T> |
Use exact type from .manage() |
| White screen on launch | Frontend not building | Check beforeDevCommand in config |
| IPC timeout | Blocking async command | Remove blocking code or use spawn |
| Mobile build fails | Missing Rust targets | Run rustup target add <target> |
Configuration Reference
tauri.conf.json
{
"$schema": "./gen/schemas/desktop-schema.json",
"productName": "my-app",
"version": "1.0.0",
"identifier": "com.example.myapp",
"build": {
"devUrl": "http://localhost:5173",
"frontendDist": "../dist",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [{
"label": "main",
"title": "My App",
"width": 800,
"height": 600
}],
"security": {
"csp": "default-src 'self'; img-src 'self' data:",
"capabilities": ["default"]
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": ["icons/icon.icns", "icons/icon.ico", "icons/icon.png"]
}
}
Key settings:
build.devUrl: Must match your frontend dev server portapp.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 buildscrate-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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
serializer.serialize_str(self.to_string().as_ref())
}
}
#[tauri::command]
fn risky_operation() -> Result<String, AppError> {
Ok("success".into())
}
State Management Pattern
use std::sync::Mutex;
use tauri::State;
struct AppState {
counter: u32,
}
#[tauri::command]
fn increment(state: State<'_, Mutex<AppState>>) -> u32 {
let mut s = state.lock().unwrap();
s.counter += 1;
s.counter
}
// 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<DownloadEvent>) {
for i in 0..=100 {
on_event.send(DownloadEvent::Progress { percent: i }).unwrap();
}
on_event.send(DownloadEvent::Complete { path: "/downloads/file".into() }).unwrap();
}
import { invoke, Channel } from '@tauri-apps/api/core';
const channel = new Channel<DownloadEvent>();
channel.onmessage = (msg) => console.log(msg.event, msg.data);
await invoke('download', { url: 'https://...', onEvent: channel });
Bundled Resources
References
Located in references/:
capabilities-reference.md- Permission patterns and examplesipc-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
Troubleshooting
White Screen on Launch
Symptoms: App launches but shows blank white screen
Solution:
- Verify
devUrlmatches your frontend dev server port - Check
beforeDevCommandruns 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 infoshows correct Tauri v2 versions -
src-tauri/capabilities/default.jsonexists with at leastcore:default - All commands registered in
generate_handler![] -
lib.rscontains shared code (for mobile support) - Required Rust targets installed for target platforms