npx skills add https://github.com/nodnarbnitram/claude-code-extensions --skill tauri-v2How Tauri V2 fits into a Paperclip company.
Tauri V2 drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
SKILL.md497 linesExpandCollapse
---name: tauri-v2description: "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."version: 1.0.1--- # 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 1. Permission denied errors from missing capabilities2. IPC failures from unregistered commands in `generate_handler!`3. State management panics from type mismatches4. Mobile build failures from missing Rust targets5. White screen issues from misconfigured dev URLs ## Quick Start ### Step 1: Create a Tauri Command ```rust// 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.rs` should only be a thin passthrough — all application logic lives in `lib.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 replaces `main()` with `mobile_entry_point` on mobile targets. ### Step 2: Call from Frontend ```typescriptimport { 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 ```json// 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.rs` for shared code (required for mobile builds)- Use `#[cfg_attr(mobile, tauri::mobile_entry_point)]` on `pub fn run()` in `lib.rs` for 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:**```rust#[tauri::command]async fn bad(name: &str) -> String { // Compile error! name.to_string()}``` **Correct - Owned type:**```rust#[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`](references/capabilities-reference.md)- **IPC decision guide** → [`references/ipc-patterns.md`](references/ipc-patterns.md)- **Official plugins** → [`references/plugin-reference.md`](references/plugin-reference.md)- **Updater & distribution** → [`references/updater-distribution-reference.md`](references/updater-distribution-reference.md)- **Tray, sidecars, deep links** → [`references/advanced-runtime-reference.md`](references/advanced-runtime-reference.md) ## Configuration Reference ### tauri.conf.json ```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 port- `app.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.json``` **Why `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 ```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 `Result<T, E>` and `thiserror` for type-safe error propagation across the IPC boundary. See [`references/ipc-patterns.md`](references/ipc-patterns.md) for full implementation details. ```rustuse 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())}``` ### Serde Boundary Rules All command arguments must implement `serde::Deserialize`, and return types must implement `serde::Serialize`. This is how Tauri bridges JSON over the IPC boundary. ```rustuse serde::{Deserialize, Serialize}; #[derive(Deserialize)]struct CreateUserArgs { name: String, email: String, role: Option<String>, // Optional fields use Option<T>} #[derive(Serialize)]struct User { id: u64, name: String,} #[tauri::command]fn create_user(args: CreateUserArgs) -> Result<User, String> { Ok(User { id: 1, name: args.name })}``` **Common serde pitfalls:**- Field names are camelCase in JS, snake_case in Rust — Tauri automatically converts between them- `Option<T>` maps to optional JS arguments (can be `undefined` or `null`)- Complex enums need `#[serde(tag = "type")]` or similar to be JSON-safe- Error types must also implement `Serialize` (see Error Handling Pattern above) ### State Management Pattern Tauri state manages application data across commands. See [`references/ipc-patterns.md`](references/ipc-patterns.md) for more complex state patterns. ```rustuse 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 Events are fire-and-forget notifications. See [`references/ipc-patterns.md`](references/ipc-patterns.md) for bidirectional examples. ```rustuse 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(); });}``` ```typescriptimport { 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 Channels provide high-frequency, typed streaming from Rust to Frontend. See [`references/ipc-patterns.md`](references/ipc-patterns.md) for full implementation details. ```rustuse 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();}``` ```typescriptimport { 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 });``` ### Window Access Pattern Tauri v2 uses `WebviewWindow` for unified window and webview management. ```rustuse tauri::Manager; #[tauri::command]fn focus_window(app: tauri::AppHandle) { if let Some(window) = app.get_webview_window("main") { let _ = window.set_focus(); }}``` **Why this matters:** Use `tauri::WebviewWindow` and `app.get_webview_window("label")` in v2 — the v1 `app.get_window()` API is removed in v2. ## Bundled Resources ### References Located in `references/`:- [`capabilities-reference.md`](references/capabilities-reference.md) - Permission patterns and examples- [`ipc-patterns.md`](references/ipc-patterns.md) - Complete IPC examples- [`plugin-reference.md`](references/plugin-reference.md) - Official plugin install, registration, and permission strings- [`updater-distribution-reference.md`](references/updater-distribution-reference.md) - Signing, HTTPS requirements, and bundle shipping- [`advanced-runtime-reference.md`](references/advanced-runtime-reference.md) - `TrayIconBuilder`, sidecars, deep links, and asset protocols > **Note:** For deep dives on specific topics, see the reference files above. ## Dependencies ### Required | Package | Version | Purpose ||---------|---------|---------|| `@tauri-apps/cli` | ^2 (v2+) | CLI tooling || `@tauri-apps/api` | ^2 (v2+) | Frontend APIs || `tauri` | ^2 (v2+) | Rust core || `tauri-build` | ^2 (v2+) | Build scripts | *\*Last verified: 2026-04-02. Always check [official changelog](https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/CHANGELOG.md) for feature timing.* ### Optional (Plugins) | Package | Version | Purpose | Key Permission ||---------|---------|---------|----------------|| `tauri-plugin-fs` | ^2 (v2+) | File system access | `fs:default` || `tauri-plugin-dialog` | ^2 (v2+) | Native dialogs | `dialog:default` || `tauri-plugin-shell` | ^2 (v2+) | Shell commands, open URLs | `shell:default` || `tauri-plugin-http` | ^2 (v2+) | HTTP client | `http:default` || `tauri-plugin-store` | ^2 (v2+) | Key-value storage | `store:default` | > **Plugin permissions are mandatory.** Installing a plugin without adding its permission string to a capability file causes silent runtime failures. See [`references/plugin-reference.md`](references/plugin-reference.md) for full install + permission details for all official plugins. ## Official Documentation - [Tauri v2+ Documentation](https://v2.tauri.app/)- [Commands Reference](https://v2.tauri.app/develop/calling-rust/)- [Capabilities & Permissions](https://v2.tauri.app/security/capabilities/)- [Configuration Reference](https://v2.tauri.app/reference/config/) ## Troubleshooting ### White Screen on Launch **Symptoms:** App launches but shows blank white screen **Solution:**1. Verify `devUrl` matches your frontend dev server port2. Check `beforeDevCommand` runs your dev server3. Open DevTools (Cmd+Option+I / Ctrl+Shift+I) to check for errors ### Command Returns Undefined **Symptoms:** `invoke()` returns undefined instead of expected value **Solution:**1. Verify command is in `generate_handler![]`2. Check Rust command actually returns a value3. 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:**```bash# Android targetsrustup 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``` ### Desktop vs Mobile Behavioral Differences Not all Tauri APIs and plugins support mobile (iOS/Android). Before using any plugin or API in a mobile build: 1. **Check the plugin page** at `v2.tauri.app/plugin/<name>/` for platform support matrix2. **Common desktop-only items**: System tray (`TrayIconBuilder`), window labels/multi-window, some shell plugin features3. **Mobile-safe patterns**: IPC commands/events/channels work on all platforms; `tauri::AppHandle` is mobile-safe4. **Conditional compilation**: Use `#[cfg(desktop)]` / `#[cfg(mobile)]` for platform-specific Rust logic ```rust#[tauri::command]fn platform_info() -> String { #[cfg(desktop)] return "desktop".to_string(); #[cfg(mobile)] return "mobile".to_string();}``` ## 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 platforms1password
Install 1password skill for Claude Code from steipete/clawdis.
3d Web Experience
Install 3d Web Experience skill for Claude Code from sickn33/antigravity-awesome-skills.
Ab Test Setup
This handles the full A/B testing workflow from hypothesis formation to statistical analysis. It walks you through proper test design, calculates sample sizes,