tauri2-guide/tauri2-docs/cheatsheet.md
Z User 5972c96ff9 Comprehensive improvements across all guide documents
definitive-guide.md:
- Add missing AsyncReadExt import in streaming example
- Fix v1/v2 WindowUrl table entry (v1 used WindowUrl not WebviewUrl)
- Rename 3-Step Pattern to 4-Step Pattern for plugins

agent-prompts.md:
- Add new Section 15: Prompt for debugging build/compile errors

error-resolution-guide.md:
- Improve A3 lib naming convention explanation
- Clarify G3 multiple definition root cause
- Add actionable WebView2 install commands to H1
- Add O9-O11 mobile build errors (Android SDK, macOS, CocoaPods)

cheatsheet.md:
- Add deep linking subsection with plugin example
- Add gen/schemas/ to file hierarchy
- Fix tokio::spawn -> tauri::async_runtime::spawn in WS example
- Bump tokio-tungstenite 0.21 -> 0.24

ws_server_client_inject_guide.md:
- Add WebSocket.OPEN guard to sendToAllWindows()

tauri2-links.md:
- Expand from 6 to 16 official documentation links
2026-05-30 20:45:20 +00:00

10 KiB

🚀 Tauri 2.0 Definitive Cheat Sheet

Tauri 2.0 features a complete architecture overhaul centered around mobile support (iOS/Android), modularized core plugins, and a strict Access Control List (ACL) security model. This cheat sheet covers core implementation patterns with up-to-date syntax.


🛠️ Project Setup & Structure

Tauri 2.0 splits frontend configurations and introduces modular Rust codebases via lib.rs.

CLI Management

# Initialize a new app
npm create tauri-app@latest

# Run development mode (Hot-reloads UI + Rust)
npm run tauri dev

# Build production artifacts
npm run tauri build

File Hierarchy

my-app/
├── src/                      # Frontend UI (React, Vue, Svelte, etc.)
├── src-tauri/                # Backend environment
│   ├── capabilities/         # NEW: Security capability JSON/TOML files
│   │   └── default.json      # Maps app windows to permissions
│   ├── gen/
│   │   └── schemas/           # Generated JSON schemas (referenced by $schema in capabilities)
│   ├── src/
│   │   ├── main.rs           # Minimal platform entry-point
│   │   └── lib.rs            # Core Application setup & commands
│   ├── Cargo.toml            # Rust dependencies
│   └── tauri.conf.json       # Desktop/Mobile global window configurations

🔒 Security: The Tauri 2.0 Permission System

Tauri 2.0 enforces explicit privileges. Frontend access to system commands or plugins requires configuring a Permission inside a Capability map.

1. Identify Plugin Permissions

Official plugins expose granular identifier strings:

  • Core Permissions: core:path:allow-home, core:event:allow-listen, core:event:allow-emit.
  • Plugin Permissions: <plugin-name>:allow-<command> (e.g., http:allow-request, fs:allow-read).

2. Define Capabilities (src-tauri/capabilities/default.json)

Capabilities attach permissions to specific application windows.

{
  "\$schema": "../gen/schemas/capability.json",
  "identifier": "main-window-capability",
  "description": "Allowed permissions for the primary UI",
  "windows": ["main"],
  "permissions": [
    "core:path:allow-home",
    "core:event:allow-listen",
    "core:event:allow-emit",
    "fs:allow-read",
    "websocket:allow-connect",
    "websocket:allow-send"
  ]
}

3. Fine-Grained Scopes

Scopes restrict what a plugin command can touch (e.g., locking file-system access down to a explicit directory). Define these inside your capability entries:

{
  "identifier": "secure-fs-scope",
  "windows": ["main"],
  "permissions": [
    {
      "identifier": "fs:allow-write",
      "allow": [{ "path": "\$APPDATA/logs/*" }]
    }
  ]
}

📡 WebSockets in Tauri 2.0

Tauri 2.0 approaches WebSockets from two distinct perspectives depending on requirements: utilizing the client plugin to query a external endpoint, or instantiating a native high-performance Rust WebSocket Server.

Option A: The Built-in WebSocket Client Plugin

Use this to safely initiate connections to external services from the frontend via a high-performance Rust proxy client.

1. Setup Backend Setup (src-tauri/src/lib.rs)

// Ensure 'tauri-plugin-websocket' is added to Cargo.toml
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_websocket::init()) // Initialize client plugin
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

2. Frontend Execution (JavaScript / TypeScript)

Ensure websocket:allow-connect and websocket:allow-send are added to your active capability configuration.

import WebSocket from '@tauri-apps/plugin-websocket';

// Connect to a server
const ws = await WebSocket.connect('ws://127.0.0.1:8080');

// Handle incoming messages
ws.addListener((msg) => {
  console.log('Received payload:', msg.data);
});

// Push data out
await ws.send({ type: 'Text', data: 'Hello World!' });

// Disconnect gracefully
await ws.disconnect();

Option B: Built-in Custom Rust WebSocket Server

