Tauri Core Knowledge
Full Reference: See advanced.md for event system, plugins (Dialog, Filesystem, Store), bundling configuration, code signing, testing patterns, and input validation.
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:taurifor comprehensive API documentation.
When NOT to Use This Skill
- Electron applications - Use the
electronskill - Pure Rust applications - Use Rust skill for CLI or backend-only
- Web-only deployments - Tauri is for desktop apps
- Mobile apps - Tauri Mobile is in alpha
- Applications requiring Node.js ecosystem - Use Electron
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Rust Backend │
│ • Tauri Runtime (window management, events, plugins) │
│ • Custom Commands (#[tauri::command]) │
│ • State Management (tauri::State with Mutex/RwLock) │
└──────────────────────────┬──────────────────────────────────┘
│ IPC (invoke / events)
┌──────────────────────────▼──────────────────────────────────┐
│ System WebView │
│ • Web APIs (DOM, CSS, JavaScript) - No Node.js │
│ • @tauri-apps/api (type-safe IPC) │
│ • Frontend Framework (Svelte/React/Vue) │
└─────────────────────────────────────────────────────────────┘
Project Structure
tauri-app/
├── src/ # Frontend source
│ └── routes/
├── src-tauri/ # Rust backend
│ ├── Cargo.toml
│ ├── tauri.conf.json
│ ├── capabilities/ # Permissions
│ └── src/
│ ├── main.rs
│ └── lib.rs
├── package.json
└── vite.config.ts
Configuration
tauri.conf.json
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "My App",
"version": "1.0.0",
"identifier": "com.company.myapp",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:5173",
"frontendDist": "../build"
},
"app": {
"windows": [{
"title": "My App",
"width": 1200,
"height": 800,
"center": true
}],
"security": {
"csp": "default-src 'self'; script-src 'self'"
}
}
}
IPC Communication
Basic Command
// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// Frontend
import { invoke } from '@tauri-apps/api/core';
const message = await invoke<string>('greet', { name: 'World' });
Async Command with Error Handling
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize)]
pub struct CommandError {
pub code: String,
pub message: String,
}
#[derive(Serialize, Deserialize)]
pub struct User {
id: u32,
name: String,
email: String,
}
#[tauri::command]
async fn get_user(id: u32) -> Result<User, CommandError> {
if id == 0 {
return Err(CommandError {
code: "NOT_FOUND".to_string(),
message: "User not found".to_string(),
});
}
Ok(User {
id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
})
}
interface CommandError {
code: string;
message: string;
}
try {
const user = await invoke<User>('get_user', { id: 1 });
} catch (error) {
const err = error as CommandError;
console.error(`[${err.code}] ${err.message}`);
}
State Management
use std::sync::Mutex;
use tauri::State;
pub struct AppState {
pub counter: Mutex<i32>,
}
#[tauri::command]
fn get_count(state: State<AppState>) -> i32 {
*state.counter.lock().unwrap()
}
#[tauri::command]
fn increment(state: State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
// In main
tauri::Builder::default()
.manage(AppState { counter: Mutex::new(0) })
.invoke_handler(tauri::generate_handler![get_count, increment])
Security
Capability-Based Permissions
// src-tauri/capabilities/default.json
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-open",
"dialog:allow-save",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
{
"identifier": "fs:scope",
"allow": ["$APPDATA/**", "$DOCUMENT/**"]
},
"store:default"
]
}
Anti-Patterns
| Anti-Pattern | Why It's Wrong | Correct Approach |
|---|---|---|
| No input validation in commands | Vulnerable to injection | Validate all parameters |
Using .unwrap() in commands | Panics crash the app | Use Result<T, E> |
| Exposing all filesystem | Security risk | Use scoped permissions |
| Synchronous blocking in commands | Freezes UI | Use async functions |
| Not using State for shared data | Data races | Use tauri::State with Mutex |
| Hardcoding paths | Breaks cross-platform | Use BaseDirectory enum |
allow-all in permissions | Defeats security model | List required permissions |
Quick Troubleshooting
| Issue | Diagnosis | Solution |
|---|---|---|
| "Command not found" | Not registered | Add to generate_handler![] |
allowlist errors after v2 | V2 uses capabilities | Migrate to capabilities/ |
| Frontend can't read file | Missing permissions | Add fs:allow-read-* |
tauri dev shows blank window | Dev server not running | Check devUrl matches |
| IPC calls are slow | Large payload | Minimize data transfer |
| App crashes on state access | State not initialized | Register with .manage() |