tauri-v2

Tauri v2+ Development Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tauri-v2" with this command: npx skills add nodnarbnitram/claude-code-extensions/nodnarbnitram-claude-code-extensions-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) }

#[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 :

// 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

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.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:

#[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 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

[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 for full implementation details.

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()) }

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.

use 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 for more complex state patterns.

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

Events are fire-and-forget notifications. See references/ipc-patterns.md for bidirectional examples.

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

Channels provide high-frequency, typed streaming from Rust to Frontend. See references/ipc-patterns.md for full implementation details.

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 });

Window Access Pattern

Tauri v2 uses WebviewWindow for unified window and webview management.

use 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

  • Permission patterns and examples

  • ipc-patterns.md

  • Complete IPC examples

  • plugin-reference.md

  • Official plugin install, registration, and permission strings

  • updater-distribution-reference.md

  • Signing, HTTPS requirements, and bundle shipping

  • 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 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 for full install + permission details for all official plugins.

Official Documentation

  • Tauri v2+ Documentation

  • Commands Reference

  • Capabilities & Permissions

  • Configuration Reference

Troubleshooting

White Screen on Launch

Symptoms: App launches but shows blank white screen

Solution:

  • Verify devUrl matches your frontend dev server port

  • Check beforeDevCommand runs 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

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:

  • Check the plugin page at v2.tauri.app/plugin/<name>/ for platform support matrix

  • Common desktop-only items: System tray (TrayIconBuilder ), window labels/multi-window, some shell plugin features

  • Mobile-safe patterns: IPC commands/events/channels work on all platforms; tauri::AppHandle is mobile-safe

  • Conditional compilation: Use #[cfg(desktop)] / #[cfg(mobile)] for platform-specific Rust logic

#[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 platforms

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

arxiv-paper-writer

Use this skill whenever the user wants Claude Code to write, scaffold, compile, debug, or review an arXiv-style academic paper, especially survey papers with LaTeX, BibTeX citations, TikZ figures, tables, and PDF output. This skill should trigger for requests like writing a full paper, creating an arXiv paper project, turning a research topic into a LaTeX manuscript, reproducing the Paper-Write-Skill-Test agent-survey workflow, or setting up a Windows/Linux Claude Code paper-writing loop.

Archived SourceRecently Updated
Coding

cli-proxy-troubleshooting

排查 CLI Proxy API(codex-api-proxy)的配置、认证、模型注册和请求问题。适用场景包括:(1) AI 请求报错 unknown provider for model, (2) 模型列表中缺少预期模型, (3) codex-api-key/auth-dir 配置不生效, (4) CLI Proxy 启动后 AI 无法调用, (5) 认证成功但请求失败或超时。包含源码级排查方法:模型注册表架构、认证加载链路、 SanitizeCodexKeys 规则、常见错误的真实根因。

Archived SourceRecently Updated
Coding

visual-summary-analysis

Performs AI analysis on input video clips/image content and generates a smooth, natural scene description. | 视觉摘要智述技能,对传入的视频片段/图片内容进行AI分析,生成一段通顺自然的场景描述内容

Archived SourceRecently Updated
Coding

frontend-skill

全能高级前端研发工程师技能。擅长AI时代前沿技术栈(React最新 + shadcn/ui + Tailwind CSS v4 + TypeScript + Next.js),精通动效库与交互特效开发。采用Glue Code风格快速实现代码,强调高质量产品体验与高度友好的UI视觉规范。在组件调用、交互特效、全局Theme上保持高度规范:绝不重复造轮子,相同逻辑出现两次即封装为组件。具备安全意识,防范各类注入攻击。开发页面具有高度自适应能力,响应式设计贯穿始终。当用户无特殊技术栈要求时,默认采用主流前沿技术栈。

Archived SourceRecently Updated