Skip to content
Agent Orchestration
Skill

/tauri-app-dev

Expert guidance for building cross-platform desktop applications with Tauri 2.0 and Rust. Use when developing Tauri apps including commands and IPC, file system operations, window management, state management, system tray, menus, plugin development, security configuration

From plugin
vmark
49819 skills9 agents7 commands2 MCP
Install
$ npx -y skills add xiaolai/vmark --skill tauri-app-dev --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/tauri-app-dev

Context preview

The summary Claude sees to decide when to auto-load this skill.

Expert guidance for building cross-platform desktop applications with Tauri 2.0 and Rust. Use when developing Tauri apps including commands and IPC, file system operations, window management, state management, system tray, menus, plugin development, security configuration

SKILL.md

tauri-app-dev.SKILL.md
name: tauri-app-dev
description: Expert guidance for building cross-platform desktop applications with Tauri 2.0 and Rust. Use when developing Tauri apps including commands and IPC, file system operations, window management, state management, system tray, menus, plugin development, security configuration (capabilities/permissions), bundling/distribution, and auto-updates. Covers patterns for editor applications requiring file dialogs, native menus, and frontend-backend communication.

Tauri 2.0 App Development

Tauri is a framework for building small, fast, secure desktop apps using web frontends and Rust backends.

Architecture Overview

┌─────────────────────────────────────────┐
│           Frontend (Webview)            │
│     HTML/CSS/JS • React/Vue/Svelte      │
└────────────────┬────────────────────────┘
                 │ IPC (invoke/events)
┌────────────────▼────────────────────────┐
│           Tauri Core (Rust)             │
│  Commands • State • Plugins • Events    │
└────────────────┬────────────────────────┘
                 │ TAO (windows) + WRY (webview)
┌────────────────▼────────────────────────┐
│          Operating System               │
│   macOS • Windows • Linux • Mobile      │
└─────────────────────────────────────────┘

Project Structure

my-app/
├── src/                    # Frontend source
├── src-tauri/
│   ├── Cargo.toml          # Rust dependencies
│   ├── tauri.conf.json     # Tauri configuration
│   ├── capabilities/       # Security permissions (v2)
│   │   └── default.json
│   ├── src/
│   │   ├── main.rs         # Desktop entry point
│   │   └── lib.rs          # Main app logic + mobile entry
│   └── icons/
└── package.json

Commands (Frontend → Rust)

Define commands in Rust with `#[tauri::command]`:

// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
    format!("Hello, {}!", name)
}

#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
    std::fs::read_to_string(&path).map_err(|e| e.to_string())
}

pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet, read_file])
        .run(tauri::generate_context!())
        .expect("error running app");
}

Call from frontend (direct):

import { invoke } from '@tauri-apps/api/core';

const greeting = await invoke<string>('greet', { name: 'World' });
const content = await invoke<string>('read_file', { path: '/tmp/test.txt' });

**Project convention:** Wrap `invoke()` with TanStack Query for caching and state management:

import { useQuery, useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';

// Query (read operations)
const { data: content } = useQuery({
  queryKey: ['file', path],
  queryFn: () => invoke<string>('read_file', { path }),
});

// Mutation (write operations)
const { mutate: saveFile } = useMutation({
  mutationFn: (content: string) => invoke('write_file', { path, content }),
});

**Key rules:**

  • Arguments must implement `serde::Deserialize`
  • Return types must implement `serde::Serialize`
  • Use `Result<T, E>` for fallible operations
  • Async commands run on thread pool (non-blocking)
  • Snake_case in Rust → camelCase in JS arguments

State Management

Share state across commands:

use std::sync::Mutex;
use tauri::State;

struct AppState {
    counter: Mutex<i32>,
    db: Mutex<Option<Database>>,
}

#[tauri::command]
fn increment(state: State<'_, AppState>) -> i32 {
    let mut counter = state.counter.lock().unwrap();
    *counter += 1;
    *counter
}

pub fn run() {
    tauri::Builder::default()
        .manage(AppState {
            counter: Mutex::new(0),
            db: Mutex::new(None),
        })
        .invoke_handler(tauri::generate_handler![increment])
        .run(tauri::generate_context!())
        .expect("error running app");
}

**Access via AppHandle** (for background threads):

use tauri::Manager;

#[tauri::command]
async fn background_task(app: tauri::AppHandle) {
    let state = app.state::<AppState>();
    // use state...
}

Events (Rust → Frontend)

Emit events from Rust:

use tauri::Emitter;

#[tauri::command]
fn start_process(app: tauri::AppHandle) {
    std::thread::spawn(move || {
        for i in 0..100 {
            app.emit("progress", i).unwrap();
            std::thread::sleep(std::time::Duration::from_millis(50));
        }
        app.emit("complete", "Done!").unwrap();
    });
}

Listen in frontend:

import { listen } from '@tauri-apps/api/event';

const unlisten = await listen<number>('progress', (event) => {
    console.log(`Progress: ${event.payload}%`);
});

// Clean up when done
unlisten();

Essential Plugins

Install plugins: `cargo add <plugin>` in src-tauri, `pnpm add <package>` in frontend.

| Plugin | Cargo Crate | NPM Package | Purpose | |--------|-------------|-------------|---------| | File System | `tauri-plugin-fs` | `@tauri-apps/plugin-fs` | Read/write files | | Dialog | `tauri-plugin-dialog` | `@tauri-apps/plugin-dialog` | Open/save dialogs | | Clipboard | `tauri-plugin-clipboard-manager` | `@tauri-apps/plugin-clipboard-manager` | Copy/paste | | Shell | `tauri-plugin-shell` | `@tauri-apps/plugin-shell` | Run external commands | | Store | `tauri-plugin-store` | `@tauri-apps/plugin-store` | Key-value persistence | | Updater | `tauri-plugin-updater` | `@tauri-apps/plugin-updater` | Auto-updates |

Register in Rust:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_clipboard_manager::init())
        .run(tauri::generate_context!())
        .expect("error running app");
}

Security: Capabilities & Permissions

Tauri 2.0 uses capabilities (in `src-tauri/capabilities/`) to control what APIs each window can access.

**src-tauri/capabilities/default.json:**

Read more
Ships withvmark

The Plain-Text Workspace Where Humans and AI Collaborate Free. Local-first. Format-aware. VMark is the plain-text workspace where humans and AI collaborate.

Get the whole plugin