Tauri V2
Tauri v2+ cross-platform app development with Rust backend. Use when configuring tauri.conf.json, implementing Rust commands (#[tauri::command]), setting up IPC patterns (invoke, emit, channels), configuring permissions/capabilities, troubleshooting build issues, or deploying desktop/mobile apps. Triggers on Tauri, src-tauri, invoke, emit, capabilities.json.
- Skill ID
- nodnarbnitram/claude-code-extensions/tauri-v2
- Publisher
- nodnarbnitram
- Repository
- claude-code-extensions
- Installs
- 0
- Files
- 8
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
nodnarbnitram/claude-code-extensions/tauri-v2Installs these files- README.md
- SKILL.md
- references/README.md
- references/advanced-runtime-reference.md
- references/capabilities-reference.md
- references/ipc-patterns.md
- references/plugin-reference.md
- references/updater-distribution-reference.md
What this skill tells the agent
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)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
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.
`main.rs` stays thin:src-tauri/src/main.rsshould only be a thin passthrough — all application logic lives inlib.rs: ``rust // src-tauri/src/main.rs #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { app_lib::run(); }`This split is required for mobile builds — Tauri replacesmain()withmobile_entry_point` on mobile targets.
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) - Use
#[cfg_attr(mobile, tauri::mobile_entry_point)]onpub fn run()inlib.rsfor mobile compatibility
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 |
| Plugin feature silently fails | Plugin installed but permission not in capability | Add plugin permission string to capabilities/default.json |
| Updater fails in production | Unsigned artifacts or HTTP endpoint | Generate keys with cargo tauri signer generate, use HTTPS endpoint only |
| Sidecar not found | externalBin not in tauri.conf.json or missing executable | Add path to bundle.externalBin, ensure binary is bundled |
| Feature works on desktop, breaks on mobile | Desktop-only API used | Check if API has mobile support — some plugins are desktop-only |
| 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> |
Deep-Dive References
- Security & permissions → `references/capabilities-reference.md`
- IPC decision guide → `references/ipc-patterns.md`
- Official plugins → `references/plugin-reference.md`
- Updater & distribution → `references/updater-distribution-reference.md`
- Tray, sidecars, deep links → `references/advanced-runtime-reference.md`
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
Plugin configuration — Some plugins require additional tauri.conf.json blocks (e.g., store, updater). Always check the specific plugin docs at v2.tauri.app/plugin/<plugin-name>/ for required config keys.
Project Structure
my-tauri-app/
├── src/ # Frontend source
├── src-tauri/
│ ├── src/
│ │ ├── main.rs # Thin passthrough — calls lib::run()
│ │ └── lib.rs # ALL application logic lives here
│ ├── capabilities/
│ │ └── default.json # Capability definitions (grant permissions here)
│ ├── tauri.conf.json # App configuration (devUrl, bundle, security)
│ ├── Cargo.toml # Rust dependencies
│ └── build.rs # Build script (required for tauri-build)
└── package.jsonWhy `lib.rs` owns all logic: Tauri replaces main() with #[cfg_attr(mobile, tauri::mobile_entry_point)] on mobile. All commands, state, and builder setup must live in lib.rs::run().
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 Result<T, E> and thiserror for type-safe error propagation across the IPC boundary. See `references/ipc-patterns.md` for full implementation details.
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),Use this skill in a project
Open any RiverX project, search the Skills panel for nodnarbnitram/claude-code-extensions/tauri-v2, and install it in one click.