If you want your Tauri application to act as a WebSocket Server (e.g., exposing a local port so mobile apps or local interfaces can stream data directly to your backend), spin up a Tokio-backed server thread inside the Tauri setup stage.

1. Dependencies (src-tauri/Cargo.toml)

[dependencies]
tauri = { version = "2.0", features = [] }
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.24" # Industry standard high-performance WS crate
futures-util = "0.3"

2. Server Implementation (src-tauri/src/lib.rs)

use tauri::Manager;
use tokio::net::TcpListener;
use futures-util::{StreamExt, SinkExt};
use tokio_tungstenite::accept_async;

async fn start_ws_server(port: &str) {
    let listener = TcpListener::bind(format!("127.0.0.1:{}", port))
        .await
        .expect("Failed to bind port");

    while let Ok((stream, _)) = listener.accept().await {
        tauri::async_runtime::spawn(async move {
            if let Ok(mut ws_stream) = accept_async(stream).await {
                println!("New connection established to Tauri WS Server!");
                while let Some(Ok(msg)) = ws_stream.next().await {
                    if msg.is_text() || msg.is_binary() {
                        // Eco back received payloads
                        let _ = ws_stream.send(msg).await;
                    }
                }
            }
        });
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            // Spin up native server safely using Tauri's async runtime (NOT tokio::spawn directly)
            tauri::async_runtime::spawn(start_ws_server("8080"));
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Inter-Process Communication (IPC) & State

Commands (Invoking Rust from Frontend)

Pass data directly via JSON-RPC structures across the isolation barrier.

// src-tauri/src/lib.rs
use std::sync::Mutex;
use tauri::State;

pub struct AppState {
    pub counter: Mutex<u32>,
}

#[tauri::command]
fn increment_counter(state: State<'_, AppState>, value: u32) -> u32 {
    let mut counter = state.counter.lock().unwrap();
    *counter += value;
    *counter // Return to frontend
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(AppState { counter: Mutex::new(0) }) // Inject State
        .invoke_handler(tauri::generate_handler![increment_counter])
        .run(tauri::generate_context!())
        .expect("failed to run app");
}
// Frontend Invoke Pattern
import { invoke } from '@tauri-apps/api/core';

const result = await invoke<number>('increment_counter', { value: 5 });
console.log(result); // Output: 5

Events (Asynchronous Global Communication)

Ideal for un-prompted data streaming from Rust to UI components.

// Backend Emit
use tauri::{Emitter, AppHandle};

fn stream_system_status(app_handle: &AppHandle, load: f32) {
    // Emits a global event to all active window contexts
    app_handle.emit("cpu-status", load).unwrap();
}
// Frontend Listen
import { listen } from '@tauri-apps/api/event';

// Make sure 'core:event:allow-listen' is in capabilities
const unlisten = await listen<number>('cpu-status', (event) => {
  console.log(`CPU utilization: ${event.payload}%`);
});

// Execute unlisten() later to clean up memory hooks

Multiple Window Spawning

// Rust implementation
use tauri::WebviewUrl;

fn create_extra_window(app: &tauri::AppHandle) {
    let _window = tauri::WebviewWindowBuilder::new(
        app,
        "secondary_panel",
        WebviewUrl::App("index.html/#/settings".into())
    )
    .title("Settings Panel")
    .inner_size(600.0, 400.0)
    .build()
    .unwrap();
}

Single Instance Lock

Ensures only one instance runs, focusing the existing window when a second launch is attempted.

// src-tauri/Cargo.toml: tauri-plugin-single-instance = "2"
// src-tauri/capabilities/default.json: add "single-instance:default" to permissions

// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            println!("Attempted alternative launch instances: {:?}, working dir: {:?}", args, cwd);
            // Focus primary window on secondary call triggers
            if let Some(window) = app.get_webview_window("main") {
                let _ = window.set_focus();
            }
        }))
        .run(tauri::generate_context!())
        .expect("failed execution");
}

Registers a custom URL scheme (e.g., my-app://open?token=xyz) so the OS routes external links into your running app.

1. Setup

# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-deep-link = "2"
// src-tauri/capabilities/default.json — add permission
{
  "permissions": ["deep-link:default"]
}
// src-tauri/tauri.conf.json — register the scheme
{
  "app": {
    "deepLink": {
      "desktop": {
        "schemes": ["my-app"]
      },
      "mobile": {
        "scheme": "myapp"
      }
    }
  }
}

2. Rust Handler

// src-tauri/src/lib.rs
use tauri_plugin_deep_link::DeepLinkExt;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_deep_link::init())
        .setup(|app| {
            // macOS / Linux / Windows desktop handler
            #[cfg(desktop)]
            app.deep_link().on_open_url(|url| {
                println!("Deep link opened: {}", url);
                // Parse URL and route to the appropriate handler
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("failed execution");
}

3. Frontend Listener (optional)

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

// The deep-link plugin emits 'deep-link://new-url' events
await listen('deep-link://new-url', (event) => {
  console.log('Received deep link:', event.payload);
});