tauri-v2
Use this skill whenever "tauri" appears in the user's message, or they reference tauri.conf.json, src-tauri, or @tauri-apps packages. Also use when converting a web app (React, Vue, Svelte, etc.) into a desktop application without Electron. Covers: creating Tauri v2 projects, Rust commands and IPC (invoke/events/channels), permissions and capabilities config, official Tauri plugins (fs, dialog, updater, stronghold, store, etc.), state management, window configuration, building/bundling for macOS/Windows/Linux/mobile, and debugging Tauri-specific errors like blank windows, lifetime errors in commands, or permission denied issues. Do NOT use for Electron, Flutter, Wails, or general Rust development unrelated to Tauri.
What this skill does
# Tauri v2 Development Skill
Tauri v2 lets you build tiny, fast apps for desktop (macOS, Windows, Linux) and mobile (iOS, Android) by combining a web frontend with a Rust backend. Apps use the system's native webview instead of bundling a browser engine, so a minimal app can be under 600KB.
**Default stack in this skill:** React + Vite + TypeScript frontend, Rust backend. Adapt if the user specifies a different framework.
## Quick Reference
- Config file: `src-tauri/tauri.conf.json`
- Rust entry: `src-tauri/src/lib.rs` (or `main.rs`)
- Capabilities: `src-tauri/capabilities/*.json`
- Permissions: `src-tauri/permissions/*.toml`
- JS API: `@tauri-apps/api`
- CLI: `@tauri-apps/cli` (npm) or `tauri-cli` (cargo)
For detailed reference on configuration, plugins, permissions, and mobile setup, see the `references/` directory. Read the relevant file when you need specifics beyond what's covered here.
## Prerequisites
Before creating a Tauri project, ensure these are installed:
**All platforms:** Rust via rustup (`curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh`), Node.js LTS
**macOS:** Xcode or Xcode Command Line Tools (`xcode-select --install`)
**Windows:** Microsoft C++ Build Tools (select "Desktop development with C++"), WebView2 Runtime (pre-installed on Windows 10+), `rustup default stable-msvc`
**Linux (Debian/Ubuntu):**
```bash
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
```
**Mobile (optional):** See `references/mobile.md` for Android Studio / iOS setup.
## Creating a Project
### Scaffolding (recommended)
```bash
npm create tauri-app@latest
# Choose: TypeScript/JavaScript → pnpm/npm → React → TypeScript
cd my-app
npm install
npm run tauri dev
```
This creates a project with `src/` (React frontend) and `src-tauri/` (Rust backend).
### Adding Tauri to an existing project
```bash
npm install -D @tauri-apps/cli@latest
npx tauri init
```
Answer the prompts for app name, dev server URL (e.g. `http://localhost:5173` for Vite), and frontend dist directory (e.g. `../dist`).
## Core Concepts
### Commands — Frontend calls Rust
Commands are the primary way the frontend talks to the backend. Define a Rust function with `#[tauri::command]`, register it, and call it from JS with `invoke`.
**Rust side** (`src-tauri/src/lib.rs`):
```rust
#[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");
}
```
**JS side**:
```typescript
import { invoke } from '@tauri-apps/api/core';
const greeting = await invoke<string>('greet', { name: 'World' });
```
Key rules:
- Command names must be unique across the app
- Rust args are snake_case, JS passes them as camelCase (e.g. `invoke_message` → `{ invokeMessage: '...' }`)
- Use `#[tauri::command(rename_all = "snake_case")]` to keep snake_case on both sides
- Commands can be `async` for non-blocking work
- Return `Result<T, String>` (or custom error types) for error handling — `Err` rejects the JS promise
### Events — Rust notifies the frontend
For fire-and-forget notifications or streaming updates, use the event system:
**Rust emitting:**
```rust
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn start_download(app: AppHandle, url: String) {
app.emit("download-progress", 50).unwrap();
}
```
**JS listening:**
```typescript
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<number>('download-progress', (event) => {
console.log(`Progress: ${event.payload}%`);
});
// Call unlisten() to stop listening
```
### Channels — High-throughput streaming
For ordered, high-throughput data (file reads, progress), use channels instead of events:
```rust
use tauri::ipc::Channel;
#[tauri::command]
async fn stream_data(on_chunk: Channel<Vec<u8>>) {
for chunk in data_chunks {
on_chunk.send(chunk).unwrap();
}
}
```
```typescript
import { invoke, Channel } from '@tauri-apps/api/core';
const onChunk = new Channel<Uint8Array>();
onChunk.onmessage = (chunk) => { /* handle chunk */ };
await invoke('stream_data', { onChunk });
```
### State Management
Register state with `.manage()` and inject it into commands with `State<>`:
```rust
use std::sync::Mutex;
use tauri::State;
struct AppState {
count: u32,
}
#[tauri::command]
fn increment(state: State<'_, Mutex<AppState>>) -> u32 {
let mut s = state.lock().unwrap();
s.count += 1;
s.count
}
// In run():
tauri::Builder::default()
.manage(Mutex::new(AppState { count: 0 }))
.invoke_handler(tauri::generate_handler![increment])
```
Important: Tauri wraps state in `Arc` automatically — don't wrap in `Arc` yourself. Use `Mutex` for mutable state. Use `std::sync::Mutex` (not tokio's) unless you need to hold the lock across `.await` points.
**Type mismatch pitfall:** If you `.manage(Mutex::new(state))` but inject `State<'_, AppState>` (without `Mutex`), it panics at runtime, not compile time. Use a type alias to prevent this:
```rust
type AppState = Mutex<AppStateInner>;
```
## Configuration — tauri.conf.json
The config file lives at `src-tauri/tauri.conf.json`. Key sections:
```jsonc
{
"productName": "My App",
"version": "1.0.0",
"identifier": "com.example.myapp", // Required, reverse domain notation
"build": {
"devUrl": "http://localhost:5173", // Dev server URL
"frontendDist": "../dist", // Production build output
"beforeDevCommand": "npm run dev", // Starts your dev server
"beforeBuildCommand": "npm run build" // Builds frontend for production
},
"app": {
"windows": [{
"title": "My App",
"width": 1024,
"height": 768
}],
"security": {
"capabilities": [] // Reference capability files here
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.icns", "icons/icon.ico"]
}
}
```
Platform-specific overrides: `tauri.linux.conf.json`, `tauri.windows.conf.json`, `tauri.macos.conf.json` — these merge with the main config.
See `references/config.md` for the full configuration reference.
## Security — Permissions & Capabilities
Tauri v2 has a capability-based security model. By default, the frontend cannot call any commands — you must explicitly grant access.
### Capabilities
A capability grants a set of permissions to specific windows. Create JSON files in `src-tauri/capabilities/`:
```json
{
"identifier": "main-capability",
"description": "Permissions for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"my-app:default"
]
}
```
### Permissions for your own commands
Define in `src-tauri/permissions/default.toml`:
```toml
[default]
description = "Default permissions for the app"
permissions = ["allow-greet", "allow-increment"]
```
Each command you register automatically gets `allow-<command-name>` and `deny-<command-name>` identifiers.
### Plugin permissions
Plugins ship their own permissions. Add them to your capability:
```json
{
"permissions": [
"core:default",
"fs:default",
"fs:allow-read-file",
"dialog:default",
"shell:allow-open"
]
}
```
### Scoped permissions
Restrict commands to specific paths/resources:
```toml
[[permission]]
identifier = "scope-home"
description = "Access files in $HOME"
[[scope.allow]]
path = "$HOME/*"
```
See `references/permissions.md` for the full permissions reference.
## Plugins
Install official plugins via npm + cargo:
```bash
npm install @tauri-apps/plugin-fs
# The Cargo dependency is added automatically by the CLI
```
Register in Rust:
```rust
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
```
Use in JS:
```typescript
import {Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.