Split guide files into <8k token chunks for small LLMs

- definitive-guide.md → 2 parts (core/arch/config/commands + windows/plugins/websocket/state/migration)
- error-resolution-guide.md → 2 parts (build/config/plugin errors + runtime/platform/mobile errors)
- agent-prompts.md → 2 parts (system prompt/common tasks + advanced tasks/checklist/migration map)
- Updated README.md with new file structure and per-topic routing table
- All files under ~7.5k tokens, suitable for 8k context windows
- Cross-reference links between split parts
This commit is contained in:
Z User 2026-05-30 20:52:16 +00:00
parent 5972c96ff9
commit 368a3db23d
7 changed files with 2099 additions and 1988 deletions

155
README.md
View File

@ -12,79 +12,120 @@ A comprehensive, self-contained reference library for building applications with
```text
tauri2-vin-ws-temp/
├── tauri2-guide/ # AUTHORITATIVE GUIDES — start here
│ ├── definitive-guide.md # Full reference (architecture, config, commands, events, plugins, windows, state, migration)
│ ├── agent-prompts.md # Copy-paste prompts for instructing LLM agents to build Tauri 2.0 apps
│ └── error-resolution-guide.md # Every known error category with exact messages, root causes, and solutions
│ │
│ │ ── Definitive Developer Guide (2 parts) ──────────────────────
│ ├── 01-core-architecture-config-commands-ipc.md # Sections 17
│ ├── 02-windows-plugins-websocket-state-migration.md # Sections 815
│ │
│ │ ── Agent Prompts (2 parts) ──────────────────────────────────
│ ├── 01-system-prompt-and-common-prompts.md # System prompt + prompts 17
│ ├── 02-advanced-prompts-checklist-migration.md # Prompts 815 + checklist + API map
│ │
│ │ ── Error Resolution Guide (2 parts) ────────────────────────
│ ├── 01-build-config-permission-plugin-errors.md # Categories AH
│ └── 02-runtime-platform-mobile-errors.md # Categories IR + Top 25
├── tauri2-docs/ # SUPPLEMENTARY REFERENCES
│ ├── cheatsheet.md # Quick-reference cheat sheet (commands, config, IPC, WebSocket patterns)
│ ├── ws_server_client_inject_guide.md # Deep-dive: native WebSocket server, frontend clients, script injection
│ └── tauri2-links.md # Curated links to official Tauri 2.0 documentation pages
│ ├── cheatsheet.md # Quick-reference cheat sheet
│ ├── ws_server_client_inject_guide.md # WebSocket server, client, injection deep-dive
│ └── tauri2-links.md # Curated links to official docs
└── README.md # THIS FILE — navigation index for LLMs and developers
└── README.md # THIS FILE — navigation index
```
All guide files are sized under **8k tokens** so small LLMs can load them without losing context. Each split file includes cross-reference links to its companion part.
---
## How to Use This Repository
### For LLMs / AI Agents
This repository is designed to be **searched directly**. When you receive a Tauri 2.0 question, use the table below to locate the most relevant document, then read the specific section referenced.
This repository is designed to be **searched directly**. When you receive a Tauri 2.0 question, use the table below to locate the most relevant file, then read the specific section referenced.
**Important constraints for agents:**
- You **cannot compile or run code** in a sandbox. Verify correctness using logic, API pattern matching, and cross-referencing these documents.
- All code in this repository is **Tauri 2.0 only** — do NOT use v1 patterns (see the migration section in `definitive-guide.md` for the full v1-to-v2 diff).
- All code in this repository is **Tauri 2.0 only** — do NOT use v1 patterns (see the migration section for the full v1-to-v2 diff).
- Every plugin requires 4 things: (1) Rust crate in `Cargo.toml`, (2) `.plugin()` registration in the Builder, (3) npm package, (4) permissions in capabilities.
### For Developers
Use the **Definitive Guide** (`tauri2-guide/definitive-guide.md`) as your primary reference. It covers every aspect of Tauri 2.0 development end-to-end. For specific tasks, use the **Agent Prompts** as templates. For debugging, search the **Error Resolution Guide** by error message.
Use the **Definitive Guide** Part 1 as your primary reference for core concepts. Part 2 covers advanced topics. For specific tasks, use the **Agent Prompts** as templates. For debugging, search the **Error Resolution Guide** by error message.
---
## Document Index — What to Read for Each Topic
| Topic | File | Key Sections |
|-------|------|-------------|
| **Architecture overview, project structure, scaffolding** | `tauri2-guide/definitive-guide.md` | Sections 12 |
| **Configuration (`tauri.conf.json` v2 schema)** | `tauri2-guide/definitive-guide.md` | Section 3 |
| **Capabilities & permissions (ACL security model)** | `tauri2-guide/definitive-guide.md` | Section 4 |
| **Rust backend: `lib.rs`, commands, `Builder` pattern** | `tauri2-guide/definitive-guide.md` | Section 5 |
| **Frontend integration (JS imports, `invoke`, plugins)** | `tauri2-guide/definitive-guide.md` | Section 6 |
| **IPC: commands, events, channels, streaming** | `tauri2-guide/definitive-guide.md` | Section 7 |
| **Window management (multi-window, external URLs)** | `tauri2-guide/definitive-guide.md` | Section 8 |
| **Plugin ecosystem (all 33+ official plugins)** | `tauri2-guide/definitive-guide.md` | Section 9 |
| **Native WebSocket server (Tokio + broadcast)** | `tauri2-guide/definitive-guide.md` Section 10 / `tauri2-docs/ws_server_client_inject_guide.md` Part 1 |
| **Script injection into webview windows** | `tauri2-guide/definitive-guide.md` Section 11 / `tauri2-docs/ws_server_client_inject_guide.md` Part 3 |
| **State management (Mutex, managed state)** | `tauri2-guide/definitive-guide.md` | Section 12 |
| **v1 to v2 migration (full API diff)** | `tauri2-guide/definitive-guide.md` Section 13 / `tauri2-guide/agent-prompts.md` Section 14 |
| **Anti-patterns to avoid** | `tauri2-guide/definitive-guide.md` | Section 14 |
| **Quick reference cheat sheet** | `tauri2-docs/cheatsheet.md` | All sections |
| **WebSocket server + client + injection deep-dive** | `tauri2-docs/ws_server_client_inject_guide.md` | Parts 13 |
| **Links to official docs (migration, security, config)** | `tauri2-docs/tauri2-links.md` | All entries |
| **Agent system prompt (mandatory prefix for all tasks)** | `tauri2-guide/agent-prompts.md` | Section 1 |
| **Task-specific agent prompts (scaffold, plugin, command, etc.)** | `tauri2-guide/agent-prompts.md` | Sections 212 |
| **Agent verification checklist** | `tauri2-guide/agent-prompts.md` | Section 13 |
| **Rust compilation errors (Cargo build failures)** | `tauri2-guide/error-resolution-guide.md` | Category A |
| **v1-to-v2 migration compile errors** | `tauri2-guide/error-resolution-guide.md` | Category B |
| **Async/thread safety errors (Mutex, Send, lifetime)** | `tauri2-guide/error-resolution-guide.md` | Category C |
| **Config errors (`tauri.conf.json` validation)** | `tauri2-guide/error-resolution-guide.md` | Category D |
| **Permission/capability errors ("not allowed")** | `tauri2-guide/error-resolution-guide.md` | Category E |
| **Plugin integration errors** | `tauri2-guide/error-resolution-guide.md` | Category F |
| **IPC / command runtime errors** | `tauri2-guide/error-resolution-guide.md` | Category G |
| **Webview & window runtime errors** | `tauri2-guide/error-resolution-guide.md` | Category H |
| **Event system errors** | `tauri2-guide/error-resolution-guide.md` | Category I |
| **Content Security Policy (CSP) errors** | `tauri2-guide/error-resolution-guide.md` | Category J |
| **Frontend integration errors (Vite, Next.js, SvelteKit)** | `tauri2-guide/error-resolution-guide.md` | Category K |
| **WebSocket errors** | `tauri2-guide/error-resolution-guide.md` | Category L |
| **Path & asset protocol errors** | `tauri2-guide/error-resolution-guide.md` | Category M |
| **Shell & sidecar errors** | `tauri2-guide/error-resolution-guide.md` | Category N |
| **Mobile build errors (iOS & Android)** | `tauri2-guide/error-resolution-guide.md` | Category O |
| **Platform-specific build errors** | `tauri2-guide/error-resolution-guide.md` | Category P |
| **Panics & runtime crashes** | `tauri2-guide/error-resolution-guide.md` | Category Q |
| **Error handling best practices** | `tauri2-guide/error-resolution-guide.md` | Category R |
| **Top 25 most common errors (quick lookup)** | `tauri2-guide/error-resolution-guide.md` | Quick-Reference section |
### Definitive Developer Guide
| Topic | File | Sections |
|-------|------|----------|
| Architecture overview, project structure, scaffolding | `01-core-architecture-config-commands-ipc.md` | 12 |
| Configuration (`tauri.conf.json` v2 schema) | `01-core-architecture-config-commands-ipc.md` | 3 |
| Capabilities & permissions (ACL security model) | `01-core-architecture-config-commands-ipc.md` | 4 |
| Rust backend: `lib.rs`, commands, Builder pattern | `01-core-architecture-config-commands-ipc.md` | 5 |
| Frontend integration (JS imports, invoke, plugins) | `01-core-architecture-config-commands-ipc.md` | 6 |
| IPC: commands, events, channels, streaming | `01-core-architecture-config-commands-ipc.md` | 7 |
| Window management (multi-window, external URLs) | `02-windows-plugins-websocket-state-migration.md` | 8 |
| Plugin ecosystem (all 33+ official plugins) | `02-windows-plugins-websocket-state-migration.md` | 9 |
| Native WebSocket server (Tokio + broadcast) | `02-windows-plugins-websocket-state-migration.md` / `tauri2-docs/ws_server_client_inject_guide.md` | 10 |
| Script injection into webview windows | `02-windows-plugins-websocket-state-migration.md` / `tauri2-docs/ws_server_client_inject_guide.md` | 11 |
| State management (Mutex, managed state) | `02-windows-plugins-websocket-state-migration.md` | 12 |
| v1 to v2 migration (full API diff) | `02-windows-plugins-websocket-state-migration.md` | 13 |
| Anti-patterns to avoid | `02-windows-plugins-websocket-state-migration.md` | 14 |
| Quick reference cheat sheet | `02-windows-plugins-websocket-state-migration.md` | 15 |
### Agent Prompts
| Topic | File | Section |
|-------|------|---------|
| Universal system prompt (REQUIRED prefix) | `01-system-prompt-and-common-prompts.md` | 1 |
| Scaffold a new Tauri 2.0 project | `01-system-prompt-and-common-prompts.md` | 2 |
| Add a Tauri plugin | `01-system-prompt-and-common-prompts.md` | 3 |
| Create a custom command (IPC) | `01-system-prompt-and-common-prompts.md` | 4 |
| Add state management | `01-system-prompt-and-common-prompts.md` | 5 |
| Implement event-based communication | `01-system-prompt-and-common-prompts.md` | 6 |
| Create multi-window app | `01-system-prompt-and-common-prompts.md` | 7 |
| Build a WebSocket server in Rust | `02-advanced-prompts-checklist-migration.md` | 8 |
| Implement script injection | `02-advanced-prompts-checklist-migration.md` | 9 |
| Set up capabilities & permissions | `02-advanced-prompts-checklist-migration.md` | 10 |
| Migrate a v1 app to v2 | `02-advanced-prompts-checklist-migration.md` | 11 |
| Full app build (end-to-end) | `02-advanced-prompts-checklist-migration.md` | 12 |
| Verification checklist for agents | `02-advanced-prompts-checklist-migration.md` | 13 |
| v1 vs v2 API map | `02-advanced-prompts-checklist-migration.md` | 14 |
| Debug a Tauri 2.0 build/compile error | `02-advanced-prompts-checklist-migration.md` | 15 |
### Error Resolution Guide
| Topic | File | Category |
|-------|------|----------|
| Rust compilation errors (Cargo build failures) | `01-build-config-permission-plugin-errors.md` | A |
| v1-to-v2 migration compile errors | `01-build-config-permission-plugin-errors.md` | B |
| Async/thread safety errors (Mutex, Send, lifetime) | `01-build-config-permission-plugin-errors.md` | C |
| Config errors (`tauri.conf.json` validation) | `01-build-config-permission-plugin-errors.md` | D |
| Permission/capability errors ("not allowed") | `01-build-config-permission-plugin-errors.md` | E |
| Plugin integration errors | `01-build-config-permission-plugin-errors.md` | F |
| IPC / command runtime errors | `01-build-config-permission-plugin-errors.md` | G |
| Webview & window runtime errors | `01-build-config-permission-plugin-errors.md` | H |
| Event system errors | `02-runtime-platform-mobile-errors.md` | I |
| Content Security Policy (CSP) errors | `02-runtime-platform-mobile-errors.md` | J |
| Frontend integration errors (Vite, Next.js, SvelteKit) | `02-runtime-platform-mobile-errors.md` | K |
| WebSocket errors | `02-runtime-platform-mobile-errors.md` | L |
| Path & asset protocol errors | `02-runtime-platform-mobile-errors.md` | M |
| Shell & sidecar errors | `02-runtime-platform-mobile-errors.md` | N |
| Mobile build errors (iOS & Android) | `02-runtime-platform-mobile-errors.md` | O |
| Platform-specific build errors | `02-runtime-platform-mobile-errors.md` | P |
| Panics & runtime crashes | `02-runtime-platform-mobile-errors.md` | Q |
| Error handling best practices | `02-runtime-platform-mobile-errors.md` | R |
| Top 25 most common errors (quick lookup) | `02-runtime-platform-mobile-errors.md` | Quick-Reference |
### Supplementary References
| Topic | File |
|-------|------|
| Quick-reference cheat sheet (commands, config, IPC, WebSocket patterns) | `tauri2-docs/cheatsheet.md` |
| WebSocket server + client + injection deep-dive | `tauri2-docs/ws_server_client_inject_guide.md` |
| Links to official docs (migration, security, config) | `tauri2-docs/tauri2-links.md` |
---
@ -110,7 +151,7 @@ Tauri 2.0 is a **complete rewrite**, not an incremental update. The most common
| Event emit | `window.emit()` | `app.emit()` (global) / `app.emit_to()` (targeted) |
| Rust APIs | `tauri::api::*` modules | Everything moved to plugins |
**Full migration map**: See `tauri2-guide/agent-prompts.md` Section 14 (v1 vs v2 API Map).
**Full migration map**: See `02-advanced-prompts-checklist-migration.md` Section 14 (v1 vs v2 API Map).
### The 4-Step Plugin Pattern
@ -159,13 +200,13 @@ const result = await invoke('my_command', { name: 'World' });
When looking for information in this repository, search for these keywords and patterns:
- **Error messages**: Search the exact error string (e.g., `"unresolved import tauri::api"`, `"command not found"`, `"not allowed"`) in `error-resolution-guide.md`
- **API names**: Search for type names (e.g., `WebviewWindowBuilder`, `Emitter`, `Channel`) in `definitive-guide.md`
- **Config keys**: Search for JSON key names (e.g., `frontendDist`, `withGlobalTauri`, `capabilities`) in `definitive-guide.md` Section 3
- **Plugin names**: Search for plugin identifiers (e.g., `tauri-plugin-fs`, `plugin-dialog`, `store:default`) in `definitive-guide.md` Section 9
- **Error messages**: Search the exact error string (e.g., `"unresolved import tauri::api"`, `"command not found"`, `"not allowed"`) in the Error Resolution Guide files
- **API names**: Search for type names (e.g., `WebviewWindowBuilder`, `Emitter`, `Channel`) in the Definitive Guide files
- **Config keys**: Search for JSON key names (e.g., `frontendDist`, `withGlobalTauri`, `capabilities`) in `01-core-architecture-config-commands-ipc.md`
- **Plugin names**: Search for plugin identifiers (e.g., `tauri-plugin-fs`, `plugin-dialog`, `store:default`) in `02-windows-plugins-websocket-state-migration.md`
- **Migration**: Search for old v1 names (e.g., `WindowBuilder`, `allowlist`, `@tauri-apps/api/tauri`) to find their v2 replacements
- **Patterns**: Search for pattern keywords (e.g., `initialization_script`, `run_on_main_thread`, `broadcast channel`, `include_str`) in both `definitive-guide.md` and `ws_server_client_inject_guide.md`
- **Permission identifiers**: Search for permission strings (e.g., `core:event:allow-listen`, `fs:allow-read`, `http:default`) in `definitive-guide.md` Section 4 and `error-resolution-guide.md` Category E
- **Patterns**: Search for pattern keywords (e.g., `initialization_script`, `run_on_main_thread`, `broadcast channel`, `include_str`) in both the Definitive Guide and `ws_server_client_inject_guide.md`
- **Permission identifiers**: Search for permission strings (e.g., `core:event:allow-listen`, `fs:allow-read`, `http:default`) in `01-core-architecture-config-commands-ipc.md` Section 4 and `01-build-config-permission-plugin-errors.md` Category E
---

View File

@ -1,9 +1,9 @@
# Tauri 2.0 Error Resolution Guide
# Tauri 2.0 Error Resolution Guide — Part 1: Build, Config & Plugin Errors
> **Version**: 2.0 | **Last Updated**: 2026-05-31
> **Official Docs**: https://v2.tauri.app
>
> This guide catalogs every known class of error in Tauri 2.0 development — Rust compilation errors, Cargo/dependency failures, runtime panics, permission errors, frontend integration issues, mobile build failures, and platform-specific problems. Each error includes the exact message, root cause, and working solution.
> This file covers Categories A-H (build-time and configuration errors). Part 2 covers runtime, platform, mobile errors and best practices.
---
@ -17,17 +17,8 @@
6. [Category F: Plugin Integration Errors](#category-f-plugin-integration-errors)
7. [Category G: IPC / Command Runtime Errors](#category-g-ipc--command-runtime-errors)
8. [Category H: Webview & Window Runtime Errors](#category-h-webview--window-runtime-errors)
9. [Category I: Event System Errors](#category-i-event-system-errors)
10. [Category J: Content Security Policy (CSP) Errors](#category-j-content-security-policy-csp-errors)
11. [Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)](#category-k-frontend-integration-errors-vite--nextjs--sveltekit)
12. [Category L: WebSocket Errors](#category-l-websocket-errors)
13. [Category M: Path & Asset Protocol Errors](#category-m-path--asset-protocol-errors)
14. [Category N: Shell & Sidecar Errors](#category-n-shell--sidecar-errors)
15. [Category O: Mobile Build Errors (iOS & Android)](#category-o-mobile-build-errors-ios--android)
16. [Category P: Platform-Specific Build Errors](#category-p-platform-specific-build-errors)
17. [Category Q: Panics & Runtime Crashes](#category-q-panics--runtime-crashes)
18. [Category R: Error Handling Best Practices](#category-r-error-handling-best-practices)
19. [Quick-Reference: Top 25 Most Common Errors](#quick-reference-top-25-most-common-errors)
**See also**: [Part 2: Runtime, Platform & Mobile Errors](./02-runtime-platform-mobile-errors.md)
---
@ -1091,805 +1082,6 @@ Note: This uses nightly/unstable Rust features.
---
## Category I: Event System Errors
### I1. Events Emitted But Not Received by New Window
**Error:** Events emitted to a newly created window are not received the first time.
**Cause:** Race condition — the event is emitted before the listener is attached in the new window.
**Solution:** Use a handshake pattern:
```javascript
// In new window — announce ready
import { emit } from '@tauri-apps/api/event';
emit('window-ready', { label: 'my-window' });
// In main window — respond after ready signal
listen('window-ready', (event) => {
emitTo(event.payload.label, 'initial-data', myData);
});
```
---
### I2. Events Not Working Between Windows
**Error:** Events emitted from one window are not received by another window.
**Cause:** Using `emit()` (which broadcasts to all) vs. `emitTo()` (which targets a specific window) incorrectly, or listeners scoped incorrectly.
**Solution:**
```rust
// Emit to ALL windows
app.emit("my-event", payload)?;
// Emit to a SPECIFIC window
app.emit_to("settings-panel", "my-event", payload)?;
```
```javascript
// Frontend: listen globally
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('my-event', handler);
// Frontend: listen on specific window only
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
const unlisten2 = await appWebview.listen('my-event', handler);
```
---
### I3. Rapid Event Emission Causes Panic
**Error:** App crashes when emitting large numbers of events rapidly.
**Cause:** Internal buffer overflow or deadlock in the event system.
**Solution:** Throttle/debounce event emission. For high-frequency data, use `Channel<T>` instead of events:
```rust
use tauri::ipc::Channel;
#[tauri::command]
fn stream_data(on_event: Channel<MyEvent>) {
for item in data_source {
on_event.send(item).unwrap();
}
}
```
---
## Category J: Content Security Policy (CSP) Errors
### J1. `Refused to connect to 'http://ipc.localhost'`
**Error:**
```
Refused to connect to 'http://ipc.localhost/test' because it violates the document's Content Security Policy.
```
**Cause:** External URLs loaded in webviews have their own CSP that doesn't allow connections to Tauri's IPC endpoint.
**Solution:** Configure CSP to include IPC:
```json
{
"app": {
"security": {
"csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://your-url.com"
}
}
}
```
For maximum compatibility with injected scripts on external pages: `"csp": null` (use with caution).
---
### J2. CSS-in-JS Libraries Blocked by CSP
**Error:** Styles/scripts from styled-components, emotion, or other CSS-in-JS libraries are blocked.
**Cause:** Default CSP doesn't allow inline styles.
**Solution:** Add `'unsafe-inline'` to `style-src`:
```json
{
"app": {
"security": {
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'"
}
}
}
```
---
## Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)
### K1. Vite Dev Server Not Connecting / White Screen
**Error:** Tauri window opens but shows blank white screen. Dev server connection not established.
**Cause:** Port mismatch between `devUrl` in `tauri.conf.json` and Vite's actual port.
**Solution:**
1. Ensure `devUrl` matches Vite's actual port: `"devUrl": "http://localhost:5173"`
2. For mobile, use `process.env.TAURI_DEV_HOST` as the host in Vite config
3. Set `server.host` in `vite.config.js` to allow network access
---
### K2. Vite Watches `src-tauri` — Infinite Recompilation
**Error:** App keeps recompiling Rust code repeatedly in an infinite loop.
**Cause:** Vite's file watcher detects changes in `src-tauri/` (from Cargo build output) and triggers re-runs.
**Solution:** Add `src-tauri` to Vite's ignore list:
```javascript
// vite.config.js
export default defineConfig({
server: {
watch: {
ignored: ["**/src-tauri/**"]
}
}
});
```
---
### K3. Next.js App Router Breaks in Production
**Error:** `useEffect` hooks broken, syntax errors in chunk files, component rendering failures in production.
**Cause:** Next.js App Router uses React Server Components and advanced chunking that conflicts with Tauri's custom protocol.
**Solution:** Use static export only:
```javascript
// next.config.js
module.exports = {
output: 'export', // Required — no SSR
images: { unoptimized: true }, // No server-side image optimization
};
```
Add `'use client'` directives to all interactive components.
---
### K4. Next.js `assetPrefix` Breaks Hot Reload
**Error:** Hot reload doesn't work when `assetPrefix` is set for Tauri.
**Cause:** `assetPrefix` changes the URL for WebSocket connections, breaking HMR.
**Solution:** Only set `assetPrefix` in production:
```javascript
// next.config.js
module.exports = {
output: 'export',
assetPrefix: process.env.NODE_ENV === 'production' ? './' : undefined,
};
```
---
### K5. Next.js Standalone Output — `asset not found: index.html`
**Error:**
```
asset not found: index.html
```
**Cause:** Next.js standalone output doesn't produce a static `index.html` file.
**Solution:** Use `output: "export"`, NOT `standalone`:
```javascript
module.exports = {
output: 'export', // Required for Tauri
// NOT output: 'standalone'
};
```
---
### K6. SvelteKit Blank Screen / PostCSS Errors
**Error:** Blank white screen or `[postcss] Internal server error`.
**Cause:** SvelteKit's routing conflicts with Tauri's static file serving.
**Solution:** Use `@sveltejs/adapter-static`:
```javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({ fallback: 'index.html' })
}
};
```
Add to root layout: `export const ssr = false;`
---
## Category L: WebSocket Errors
### L1. WebSocket Connection Closing / "Broken Pipe"
**Error:** WebSocket connection closes unexpectedly after app has been open for a long time.
**Cause:** Idle timeouts or OS-level connection cleanup.
**Solution:** Implement ping/pong keepalive in custom WebSocket connections:
```javascript
const ws = new WebSocket('ws://127.0.0.1:8080');
// Keepalive
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
```
---
### L2. WebSocket Plugin — Unstable Connections
**Error:** Messages not received right after connection with `tauri-plugin-websocket`.
**Cause:** The WebSocket connection may not be fully initialized before messages are sent.
**Solution:** Wait for the `onopen` event:
```javascript
import WebSocket from '@tauri-apps/plugin-websocket';
const ws = await WebSocket.connect('ws://localhost:8080');
// Ensure ready before sending
ws.addListener((msg) => {
console.log('Received:', msg);
});
```
---
### L3. WebSocket + Tauri Client Conflict
**Error:** WebSocket client code and Tauri app cannot run simultaneously.
**Cause:** Resource conflicts between the Tauri WebView and WebSocket connections, especially on mobile.
**Solution:** Use `tauri-plugin-websocket` instead of raw `WebSocket` API for mobile compatibility.
---
## Category M: Path & Asset Protocol Errors
### M1. 403 Forbidden on Asset Protocol URLs
**Error:** `403 Forbidden` when accessing files via `asset://` protocol.
**Cause:** The asset protocol has a scope restricting which directories can be accessed.
**Solution:** Configure asset protocol scope:
```json
{
"identifier": "asset-capability",
"permissions": [
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$RESOURCE/**" }]
}
]
}
```
Use `convertFileSrc()` from `@tauri-apps/api/core` for proper path conversion.
---
### M2. `resource_dir()` Returns Unknown Path
**Error:** `PathResolver.resource_dir()` returns empty or unknown path.
**Cause:** A custom `target-dir` in `~/.cargo/config.toml` breaks resource resolution logic.
**Solution:** Remove global `target-dir` setting, or configure it per-project instead of globally.
---
### M3. Path Traversal Prevention Blocking Valid Paths
**Error:** `path traversal` error from `@tauri-apps/plugin-fs`.
**Cause:** The FS plugin blocks paths containing `..`.
**Solution:** Normalize paths before use:
```javascript
import { resolve } from '@tauri-apps/api/path';
const normalizedPath = await resolve('some', 'relative', 'path');
```
---
## Category N: Shell & Sidecar Errors
### N1. Sidecar Binary Not Found
**Error:**
```
Failed to spawn my-binary: Io(Os { code: 3, kind: NotFound })
```
**Cause:** The sidecar binary doesn't follow Tauri's naming convention. Tauri appends the target triple to the binary name.
**Solution:**
```json
// tauri.conf.json
{
"bundle": {
"externalBin": ["binaries/my-binary"]
}
}
```
Name the binary with the target triple suffix:
- Windows: `my-binary-x86_64-pc-windows-msvc.exe`
- macOS: `my-binary-aarch64-apple-darwin`
- Linux: `my-binary-x86_64-unknown-linux-gnu`
Reference in Rust without the suffix:
```rust
app.shell().sidecar("binaries/my-binary").unwrap()
```
---
## Category O: Mobile Build Errors (iOS & Android)
### O1. `xcodebuild exited with code 65`
**Error:**
```
error: failed to run custom build command for tauri v2 ... xcodebuild exited with code 65
```
**Cause:** iOS build failure. Common causes: code signing issues, stale build artifacts, Xcode version incompatibility.
**Solution:**
1. Open Xcode project: `open src-tauri/gen/apple/Project.xcworkspace`
2. Set Signing & Capabilities → Team
3. Clean build: `Cmd+Shift+K` in Xcode
4. Clean pods: `cd src-tauri/gen/apple && rm -rf build Pods && pod install`
---
### O2. `can't find crate for core` (Missing Mobile Target)
**Error:**
```
error[E0463]: can't find crate for `core`
```
When building for iOS or Android.
**Cause:** The Rust target for the mobile platform is not installed.
**Solution:**
```bash
# iOS
rustup target add aarch64-apple-ios
# Android (all targets)
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
cargo install cargo-ndk
```
---
### O3. `building for 'iOS', but linking in object file built for 'macOS'`
**Error:**
```
building for 'iOS', but linking in object file built for 'macOS'
```
**Cause:** A native dependency was compiled for the host platform instead of the iOS target.
**Solution:** Check which dependency causes the issue: `cargo build --target aarch64-apple-ios -v`. Ensure all dependencies support cross-compilation to iOS.
---
### O4. Android Build Fails — `Cannot find module 'tauri'`
**Error:**
```
Cannot find module 'tauri' during Gradle build
```
**Cause:** Node.js executable can't be found when Gradle's BuildTask runs.
**Solution:** Ensure Node.js is available system-wide. Use `npx tauri` or configure the Gradle BuildTask to use the correct node path.
---
### O5. Android App Crashes on Launch
**Error:** App immediately crashes after deployment to Android device/emulator.
**Cause:** Missing Android SDK components, incorrect NDK configuration, or incompatible Gradle/AGP versions.
**Solution:**
1. Ensure correct Android SDK and NDK are installed
2. Run `npx tauri android init` to set up properly
3. Check `android/app/build.gradle` has correct Tauri integration
4. Verify ADB connection: `adb devices`
---
### O6. `tauri android dev` Hangs Indefinitely
**Error:** Command hangs with no output or progress.
**Cause:** Emulator/device connection failure or Gradle build hanging.
**Solution:**
1. Verify ADB: `adb devices`
2. Clean Gradle cache: `cd android && ./gradlew clean`
3. Use a physical device instead of emulator
4. Increase Gradle memory in `android/gradle.properties`
---
### O7. `mobile_entry_point` Conflicts with `#[tokio::main]`
**Error:** App crashes on iOS when using `#[tokio::main]` with `mobile_entry_point`.
**Cause:** `mobile_entry_point` and `#[tokio::main]` generate conflicting function signatures.
**Solution:**
```rust
// ❌ WRONG
#[cfg_attr(mobile, tauri::mobile_entry_point)]
#[tokio::main]
pub async fn run() { /* ... */ }
// ✅ CORRECT — use async_runtime::block_on
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::async_runtime::block_on(async {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
});
}
```
---
### O8. No Code Signing Certificates Found (iOS)
**Error:**
```
No code signing certificates found. You must add one and set the certificate development team ID.
```
**Cause:** No Apple Developer certificate configured.
**Solution:** Open `src-tauri/gen/apple/Project.xcworkspace` in Xcode → Signing & Capabilities → select Team. Or use environment variables: `APPLE_SIGNING_IDENTITY`, `APPLE_TEAM_ID`.
---
### O9. Android SDK Not Found
**Error:**
```
Android SDK not found at ANDROID_HOME. Please set the ANDROID_HOME environment variable.
```
Or Gradle fails with `SDK location not found`.
**Cause:** The `ANDROID_HOME` (or `ANDROID_SDK_ROOT`) environment variable is not set, or points to a nonexistent directory. Tauri's Android build requires the Android SDK to be available.
**Solution:**
1. Install Android Studio, which bundles the SDK
2. Set the environment variable:
```bash
# macOS/Linux — add to ~/.zshrc or ~/.bashrc
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/platform-tools:$PATH"
# Windows — set via System Properties → Environment Variables
ANDROID_HOME=C:\Users\<you>\AppData\Local\Android\Sdk
```
3. Verify: `echo $ANDROID_HOME` and `adb version`
4. Re-run `npx tauri android init`
---
### O10. iOS Development Requires macOS
**Error:**
```
iOS is not supported on this platform
```
Or `xcodebuild: command not found` when running on Linux or Windows.
**Cause:** iOS builds require Xcode and the Apple toolchain, which are only available on macOS. Cross-compilation to iOS from Linux or Windows is not supported by Tauri.
**Solution:**
1. Use a Mac (physical or CI runner like GitHub Actions `macos-latest`)
2. Install Xcode from the Mac App Store
3. Accept the Xcode license: `sudo xcodebuild -license accept`
4. Install CocoaPods: `sudo gem install cocoapods` (or `brew install cocoapods`)
5. For CI, use GitHub Actions with `macos-latest` runner
---
### O11. `pod install` Failed (iOS)
**Error:**
```
Pod installation failed. CocoaPods could not find compatible versions for pod "TauriDriver"
```
Or `pod: command not found`.
**Cause:** CocoaPods is not installed, outdated, or the Podfile.lock is stale and conflicts with updated dependencies.
**Solution:**
1. Ensure CocoaPods is installed:
```bash
sudo gem install cocoapods
# Or via Homebrew:
brew install cocoapods
```
2. Clean and reinstall pods:
```bash
cd src-tauri/gen/apple
rm -rf Podfile.lock Pods
pod install --repo-update
```
3. If version conflicts persist, update CocoaPods to the latest version: `sudo gem install cocoapods --pre`
4. Ensure your Xcode Command Line Tools are up to date: `xcode-select --install`
---
## Category P: Platform-Specific Build Errors
### P1. Linux — Missing System Dependencies
**Error:** Build fails with GTK/WebKit-related errors.
**Cause:** Missing system libraries for GTK/WebKit development.
**Solution:**
```bash
# Ubuntu/Debian
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev \
librsvg2-dev libsoup-3.0-dev
# Fedora
sudo dnf install webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel \
librsvg2-devel libsoup3-devel
# Arch
sudo pacman -S webkit2gtk-4.1 gtk3 libappindicator-gtk3 librsvg libsoup3
```
---
### P2. Linux — Failed to Find ICNS Encoder
**Error:**
```
error: failed to compile tauri-bundler: failed to find ICNS encoder
```
**Cause:** Missing icon tools for macOS bundle creation on Linux.
**Solution:** `sudo apt install icnsutils` (Debian/Ubuntu) or equivalent package.
---
### P3. Linux — Failed to Run `linuxdeploy`
**Error:**
```
error: failed to bundle project: failed to run linuxdeploy
```
**Cause:** Missing AppImage bundling dependencies.
**Solution:** Install `linuxdeploy` and `linuxdeploy-plugin-gtk`. Ensure `appimage-builder` is available.
---
## Category Q: Panics & Runtime Crashes
### Q1. Generic "error while running tauri application"
**Error:**
```
thread 'main' panicked at src\main.rs:14:10:
error while running tauri application: <inner error>
```
**Cause:** Top-level error wrapper. The inner error reveals the actual problem — check plugin initialization, WebView creation, or config validation.
**Solution:** Replace `.expect()` with better error reporting:
```rust
if let Err(e) = tauri::Builder::default()
.plugin(tauri_plugin_http::init())
.run(tauri::generate_context!())
{
eprintln!("Failed to start: {e}");
std::process::exit(1);
}
```
---
### Q2. Panic in tauri-runtime-wry on Wayland
**Error:**
```
Panic in tauri-runtime-wry during init on Wayland
```
**Cause:** Wayland compositors missing `wlr_data_control_manager v1` protocol cause the clipboard crate to panic.
**Solution:** Use XWayland, or use a Wayland compositor that supports the protocol.
---
### Q3. `EXC_BAD_ACCESS` Crash on iOS
**Error:** App crashes on physical iPhone with `EXC_BAD_ACCESS` but works in simulator.
**Cause:** Memory access violations in native code bridging.
**Solution:** Enable address sanitizer in Xcode. Check for use-after-free or null pointer issues in Rust FFI code.
---
### Q4. Panic When `Builder::setup` Hook Fails
**Error:** App panics immediately when the `setup` callback returns an error.
**Cause:** `App::run()` panics when setup fails — no graceful error handling.
**Solution:** Wrap setup logic properly:
```rust
tauri::Builder::default()
.setup(|app| {
match critical_init(app) {
Ok(_) => Ok(()),
Err(e) => {
eprintln!("Setup failed: {e}");
Err(e.into())
}
}
})
.run(tauri::generate_context!())
```
---
## Category R: Error Handling Best Practices
### R1. Never Panic Inside Tauri Commands
Panic in a **synchronous** command crashes the app. Panic in an **asynchronous** command causes a Promise that never resolves (silent failure). Always return `Result`:
```rust
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Network(#[from] reqwest::Error),
#[error("{0}")]
Custom(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
s.serialize_str(&self.to_string())
}
}
#[tauri::command]
fn my_command() -> Result<String, AppError> {
let data = std::fs::read_to_string("config.toml")?;
Ok(data)
}
```
### R2. Frontend Error Handling
```javascript
import { invoke } from '@tauri-apps/api/core';
try {
const result = await invoke('my_command', { arg: 'value' });
console.log('Success:', result);
} catch (error) {
console.error('Command failed:', error);
// Show user-friendly error in UI
}
```
### R3. Structured Errors for the Frontend
```rust
#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
Io(String),
Network(String),
Custom(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
let kind = match self {
Self::Io(e) => ErrorKind::Io(e.to_string()),
Self::Network(e) => ErrorKind::Network(e.to_string()),
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
};
kind.serialize(s)
}
}
```
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
### R4. Cleanup Event Listeners
```javascript
import { listen } from '@tauri-apps/api/event';
// ❌ WRONG — memory leak
useEffect(() => {
listen('my-event', handler);
return () => {}; // No cleanup!
}, []);
// ✅ CORRECT — proper cleanup
useEffect(() => {
const unlisten = listen('my-event', handler);
return () => { unlisten.then(fn => fn()); };
}, []);
```
---
## Quick-Reference: Top 25 Most Common Errors
| # | Error Message | Category | Quick Fix |
|---|---|---|---|
| 1 | `X not allowed. Plugin not found` | Permission | Add permissions to `capabilities/default.json` |
| 2 | `Additional properties not allowed ('devPath', 'distDir')` | Config | Rename to `devUrl` / `frontendDist` |
| 3 | `__TAURI__ is not defined` | Frontend | Set `withGlobalTauri: true` or use npm imports |
| 4 | `AssetNotFound("index.html")` | Config | Fix `frontendDist` path to match bundler output |
| 5 | `unresolved import 'tauri::api'` | Migration | Replace with plugins (`tauri-plugin-*`) |
| 6 | `cannot find type 'Window' in 'tauri'` | Migration | Use `WebviewWindow` |
| 7 | `failed to create webview: WebView2 error` | Webview | Install WebView2; remove custom browser args |
| 8 | `Path not allowed on the configured scope` | Permission | Add FS scope permissions |
| 9 | `command not found` | IPC | Add command to `generate_handler![]` |
| 10 | `cannot find type 'SystemTray'` | Migration | Use `tauri::tray::TrayIconBuilder` |
| 11 | `lifetime not constrained` in async | Async | Use owned types (`String`, not `&str`) |
| 12 | `MutexGuard cannot be sent between threads` | Async | Use `tokio::sync::Mutex` instead of `std::sync::Mutex` |
| 13 | `xcodebuild exited with code 65` | Mobile | Set signing team in Xcode |
| 14 | `can't find crate for core` | Mobile | `rustup target add <target>` |
| 15 | `Additional properties not allowed ('allowlist')` | Config | Remove; use v2 capabilities system |
| 16 | `beforeDevCommand terminated with non-zero status` | Config | Fix npm scripts and port numbers |
| 17 | Vite infinite recompilation | Frontend | Add `src-tauri` to `watch.ignored` |
| 18 | Next.js `useEffect` broken in production | Frontend | Use `output: "export"` + `'use client'` |
| 19 | `program not allowed on shell scope` | Shell | Add program to shell capability scope |
| 20 | `Refused to connect to 'http://ipc.localhost'` | CSP | Add `ipc: http://ipc.localhost` to `connect-src` |
| 21 | Only last `invoke_handler` works | IPC | Combine all commands in one `generate_handler![]` |
| 22 | `GDK may only be used from the main thread` | Platform | Update Tauri; avoid cross-thread UI calls |
| 23 | `PluginInitialization("http", "invalid type")` | Config | Migrate old plugins config to v2 capabilities |
| 24 | Sidecar `Io(Os { code: 3 })` | Shell | Fix binary naming with target triple suffix |
| 25 | `error while running tauri application` | Generic | Check inner error; improve error handling |
**Continue to**: [Part 2: Runtime, Platform & Mobile Errors](./02-runtime-platform-mobile-errors.md)

View File

@ -0,0 +1,930 @@
# Tauri 2.0 Developer Guide — Part 1: Core Architecture, Config, Commands & IPC
> **Version**: 2.0 | **Last Updated**: 2026-05-31
> **Official Docs**: https://v2.tauri.app
>
> This file covers Sections 1-7 of the Definitive Guide. Part 2 covers Windows, Plugins, WebSocket, State, Migration & Anti-Patterns.
---
## Table of Contents
1. [Introduction & Architecture Overview](#1-introduction--architecture-overview)
2. [Project Structure & Scaffolding](#2-project-structure--scaffolding)
3. [Configuration System (`tauri.conf.json`)](#3-configuration-system-tauriconfjson)
4. [Capabilities & Permissions (Security Model)](#4-capabilities--permissions-security-model)
5. [Rust Backend: `lib.rs` & Commands](#5-rust-backend-librs--commands)
6. [Frontend Integration Patterns](#6-frontend-integration-patterns)
7. [IPC: Commands, Events & Channels](#7-ipc-commands-events--channels)
**See also**: [Part 2: Windows, Plugins, WebSocket, State & Migration](./02-windows-plugins-websocket-state-migration.md)
---
## 1. Introduction & Architecture Overview
Tauri 2.0 is a framework for building small, fast binaries for all major desktop and mobile platforms. It uses a **hybrid architecture** where a Rust backend handles system-level operations while a web frontend (HTML/CSS/JS, or any framework like React/Vue/Svelte) renders the UI inside a native OS webview.
### Core Architecture Principles
- **Rust Core (`src-tauri/`)**: Handles system access, file operations, network calls, window management, and any heavy computation. All system interactions MUST go through Rust to maintain security boundaries.
- **Web Frontend (`src/`)**: Renders the UI using standard web technologies. Communicates with Rust exclusively through Tauri's IPC mechanisms (`invoke`, events, channels).
- **Security Boundary**: The frontend runs in a sandboxed webview. It cannot directly access the filesystem, network, or OS APIs — it must request these through Tauri commands and plugins, which are governed by the **Capabilities & Permissions** system.
- **Mobile Support (NEW in v2)**: Tauri 2.0 adds first-class iOS and Android support. This requires a specific project structure (`lib.rs` + `main.rs` pattern) and platform-specific capability configurations.
### What Changed from v1 to v2
Tauri 2.0 is a **complete architectural overhaul**, not an incremental update. The most significant changes are:
| Area | v1 | v2 |
|------|----|----|
| Security | `allowlist` in config | **Capabilities + Permissions** (ACL-based) |
| APIs | Built-in `tauri::api` module | **Everything is a plugin** |
| Window type | `Window` / `WindowBuilder` | **`WebviewWindow` / `WebviewWindowBuilder`** |
| Config structure | Nested `tauri >` key | Flattened: `app`, `bundle`, `build` |
| Event system | Per-window scoped | **`emit()` is global; `emit_to()` for targeting** |
| JS imports | `@tauri-apps/api/tauri` | **`@tauri-apps/api/core`** |
| Mobile support | Community plugins | **First-class iOS/Android support** |
| Project structure | `main.rs` only | **`lib.rs` + `main.rs` pattern required** |
---
## 2. Project Structure & Scaffolding
### Standard Tauri 2.0 File Hierarchy
```text
my-app/
├── src/ # Frontend UI source
│ ├── index.html # Main HTML entry
│ ├── main.js # Frontend JavaScript
│ └── styles.css # Styles
├── src-tauri/ # Rust backend environment
│ ├── capabilities/ # Security capability definitions
│ │ └── default.json # Maps windows → permissions
│ ├── icons/ # App icons for all platforms
│ ├── src/
│ │ ├── main.rs # Minimal desktop entry-point
│ │ ├── lib.rs # Core application setup & commands
│ │ └── <modules>.rs # Additional Rust modules
│ ├── build.rs # Tauri build script
│ ├── Cargo.toml # Rust dependencies
│ ├── Cargo.lock # Lockfile (COMMIT THIS)
│ └── tauri.conf.json # Main Tauri configuration
├── package.json # Node.js dependencies & scripts
└── package-lock.json
```
### Creating a New Project
```bash
# Using the interactive CLI scaffolder
npm create tauri-app@latest
# Or with specific template
npm create tauri-app@latest -- --template vanilla-ts
# Cargo alternative
cargo create-tauri-app
```
Supported templates: Vanilla, Vue, Svelte, React, Solid, Angular, Preact, Yew, Leptos, Sycamore.
### Adding Tauri to an Existing Frontend
```bash
npm install -D @tauri-apps/cli@latest
npx tauri init
```
The `init` command will prompt for: app name, window title, web assets location, dev server URL, and frontend build/dev commands.
### The `lib.rs` + `main.rs` Pattern (Required in v2)
Tauri 2.0 requires your core application logic to live in `lib.rs`, with a minimal `main.rs` that calls into it. This is **mandatory** for mobile support because mobile platforms require a shared library entry-point rather than a standard `main()` function.
**`src-tauri/src/main.rs`** — Desktop-only entry-point:
```rust
// Prevents additional console window on Windows in release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
my_app_lib::run()
}
```
**`src-tauri/src/lib.rs`** — Core application:
```rust
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
// plugins, commands, setup, etc.
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
**`src-tauri/Cargo.toml`** — Must declare the library:
```toml
[lib]
name = "my_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
```
The `_lib` suffix in the crate name prevents naming conflicts with the binary on Windows. The three crate types cover: static linking (mobile), dynamic linking (mobile), and Rust library usage (desktop/tests).
### Development Commands
```bash
# Run in development mode (hot-reloads UI + auto-recompiles Rust)
npm run tauri dev
# or: pnpm tauri dev, bun tauri dev, cargo tauri dev
# Build production artifacts
npm run tauri build
```
---
## 3. Configuration System (`tauri.conf.json`)
### Main Configuration File
The primary configuration file is `src-tauri/tauri.conf.json`. Tauri 2.0 supports three formats:
| Format | Feature Flag Required |
|--------|-----------------------|
| **JSON** (default) | None |
| **JSON5** | `config-json5` on both `tauri` and `tauri-build` |
| **TOML** | `config-toml` on both `tauri` and `tauri-build` |
### Complete v2 Configuration Structure
```json
{
"$schema": "https://tauri.app",
"productName": "my-app",
"version": "1.0.0",
"identifier": "com.mycompany.myapp",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:1420",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"label": "main",
"title": "My App",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false,
"center": true,
"dragDropEnabled": true,
"useHttpsScheme": false
}
],
"security": {
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'",
"assetProtocol": {
"scope": ["$APPDATA/**", "$RESOURCE/**"]
}
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"licenseFile": "LICENSE",
"copyright": "",
"category": "Utility"
}
}
```
### Key Configuration Changes from v1
| v1 Path | v2 Path | Notes |
|---------|---------|-------|
| `package.productName` | Top-level `productName` | Moved out of `package` |
| `package.version` | Top-level `version` | Moved out of `package` |
| `package` | *(removed)* | Fields redistributed |
| `build.distDir` | `build.frontendDist` | Renamed |
| `build.devPath` | `build.devUrl` | Renamed |
| `build.withGlobalTauri` | `app.withGlobalTauri` | Moved to `app` section |
| `tauri.*` | `app.*` | Top-level key renamed |
| `tauri.allowlist` | *(removed)* | Replaced by capabilities |
| `tauri.windows.fileDropEnabled` | `app.windows.dragDropEnabled` | Renamed |
| `tauri.bundle` | Top-level `bundle` | Promoted to top-level |
| `tauri.updater` | `plugins.updater` | Moved to plugins |
| `tauri.systemTray` | `app.trayIcon` | Renamed |
| `tauri.cli` | `plugins.cli` | Moved to plugins |
| `bundle.identifier` | Top-level `identifier` | Promoted to top-level |
### Platform-Specific Configuration
Create platform override files that **merge** with the main config using JSON Merge Patch (RFC 7396). Arrays are replaced entirely (not element-by-element merged).
| Platform | File Pattern |
|----------|-------------|
| Linux | `tauri.linux.conf.json` or `Tauri.linux.toml` |
| Windows | `tauri.windows.conf.json` or `Tauri.windows.toml` |
| macOS | `tauri.macos.conf.json` or `Tauri.macos.toml` |
| Android | `tauri.android.conf.json` or `Tauri.android.toml` |
| iOS | `tauri.ios.conf.json` or `Tauri.ios.toml` |
### Cargo.toml Dependencies
```toml
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```
**Important**: Keep `tauri`, `tauri-build`, and `@tauri-apps/cli` on the same minor version. Always commit `Cargo.lock` for reproducible builds.
### package.json Scripts
```json
{
"scripts": {
"tauri": "tauri",
"dev": "tauri dev",
"build": "tauri build"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
}
}
```
The `"tauri"` script is only required when using `npm` (not needed for yarn/pnpm/bun).
---
## 4. Capabilities & Permissions (Security Model)
Tauri 2.0 introduces a strict **Access Control List (ACL)** security model that replaces the v1 `allowlist`. This is the most important new concept to understand.
### Core Concepts
- **Capabilities** define **which permissions** are granted to **which windows or webviews**.
- **Permissions** describe explicit privileges for specific commands (e.g., `fs:allow-read`, `http:allow-request`).
- **Scopes** further restrict what a permission can access (e.g., only certain file paths).
- Multiple capabilities can apply to the same window — permissions **merge**.
- Security boundaries are based on **window labels** (not titles).
### Capability File Location
```
src-tauri/capabilities/<identifier>.json (or .toml)
```
All files in this directory are **automatically loaded** by default.
### Basic Capability File
```json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-window-capability",
"description": "Permissions for the primary application window",
"platforms": ["linux", "macOS", "windows"],
"windows": ["main"],
"permissions": [
"core:default"
]
}
```
**Field reference:**
- `$schema` — Points to generated schema for IDE autocompletion. Use `desktop-schema.json` for desktop, `mobile-schema.json` for mobile.
- `identifier` — Unique capability name (ASCII lowercase, max 116 chars).
- `description` — Human-readable purpose.
- `windows` — Array of window labels this capability applies to. Use `["*"]` for all windows.
- `permissions` — Array of permission identifiers.
- `platforms` — Optional. Restricts to specific platforms. Defaults to all platforms if omitted.
### Permission Identifier Naming Convention
| Pattern | Meaning | Example |
|---------|---------|---------|
| `<plugin>:default` | Default permission set for a plugin | `fs:default` |
| `<plugin>:allow-<command>` | Allow a specific command | `fs:allow-read` |
| `<plugin>:deny-<command>` | Deny a specific command | `fs:deny-write` |
| `core:<module>:<permission>` | Core Tauri module permission | `core:event:allow-emit` |
### Common Permission Identifiers
**Core permissions:**
- `core:default` — Basic runtime permissions
- `core:path:default` — Path resolution
- `core:event:default` — Event system (includes listen/emit)
- `core:event:allow-listen` — Allow listening to events
- `core:event:allow-emit` — Allow emitting events
- `core:window:default` — Window management
- `core:window:allow-set-title` — Allow changing window title
**Plugin permissions:**
- `fs:allow-read`, `fs:allow-write`, `fs:allow-exists`, `fs:allow-mkdir`, `fs:allow-remove`
- `http:allow-request` — HTTP client requests
- `dialog:allow-open`, `dialog:allow-save`, `dialog:allow-message`
- `shell:allow-open`, `shell:allow-execute`
- `notification:allow-notify`, `notification:allow-is-permission-granted`
- `clipboard-manager:allow-read-text`, `clipboard-manager:allow-write-text`
- `websocket:allow-connect`, `websocket:allow-send`
- `store:default` — Persistent key-value store
### Fine-Grained Scopes
Scopes restrict what a permission can actually access:
```json
{
"identifier": "secure-fs-capability",
"windows": ["main"],
"permissions": [
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$APPDATA/logs/*" }, { "path": "$HOME/Documents/*.txt" }]
},
{
"identifier": "fs:allow-write",
"allow": [{ "path": "$APPDATA/logs/*.log" }]
},
{
"identifier": "fs:deny-write",
"deny": [{ "path": "$APPDATA/config/*" }]
}
]
}
```
Available scope variables: `$APPDATA`, `$HOME`, `$APPCONFIG`, `$APPCACHE`, `$APPLOG`, `$RESOURCE`, `$EXE`.
### Restricting Custom Commands
By default, all `#[tauri::command]` functions are accessible from all windows. To restrict a custom command to specific windows only, use `build.rs`:
```rust
fn main() {
tauri_build::try_build(
tauri_build::Attributes::new()
.app_manifest(
tauri_build::AppManifest::new()
.commands(&["my_restricted_command", "admin_only_command"])
),
)
.unwrap();
}
```
Then grant access in the capability:
```json
{
"identifier": "admin-capability",
"windows": ["admin-panel"],
"permissions": [
{ "identifier": "allow-my-restricted-command" },
{ "identifier": "allow-admin-only-command" }
]
}
```
### Referencing Capabilities Explicitly
By default, all capability files in `src-tauri/capabilities/` are auto-discovered. To control which ones are used explicitly:
```json
{
"app": {
"security": {
"capabilities": ["my-capability", "admin-capability"]
}
}
}
```
### Inline Capabilities
You can define capabilities directly in `tauri.conf.json` instead of separate files:
```json
{
"app": {
"security": {
"capabilities": [
{
"identifier": "inline-cap",
"windows": ["*"],
"permissions": ["core:default"]
}
]
}
}
}
```
---
## 5. Rust Backend: `lib.rs` & Commands
### Application Builder Pattern
All Tauri 2.0 applications follow the `tauri::Builder` pattern:
```rust
use tauri::Manager;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) // Register plugins
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.setup(|app| { // Setup hook (runs once at startup)
// Initialize resources, spawn servers, etc.
Ok(())
})
.manage(AppState { /* ... */ }) // Inject managed state
.invoke_handler(tauri::generate_handler![ // Register commands
my_command,
another_command,
module::command_in_module
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
**Critical rules:**
- `invoke_handler` can only be called **once**. All commands must be in a single `generate_handler![]` call.
- Commands defined in `lib.rs` **cannot** be marked `pub`.
- Commands defined in separate modules **must** be marked `pub`.
- The `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is required for the `run()` function.
### Defining Commands
Commands are the primary IPC mechanism. They are Rust functions annotated with `#[tauri::command]` and callable from the frontend via `invoke()`.
#### Basic Command
```rust
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}! Welcome from Rust.", name)
}
```
#### Command with Error Handling
```rust
#[tauri::command]
fn login(user: String, password: String) -> Result<String, String> {
if user == "admin" && password == "secret" {
Ok("authenticated".to_string())
} else {
Err("invalid credentials".to_string())
}
}
```
#### Async Command
```rust
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url)
.await
.map_err(|e| e.to_string())?;
response.text().await.map_err(|e| e.to_string())
}
```
**IMPORTANT limitation**: Async commands cannot accept borrowed references (`&str`, `&Path`) directly. Convert to owned types (`String`, `PathBuf`) or wrap the return in `Result`:
```rust
// Option A: Use owned types
#[tauri::command]
async fn process(value: String) -> String { /* ... */ }
// Option B: Wrap return in Result
#[tauri::command]
async fn process(value: &str) -> Result<String, ()> { /* ... */ }
```
#### Custom Error Types (Recommended)
```rust
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
}
// Implement Serialize so the error can cross the IPC boundary
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 read_config() -> Result<String, AppError> {
let content = std::fs::read_to_string("config.toml")?;
Ok(content)
}
```
#### Structured Error with Kind/Tag
```rust
#[derive(Debug, Error)]
enum AppError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("{0}")]
Custom(String),
}
#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
Io(String),
Custom(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let kind = match self {
Self::Io(e) => ErrorKind::Io(e.to_string()),
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
};
kind.serialize(serializer)
}
}
```
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
#### Accessing Special Objects in Commands
**AppHandle** — Access to the application instance:
```rust
#[tauri::command]
fn do_something(app_handle: tauri::AppHandle) {
let app_dir = app_handle.path().app_dir();
// Spawn windows, emit events, access state...
}
```
**WebviewWindow** — The window that invoked the command:
```rust
#[tauri::command]
fn get_window_info(webview_window: tauri::WebviewWindow) -> String {
webview_window.label().to_string()
}
```
**Managed State** — Shared application state:
```rust
use tauri::State;
use std::sync::Mutex;
struct DbConnection(Mutex<String>);
#[tauri::command]
fn query_database(state: State<'_, DbConnection>) -> String {
let conn = state.0.lock().unwrap();
conn.clone()
}
```
**Raw Request** — Access headers and raw body:
```rust
#[tauri::command]
fn upload(request: tauri::ipc::Request) -> Result<(), String> {
let tauri::ipc::InvokeBody::Raw(data) = request.body() else {
return Err("Expected raw body".to_string());
};
let auth = request.headers().get("Authorization")
.ok_or("Missing auth header")?;
Ok(())
}
```
#### Commands in Separate Modules
```rust
// src-tauri/src/commands.rs
#[tauri::command]
pub fn create_user(name: String) -> String {
format!("Created user: {}", name)
}
#[tauri::command]
pub fn delete_user(id: u32) -> bool {
// ...
true
}
```
```rust
// src-tauri/src/lib.rs
mod commands;
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
commands::create_user,
commands::delete_user
])
// ...
}
```
Note: The `commands::` prefix is Rust path resolution only — the frontend still calls `invoke('create_user', ...)` without any prefix.
### Returning Large Data (ArrayBuffers)
For large binary data like file contents, use `tauri::ipc::Response`:
```rust
use tauri::ipc::Response;
#[tauri::command]
fn read_file() -> Response {
let data = std::fs::read("/path/to/file").unwrap();
Response::new(data)
}
```
### Streaming Data (Channels)
For streaming large amounts of data to the frontend:
```rust
use tauri::ipc::Channel;
use tokio::io::AsyncReadExt;
#[tauri::command]
async fn stream_file(path: std::path::PathBuf, on_chunk: Channel<Vec<u8>>) {
let mut file = tokio::fs::File::open(path).await.unwrap();
let mut buf = vec![0u8; 4096];
loop {
let n = file.read(&mut buf).await.unwrap();
if n == 0 { break; }
on_chunk.send(&buf[..n]).unwrap();
}
}
```
---
## 6. Frontend Integration Patterns
### Using `@tauri-apps/api` npm Package (Recommended)
```bash
npm install @tauri-apps/api
```
```javascript
// Imports in v2 — NOTE the path changes from v1
import { invoke } from '@tauri-apps/api/core';
import { listen, emit, emitTo, once } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow, WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { Channel } from '@tauri-apps/api/core';
```
### Using Global `window.__TAURI__` (No npm package needed)
Requires `app.withGlobalTauri: true` in `tauri.conf.json`:
```javascript
// No import needed — accessed via the global object
const { invoke } = window.__TAURI__.core;
const { listen, emit } = window.__TAURI__.event;
```
This pattern is ideal for vanilla JS projects that don't use a bundler, avoiding the need for any npm packages beyond `@tauri-apps/cli`.
### Import Path Changes from v1
| v1 Import | v2 Import |
|-----------|-----------|
| `@tauri-apps/api/tauri` | `@tauri-apps/api/core` |
| `@tauri-apps/api/window` | `@tauri-apps/api/webviewWindow` |
| `@tauri-apps/api/cli` | `@tauri-apps/plugin-cli` |
| `@tauri-apps/api/clipboard` | `@tauri-apps/plugin-clipboard-manager` |
| `@tauri-apps/api/dialog` | `@tauri-apps/plugin-dialog` |
| `@tauri-apps/api/fs` | `@tauri-apps/plugin-fs` |
| `@tauri-apps/api/global-shortcut` | `@tauri-apps/plugin-global-shortcut` |
| `@tauri-apps/api/http` | `@tauri-apps/plugin-http` |
| `@tauri-apps/api/notification` | `@tauri-apps/plugin-notification` |
| `@tauri-apps/api/shell` | `@tauri-apps/plugin-shell` |
| `@tauri-apps/api/updater` | `@tauri-apps/plugin-updater` |
| `@tauri-apps/api/os` | `@tauri-apps/plugin-os` |
| `@tauri-apps/api/process` | `@tauri-apps/plugin-process` |
**Rule of thumb in v2**: If it's not `core`, `event`, or `webviewWindow`, it's a plugin.
### Plugin JS Package Naming
All v2 plugin JS packages follow: `@tauri-apps/plugin-<name>`
```bash
npm install @tauri-apps/plugin-store
npm install @tauri-apps/plugin-fs
npm install @tauri-apps/plugin-http
```
---
## 7. IPC: Commands, Events & Channels
### Commands (Frontend → Rust)
The primary request-response mechanism:
```javascript
// Frontend (v2 import)
import { invoke } from '@tauri-apps/api/core';
// Simple invocation
const result = await invoke('greet', { name: 'World' });
console.log(result); // "Hello, World! Welcome from Rust."
// With error handling
try {
const token = await invoke('login', { user: 'admin', password: 'secret' });
console.log('Authenticated:', token);
} catch (error) {
console.error('Login failed:', error);
}
```
**Argument naming convention**: Arguments are passed as a JSON object with **camelCase** keys by default. Use `#[tauri::command(rename_all = "snake_case")]` to accept snake_case from the frontend.
### Events (Bidirectional, Multi-Consumer)
Events are fire-and-forget messages. Use them for notifications, streaming status updates, or any data that multiple components might need.
#### Emitting Events from Rust
```rust
use tauri::{AppHandle, Emitter};
// Global event (all listeners receive it)
app_handle.emit("download-progress", 42)?;
// Target a specific window
app_handle.emit_to("settings-panel", "config-changed", new_config)?;
// Filter to specific windows
use tauri::EventTarget;
app_handle.emit_filter("notification", payload, |target| {
matches!(target, EventTarget::WebviewWindow { label } if label == "main")
})?;
```
#### Listening for Events on the Frontend
```javascript
import { listen, once, emit, emitTo } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
// Global listen (receives ALL events with this name)
const unlisten = await listen('download-progress', (event) => {
console.log(`Progress: ${event.payload}%`);
});
// Targeted listen (only events emitted to THIS window)
const appWebview = getCurrentWebviewWindow();
const unlisten2 = await appWebview.listen('config-changed', (event) => {
console.log('New config:', event.payload);
});
// Listen once, then auto-cleanup
await once('initialization-complete', (event) => {
console.log('App ready!', event.payload);
});
// Emit from frontend to Rust
await emit('user-action', { type: 'click', target: 'button' });
// Emit to a specific window from frontend
const target = new WebviewWindow('settings-panel');
await target.emit('settings-request', { key: 'theme' });
```
**Always call `unlisten()`** when the component unmounts or the listener is no longer needed to prevent memory leaks.
#### Listening for Events on the Rust Side
```rust
use tauri::{Listener, Manager};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
// Listen globally
app.listen("user-action", |event| {
println!("Received: {}", event.payload());
});
// Listen on a specific window
let main = app.get_webview_window("main").unwrap();
main.listen("config-changed", |event| {
println!("Config updated: {}", event.data);
});
// Listen once
app.once("ready", |event| {
println!("App is ready!");
});
Ok(())
})
// ...
}
```
### Channels (High-Throughput Streaming)
Channels are the recommended mechanism for streaming large amounts of ordered data from Rust to the frontend. They are faster and more memory-efficient than events for high-throughput scenarios.
```rust
use tauri::ipc::Channel;
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(tag = "event", content = "data")]
enum StreamEvent {
Started { total: usize },
Progress { current: usize, chunk: Vec<u8> },
Finished { total: usize },
}
#[tauri::command]
fn download_file(url: String, on_event: Channel<StreamEvent>) {
on_event.send(StreamEvent::Started { total: 1000 }).unwrap();
// ... streaming logic ...
on_event.send(StreamEvent::Finished { total: 1000 }).unwrap();
}
```
```javascript
import { invoke, Channel } from '@tauri-apps/api/core';
const onEvent = new Channel();
onEvent.onmessage = (event) => {
switch (event.event) {
case 'started': console.log(`Starting: ${event.data.total} bytes`); break;
case 'progress': /* handle chunk */ break;
case 'finished': console.log('Done!'); break;
}
};
await invoke('download_file', { url: 'https://...', onEvent });
```
### Evaluating JavaScript from Rust
```rust
use tauri::Manager;
// In a command or setup hook
let webview = app.get_webview_window("main").unwrap();
webview.eval("document.getElementById('status').textContent = 'Loaded!'")?;
```
For complex data passing, use the `serialize-to-javascript` crate.
---
**Continue to**: [Part 2: Windows, Plugins, WebSocket, State & Migration](./02-windows-plugins-websocket-state-migration.md)

View File

@ -0,0 +1,217 @@
# Tauri 2.0 Agent Prompts — Part 1: System Prompt & Common Task Prompts
> **Purpose**: Copy-paste ready prompts for instructing AI agents to build Tauri 2.0 applications.
> **Constraint**: These prompts are designed for sandbox environments where code compilation is NOT available. Agents must verify correctness using logic, API references, and pattern matching.
---
## Table of Contents
1. [Universal System Prompt (Required Prefix)](#1-universal-system-prompt-required-prefix)
2. [Prompt: Scaffold a New Tauri 2.0 Project](#2-prompt-scaffold-a-new-tauri-20-project)
3. [Prompt: Add a Tauri Plugin](#3-prompt-add-a-tauri-plugin)
4. [Prompt: Create a Custom Command (IPC)](#4-prompt-create-a-custom-command-ipc)
5. [Prompt: Add State Management](#5-prompt-add-state-management)
6. [Prompt: Implement Event-Based Communication](#6-prompt-implement-event-based-communication)
7. [Prompt: Create Multi-Window App](#7-prompt-create-multi-window-app)
**See also**: [Part 2: Advanced Prompts, Checklist & Migration Map](./02-advanced-prompts-checklist-migration.md)
---
## 1. Universal System Prompt (Required Prefix)
> **Always include this as the system context before any task-specific prompt.**
```
You are a Tauri 2.0 expert developer. You build desktop and mobile applications using the Tauri 2.0 framework (Rust backend + web frontend).
CRITICAL ENVIRONMENT CONSTRAINTS:
- You CANNOT compile or run code in this sandbox. Do NOT attempt compilation.
- Verify correctness using logic, API pattern matching, and reference knowledge only.
- Cross-reference every code snippet against known Tauri 2.0 API patterns before outputting.
- If you are uncertain about an API or pattern, say so explicitly rather than guessing.
MANDATORY TAURI 2.0 RULES — NEVER VIOLATE:
1. This is Tauri 2.0 (NOT v1). The APIs are fundamentally different.
2. Use `tauri::WebviewWindow` (NOT `tauri::Window`).
3. Use `tauri::WebviewWindowBuilder` (NOT `tauri::WindowBuilder`).
4. Use `tauri::WebviewUrl` (NOT `tauri::WindowUrl`).
5. Use `Manager::get_webview_window()` (NOT `Manager::get_window()`).
6. Use `tauri::Emitter` trait for `emit()` and `emit_to()` (NOT the old window-only emit).
7. Use `tauri::Listener` trait for `listen()` and `once()` on the Rust side.
8. JS import `invoke` from `@tauri-apps/api/core` (NOT `@tauri-apps/api/tauri`).
9. JS import window utilities from `@tauri-apps/api/webviewWindow` (NOT `@tauri-apps/api/window`).
10. JS plugins import from `@tauri-apps/plugin-<name>` (NOT `@tauri-apps/api/<name>`).
11. Config uses `app` section (NOT `tauri` section), `build.frontendDist` (NOT `build.distDir`).
12. Security uses Capabilities + Permissions (NOT `allowlist`).
13. Every plugin requires: (a) Rust crate in Cargo.toml, (b) `.plugin()` registration in Builder, (c) npm package, (d) permissions in capabilities.
14. Use the `lib.rs` + `main.rs` pattern. Core logic goes in `lib.rs`. `main.rs` only calls `lib::run()`.
15. `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is REQUIRED on the `pub fn run()` function.
16. `invoke_handler` can only be called ONCE — all commands go in a single `tauri::generate_handler![]`.
17. Async commands cannot use borrowed references (`&str`, `&Path`) directly — use owned types or wrap return in `Result`.
18. Window creation from async contexts MUST use `app_handle.run_on_main_thread()`.
19. Always clean up event listeners (call `unlisten()`) on the frontend.
20. Use `tauri = { version = "2" }` and `tauri-build = { version = "2" }` (NOT version 1).
FILE STRUCTURE:
- Frontend source: `src/`
- Rust backend: `src-tauri/`
- Rust entry-point: `src-tauri/src/main.rs` (minimal)
- Core app logic: `src-tauri/src/lib.rs`
- Extra modules: `src-tauri/src/<module>.rs`
- Capabilities: `src-tauri/capabilities/default.json`
- Config: `src-tauri/tauri.conf.json`
- Rust deps: `src-tauri/Cargo.toml`
- Build script: `src-tauri/build.rs`
OFFICIAL DOCUMENTATION:
- Main docs: https://v2.tauri.app
- Rust API: https://docs.rs/tauri/latest/tauri/
- Migration guide: https://v2.tauri.app/start/migrate/from-tauri-1/
- Capabilities: https://v2.tauri.app/security/capabilities/
- Permissions: https://v2.tauri.app/security/permissions/
- Configuration: https://v2.tauri.app/develop/configuration-files/
- Calling Rust: https://v2.tauri.app/develop/calling-rust/
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
- Plugin index: https://v2.tauri.app/plugin/
```
---
## 2. Prompt: Scaffold a New Tauri 2.0 Project
```
Create a new Tauri 2.0 project from scratch with vanilla JavaScript (no frontend framework).
Requirements:
- App name: [APP_NAME]
- Window title: [WINDOW_TITLE]
- Default window size: [WIDTH]x[HEIGHT]
- withGlobalTauri enabled (for window.__TAURI__ access without npm package)
Generate ALL of the following files with complete, correct Tauri 2.0 code:
1. `package.json` — with @tauri-apps/cli v2 devDependency and scripts (dev, build, tauri)
2. `src/index.html` — basic HTML5 boilerplate
3. `src/main.js` — demonstrate invoking a Rust command
4. `src/styles.css` — basic styling
5. `src-tauri/tauri.conf.json` — correct v2 structure (app section, build section, bundle section)
6. `src-tauri/Cargo.toml` — with tauri v2, tauri-build v2, serde, serde_json. Include [lib] with crate-type ["staticlib", "cdylib", "rlib"]
7. `src-tauri/build.rs` — standard tauri_build::build()
8. `src-tauri/capabilities/default.json` — with core:default permission
9. `src-tauri/src/main.rs` — minimal entry-point calling lib::run()
10. `src-tauri/src/lib.rs` — with Builder pattern, mobile_entry_point attribute, a sample "greet" command, and the command registered in invoke_handler
Verify every line follows Tauri 2.0 conventions. Cross-check config structure against the v2 schema.
```
---
## 3. Prompt: Add a Tauri Plugin
```
Add the [PLUGIN_NAME] plugin to an existing Tauri 2.0 project.
Plugin to add: [tauri-plugin-STORE/turi-plugin-FS/etc.]
JS package name: [@tauri-apps/plugin-store/etc.]
Instructions:
1. Add the Rust crate to `src-tauri/Cargo.toml` as `tauri-plugin-[name] = "2"`
2. Register the plugin in `src-tauri/src/lib.rs` using `.plugin(tauri_plugin_[name]::init())` or the Builder pattern if the plugin requires configuration
3. Add the npm package: `@tauri-apps/plugin-[name]`
4. Update `src-tauri/capabilities/default.json` to add the appropriate permission (usually `[name]:default` or specific allow permissions)
5. Provide a frontend usage example showing the correct v2 import from `@tauri-apps/plugin-[name]`
Show the exact changes needed for each file. Do NOT use v1 import paths or API patterns.
```
---
## 4. Prompt: Create a Custom Command (IPC)
```
Create a Tauri 2.0 command called "[COMMAND_NAME]" that [DESCRIBE WHAT IT DOES].
Requirements:
- Define the command using `#[tauri::command]` in a separate module file `src-tauri/src/commands.rs`
- The command must be `pub` (required for separate modules)
- Include proper error handling with `Result<T, String>` or a custom error type
- Register the command in `src-tauri/src/lib.rs` using `mod commands;` and including it in `generate_handler![commands::COMMAND_NAME]`
- Provide the frontend JavaScript code to invoke the command using `invoke()` from `@tauri-apps/api/core` or `window.__TAURI__.core`
- If the command is async, ensure all arguments are owned types (not borrowed references)
Show complete, correct code for both the Rust side and the JS side.
```
---
## 5. Prompt: Add State Management
```
Add shared state management to an existing Tauri 2.0 application.
Requirements:
1. Define a state struct in `src-tauri/src/lib.rs` using `std::sync::Mutex` for thread safety
2. Manage the state in the Builder using `.manage(MyState { ... })`
3. Create at least two commands that read and write the managed state using `tauri::State<'_, MyState>`
4. Show the frontend code for invoking these commands
If persistent storage is also needed:
- Add `tauri-plugin-store = "2"` to Cargo.toml
- Register the plugin
- Add `store:default` to capabilities
- Show frontend usage with `@tauri-apps/plugin-store`
```
---
## 6. Prompt: Implement Event-Based Communication
```
Implement bidirectional event communication in a Tauri 2.0 application.
Requirements:
Rust side:
- Use `use tauri::{Emitter, AppHandle}` to emit events from Rust
- Demonstrate both global emit (`app.emit()`) and targeted emit (`app.emit_to()`)
- If listening for events on the Rust side, use `use tauri::Listener` and `app.listen()` or `app.once()`
Frontend side:
- Import from `@tauri-apps/api/event` (NOT `@tauri-apps/api/tauri`)
- Show `listen()`, `once()`, `emit()`, and `emitTo()` usage
- Demonstrate proper cleanup with `unlisten()`
- Show targeted listening using `getCurrentWebviewWindow().listen()`
Show complete code for:
1. A Rust command that emits a progress event
2. A Rust command that emits a completion event to a specific window
3. Frontend listeners that handle these events
4. Frontend emitting an event back to Rust
Make sure `core:event:default` (or specific `core:event:allow-*` permissions) is included in capabilities.
```
---
## 7. Prompt: Create Multi-Window App
```
Create a Tauri 2.0 application with multiple windows.
Requirements:
1. Main window defined in `tauri.conf.json` with label "main"
2. A command `open_secondary` that creates a new window dynamically using `WebviewWindowBuilder`
3. The secondary window should load a local HTML file (`WebviewUrl::App("secondary.html".into())`)
4. If creating the window from an async context, use `app_handle.run_on_main_thread()`
5. Check for existing windows with the same label using `app_handle.get_webview_window(label)` and focus it if it exists
6. Show frontend code for creating windows using `new WebviewWindow(label, options)` from `@tauri-apps/api/webviewWindow`
CRITICAL: Use `tauri::WebviewWindowBuilder` and `tauri::WebviewUrl` (NOT the v1 `WindowBuilder`/`WindowUrl`).
CRITICAL: Use `app_handle.get_webview_window()` (NOT `app_handle.get_window()`).
```
---
**Continue to**: [Part 2: Advanced Prompts, Checklist & Migration Map](./02-advanced-prompts-checklist-migration.md)

View File

@ -1,4 +1,4 @@
# Tauri 2.0 Agent Prompts — Definitive Collection
# Tauri 2.0 Agent Prompts — Part 2: Advanced Prompts, Checklist & Migration Map
> **Purpose**: Copy-paste ready prompts for instructing AI agents to build Tauri 2.0 applications.
> **Constraint**: These prompts are designed for sandbox environments where code compilation is NOT available. Agents must verify correctness using logic, API references, and pattern matching.
@ -7,13 +7,6 @@
## Table of Contents
1. [Universal System Prompt (Required Prefix)](#1-universal-system-prompt-required-prefix)
2. [Prompt: Scaffold a New Tauri 2.0 Project](#2-prompt-scaffold-a-new-tauri-20-project)
3. [Prompt: Add a Tauri Plugin](#3-prompt-add-a-tauri-plugin)
4. [Prompt: Create a Custom Command (IPC)](#4-prompt-create-a-custom-command-ipc)
5. [Prompt: Add State Management](#5-prompt-add-state-management)
6. [Prompt: Implement Event-Based Communication](#6-prompt-implement-event-based-communication)
7. [Prompt: Create Multi-Window App](#7-prompt-create-multi-window-app)
8. [Prompt: Build a WebSocket Server in Rust](#8-prompt-build-a-websocket-server-in-rust)
9. [Prompt: Implement Script Injection](#9-prompt-implement-script-injection)
10. [Prompt: Set Up Capabilities & Permissions](#10-prompt-set-up-capabilities--permissions)
@ -23,200 +16,7 @@
14. [Quick Reference: v1 vs v2 API Map](#14-quick-reference-v1-vs-v2-api-map)
15. [Prompt: Debug a Tauri 2.0 Build/Compile Error](#15-prompt-debug-a-tauri-20-buildcompile-error)
---
## 1. Universal System Prompt (Required Prefix)
> **Always include this as the system context before any task-specific prompt.**
```
You are a Tauri 2.0 expert developer. You build desktop and mobile applications using the Tauri 2.0 framework (Rust backend + web frontend).
CRITICAL ENVIRONMENT CONSTRAINTS:
- You CANNOT compile or run code in this sandbox. Do NOT attempt compilation.
- Verify correctness using logic, API pattern matching, and reference knowledge only.
- Cross-reference every code snippet against known Tauri 2.0 API patterns before outputting.
- If you are uncertain about an API or pattern, say so explicitly rather than guessing.
MANDATORY TAURI 2.0 RULES — NEVER VIOLATE:
1. This is Tauri 2.0 (NOT v1). The APIs are fundamentally different.
2. Use `tauri::WebviewWindow` (NOT `tauri::Window`).
3. Use `tauri::WebviewWindowBuilder` (NOT `tauri::WindowBuilder`).
4. Use `tauri::WebviewUrl` (NOT `tauri::WindowUrl`).
5. Use `Manager::get_webview_window()` (NOT `Manager::get_window()`).
6. Use `tauri::Emitter` trait for `emit()` and `emit_to()` (NOT the old window-only emit).
7. Use `tauri::Listener` trait for `listen()` and `once()` on the Rust side.
8. JS import `invoke` from `@tauri-apps/api/core` (NOT `@tauri-apps/api/tauri`).
9. JS import window utilities from `@tauri-apps/api/webviewWindow` (NOT `@tauri-apps/api/window`).
10. JS plugins import from `@tauri-apps/plugin-<name>` (NOT `@tauri-apps/api/<name>`).
11. Config uses `app` section (NOT `tauri` section), `build.frontendDist` (NOT `build.distDir`).
12. Security uses Capabilities + Permissions (NOT `allowlist`).
13. Every plugin requires: (a) Rust crate in Cargo.toml, (b) `.plugin()` registration in Builder, (c) npm package, (d) permissions in capabilities.
14. Use the `lib.rs` + `main.rs` pattern. Core logic goes in `lib.rs`. `main.rs` only calls `lib::run()`.
15. `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is REQUIRED on the `pub fn run()` function.
16. `invoke_handler` can only be called ONCE — all commands go in a single `tauri::generate_handler![]`.
17. Async commands cannot use borrowed references (`&str`, `&Path`) directly — use owned types or wrap return in `Result`.
18. Window creation from async contexts MUST use `app_handle.run_on_main_thread()`.
19. Always clean up event listeners (call `unlisten()`) on the frontend.
20. Use `tauri = { version = "2" }` and `tauri-build = { version = "2" }` (NOT version 1).
FILE STRUCTURE:
- Frontend source: `src/`
- Rust backend: `src-tauri/`
- Rust entry-point: `src-tauri/src/main.rs` (minimal)
- Core app logic: `src-tauri/src/lib.rs`
- Extra modules: `src-tauri/src/<module>.rs`
- Capabilities: `src-tauri/capabilities/default.json`
- Config: `src-tauri/tauri.conf.json`
- Rust deps: `src-tauri/Cargo.toml`
- Build script: `src-tauri/build.rs`
OFFICIAL DOCUMENTATION:
- Main docs: https://v2.tauri.app
- Rust API: https://docs.rs/tauri/latest/tauri/
- Migration guide: https://v2.tauri.app/start/migrate/from-tauri-1/
- Capabilities: https://v2.tauri.app/security/capabilities/
- Permissions: https://v2.tauri.app/security/permissions/
- Configuration: https://v2.tauri.app/develop/configuration-files/
- Calling Rust: https://v2.tauri.app/develop/calling-rust/
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
- Plugin index: https://v2.tauri.app/plugin/
```
---
## 2. Prompt: Scaffold a New Tauri 2.0 Project
```
Create a new Tauri 2.0 project from scratch with vanilla JavaScript (no frontend framework).
Requirements:
- App name: [APP_NAME]
- Window title: [WINDOW_TITLE]
- Default window size: [WIDTH]x[HEIGHT]
- withGlobalTauri enabled (for window.__TAURI__ access without npm package)
Generate ALL of the following files with complete, correct Tauri 2.0 code:
1. `package.json` — with @tauri-apps/cli v2 devDependency and scripts (dev, build, tauri)
2. `src/index.html` — basic HTML5 boilerplate
3. `src/main.js` — demonstrate invoking a Rust command
4. `src/styles.css` — basic styling
5. `src-tauri/tauri.conf.json` — correct v2 structure (app section, build section, bundle section)
6. `src-tauri/Cargo.toml` — with tauri v2, tauri-build v2, serde, serde_json. Include [lib] with crate-type ["staticlib", "cdylib", "rlib"]
7. `src-tauri/build.rs` — standard tauri_build::build()
8. `src-tauri/capabilities/default.json` — with core:default permission
9. `src-tauri/src/main.rs` — minimal entry-point calling lib::run()
10. `src-tauri/src/lib.rs` — with Builder pattern, mobile_entry_point attribute, a sample "greet" command, and the command registered in invoke_handler
Verify every line follows Tauri 2.0 conventions. Cross-check config structure against the v2 schema.
```
---
## 3. Prompt: Add a Tauri Plugin
```
Add the [PLUGIN_NAME] plugin to an existing Tauri 2.0 project.
Plugin to add: [tauri-plugin-STORE/turi-plugin-FS/etc.]
JS package name: [@tauri-apps/plugin-store/etc.]
Instructions:
1. Add the Rust crate to `src-tauri/Cargo.toml` as `tauri-plugin-[name] = "2"`
2. Register the plugin in `src-tauri/src/lib.rs` using `.plugin(tauri_plugin_[name]::init())` or the Builder pattern if the plugin requires configuration
3. Add the npm package: `@tauri-apps/plugin-[name]`
4. Update `src-tauri/capabilities/default.json` to add the appropriate permission (usually `[name]:default` or specific allow permissions)
5. Provide a frontend usage example showing the correct v2 import from `@tauri-apps/plugin-[name]`
Show the exact changes needed for each file. Do NOT use v1 import paths or API patterns.
```
---
## 4. Prompt: Create a Custom Command (IPC)
```
Create a Tauri 2.0 command called "[COMMAND_NAME]" that [DESCRIBE WHAT IT DOES].
Requirements:
- Define the command using `#[tauri::command]` in a separate module file `src-tauri/src/commands.rs`
- The command must be `pub` (required for separate modules)
- Include proper error handling with `Result<T, String>` or a custom error type
- Register the command in `src-tauri/src/lib.rs` using `mod commands;` and including it in `generate_handler![commands::COMMAND_NAME]`
- Provide the frontend JavaScript code to invoke the command using `invoke()` from `@tauri-apps/api/core` or `window.__TAURI__.core`
- If the command is async, ensure all arguments are owned types (not borrowed references)
Show complete, correct code for both the Rust side and the JS side.
```
---
## 5. Prompt: Add State Management
```
Add shared state management to an existing Tauri 2.0 application.
Requirements:
1. Define a state struct in `src-tauri/src/lib.rs` using `std::sync::Mutex` for thread safety
2. Manage the state in the Builder using `.manage(MyState { ... })`
3. Create at least two commands that read and write the managed state using `tauri::State<'_, MyState>`
4. Show the frontend code for invoking these commands
If persistent storage is also needed:
- Add `tauri-plugin-store = "2"` to Cargo.toml
- Register the plugin
- Add `store:default` to capabilities
- Show frontend usage with `@tauri-apps/plugin-store`
```
---
## 6. Prompt: Implement Event-Based Communication
```
Implement bidirectional event communication in a Tauri 2.0 application.
Requirements:
Rust side:
- Use `use tauri::{Emitter, AppHandle}` to emit events from Rust
- Demonstrate both global emit (`app.emit()`) and targeted emit (`app.emit_to()`)
- If listening for events on the Rust side, use `use tauri::Listener` and `app.listen()` or `app.once()`
Frontend side:
- Import from `@tauri-apps/api/event` (NOT `@tauri-apps/api/tauri`)
- Show `listen()`, `once()`, `emit()`, and `emitTo()` usage
- Demonstrate proper cleanup with `unlisten()`
- Show targeted listening using `getCurrentWebviewWindow().listen()`
Show complete code for:
1. A Rust command that emits a progress event
2. A Rust command that emits a completion event to a specific window
3. Frontend listeners that handle these events
4. Frontend emitting an event back to Rust
Make sure `core:event:default` (or specific `core:event:allow-*` permissions) is included in capabilities.
```
---
## 7. Prompt: Create Multi-Window App
```
Create a Tauri 2.0 application with multiple windows.
Requirements:
1. Main window defined in `tauri.conf.json` with label "main"
2. A command `open_secondary` that creates a new window dynamically using `WebviewWindowBuilder`
3. The secondary window should load a local HTML file (`WebviewUrl::App("secondary.html".into())`)
4. If creating the window from an async context, use `app_handle.run_on_main_thread()`
5. Check for existing windows with the same label using `app_handle.get_webview_window(label)` and focus it if it exists
6. Show frontend code for creating windows using `new WebviewWindow(label, options)` from `@tauri-apps/api/webviewWindow`
CRITICAL: Use `tauri::WebviewWindowBuilder` and `tauri::WebviewUrl` (NOT the v1 `WindowBuilder`/`WindowUrl`).
CRITICAL: Use `app_handle.get_webview_window()` (NOT `app_handle.get_window()`).
```
**See also**: [Part 1: System Prompt & Common Task Prompts](./01-system-prompt-and-common-prompts.md)
---
@ -573,3 +373,7 @@ Follow this structured diagnosis process:
If the error doesn't match any known category, say so explicitly and provide your best analysis based on the error message, Rust compiler output, and Tauri 2.0 API knowledge.
```
---
**Back to**: [Part 1: System Prompt & Common Task Prompts](./01-system-prompt-and-common-prompts.md)

View File

@ -0,0 +1,833 @@
# Tauri 2.0 Error Resolution Guide — Part 2: Runtime, Platform & Mobile Errors
> **Version**: 2.0 | **Last Updated**: 2026-05-31
> **Official Docs**: https://v2.tauri.app
>
> This file covers Categories I-R and the Quick-Reference (runtime, platform-specific, and mobile errors). Part 1 covers build, config and plugin errors.
---
## Table of Contents
9. [Category I: Event System Errors](#category-i-event-system-errors)
10. [Category J: Content Security Policy (CSP) Errors](#category-j-content-security-policy-csp-errors)
11. [Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)](#category-k-frontend-integration-errors-vite--nextjs--sveltekit)
12. [Category L: WebSocket Errors](#category-l-websocket-errors)
13. [Category M: Path & Asset Protocol Errors](#category-m-path--asset-protocol-errors)
14. [Category N: Shell & Sidecar Errors](#category-n-shell--sidecar-errors)
15. [Category O: Mobile Build Errors (iOS & Android)](#category-o-mobile-build-errors-ios--android)
16. [Category P: Platform-Specific Build Errors](#category-p-platform-specific-build-errors)
17. [Category Q: Panics & Runtime Crashes](#category-q-panics--runtime-crashes)
18. [Category R: Error Handling Best Practices](#category-r-error-handling-best-practices)
19. [Quick-Reference: Top 25 Most Common Errors](#quick-reference-top-25-most-common-errors)
**See also**: [Part 1: Build, Config & Plugin Errors](./01-build-config-permission-plugin-errors.md)
---
## Category I: Event System Errors
### I1. Events Emitted But Not Received by New Window
**Error:** Events emitted to a newly created window are not received the first time.
**Cause:** Race condition — the event is emitted before the listener is attached in the new window.
**Solution:** Use a handshake pattern:
```javascript
// In new window — announce ready
import { emit } from '@tauri-apps/api/event';
emit('window-ready', { label: 'my-window' });
// In main window — respond after ready signal
listen('window-ready', (event) => {
emitTo(event.payload.label, 'initial-data', myData);
});
```
---
### I2. Events Not Working Between Windows
**Error:** Events emitted from one window are not received by another window.
**Cause:** Using `emit()` (which broadcasts to all) vs. `emitTo()` (which targets a specific window) incorrectly, or listeners scoped incorrectly.
**Solution:**
```rust
// Emit to ALL windows
app.emit("my-event", payload)?;
// Emit to a SPECIFIC window
app.emit_to("settings-panel", "my-event", payload)?;
```
```javascript
// Frontend: listen globally
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('my-event', handler);
// Frontend: listen on specific window only
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
const unlisten2 = await appWebview.listen('my-event', handler);
```
---
### I3. Rapid Event Emission Causes Panic
**Error:** App crashes when emitting large numbers of events rapidly.
**Cause:** Internal buffer overflow or deadlock in the event system.
**Solution:** Throttle/debounce event emission. For high-frequency data, use `Channel<T>` instead of events:
```rust
use tauri::ipc::Channel;
#[tauri::command]
fn stream_data(on_event: Channel<MyEvent>) {
for item in data_source {
on_event.send(item).unwrap();
}
}
```
---
## Category J: Content Security Policy (CSP) Errors
### J1. `Refused to connect to 'http://ipc.localhost'`
**Error:**
```
Refused to connect to 'http://ipc.localhost/test' because it violates the document's Content Security Policy.
```
**Cause:** External URLs loaded in webviews have their own CSP that doesn't allow connections to Tauri's IPC endpoint.
**Solution:** Configure CSP to include IPC:
```json
{
"app": {
"security": {
"csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://your-url.com"
}
}
}
```
For maximum compatibility with injected scripts on external pages: `"csp": null` (use with caution).
---
### J2. CSS-in-JS Libraries Blocked by CSP
**Error:** Styles/scripts from styled-components, emotion, or other CSS-in-JS libraries are blocked.
**Cause:** Default CSP doesn't allow inline styles.
**Solution:** Add `'unsafe-inline'` to `style-src`:
```json
{
"app": {
"security": {
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'"
}
}
}
```
---
## Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)
### K1. Vite Dev Server Not Connecting / White Screen
**Error:** Tauri window opens but shows blank white screen. Dev server connection not established.
**Cause:** Port mismatch between `devUrl` in `tauri.conf.json` and Vite's actual port.
**Solution:**
1. Ensure `devUrl` matches Vite's actual port: `"devUrl": "http://localhost:5173"`
2. For mobile, use `process.env.TAURI_DEV_HOST` as the host in Vite config
3. Set `server.host` in `vite.config.js` to allow network access
---
### K2. Vite Watches `src-tauri` — Infinite Recompilation
**Error:** App keeps recompiling Rust code repeatedly in an infinite loop.
**Cause:** Vite's file watcher detects changes in `src-tauri/` (from Cargo build output) and triggers re-runs.
**Solution:** Add `src-tauri` to Vite's ignore list:
```javascript
// vite.config.js
export default defineConfig({
server: {
watch: {
ignored: ["**/src-tauri/**"]
}
}
});
```
---
### K3. Next.js App Router Breaks in Production
**Error:** `useEffect` hooks broken, syntax errors in chunk files, component rendering failures in production.
**Cause:** Next.js App Router uses React Server Components and advanced chunking that conflicts with Tauri's custom protocol.
**Solution:** Use static export only:
```javascript
// next.config.js
module.exports = {
output: 'export', // Required — no SSR
images: { unoptimized: true }, // No server-side image optimization
};
```
Add `'use client'` directives to all interactive components.
---
### K4. Next.js `assetPrefix` Breaks Hot Reload
**Error:** Hot reload doesn't work when `assetPrefix` is set for Tauri.
**Cause:** `assetPrefix` changes the URL for WebSocket connections, breaking HMR.
**Solution:** Only set `assetPrefix` in production:
```javascript
// next.config.js
module.exports = {
output: 'export',
assetPrefix: process.env.NODE_ENV === 'production' ? './' : undefined,
};
```
---
### K5. Next.js Standalone Output — `asset not found: index.html`
**Error:**
```
asset not found: index.html
```
**Cause:** Next.js standalone output doesn't produce a static `index.html` file.
**Solution:** Use `output: "export"`, NOT `standalone`:
```javascript
module.exports = {
output: 'export', // Required for Tauri
// NOT output: 'standalone'
};
```
---
### K6. SvelteKit Blank Screen / PostCSS Errors
**Error:** Blank white screen or `[postcss] Internal server error`.
**Cause:** SvelteKit's routing conflicts with Tauri's static file serving.
**Solution:** Use `@sveltejs/adapter-static`:
```javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({ fallback: 'index.html' })
}
};
```
Add to root layout: `export const ssr = false;`
---
## Category L: WebSocket Errors
### L1. WebSocket Connection Closing / "Broken Pipe"
**Error:** WebSocket connection closes unexpectedly after app has been open for a long time.
**Cause:** Idle timeouts or OS-level connection cleanup.
**Solution:** Implement ping/pong keepalive in custom WebSocket connections:
```javascript
const ws = new WebSocket('ws://127.0.0.1:8080');
// Keepalive
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
```
---
### L2. WebSocket Plugin — Unstable Connections
**Error:** Messages not received right after connection with `tauri-plugin-websocket`.
**Cause:** The WebSocket connection may not be fully initialized before messages are sent.
**Solution:** Wait for the `onopen` event:
```javascript
import WebSocket from '@tauri-apps/plugin-websocket';
const ws = await WebSocket.connect('ws://localhost:8080');
// Ensure ready before sending
ws.addListener((msg) => {
console.log('Received:', msg);
});
```
---
### L3. WebSocket + Tauri Client Conflict
**Error:** WebSocket client code and Tauri app cannot run simultaneously.
**Cause:** Resource conflicts between the Tauri WebView and WebSocket connections, especially on mobile.
**Solution:** Use `tauri-plugin-websocket` instead of raw `WebSocket` API for mobile compatibility.
---
## Category M: Path & Asset Protocol Errors
### M1. 403 Forbidden on Asset Protocol URLs
**Error:** `403 Forbidden` when accessing files via `asset://` protocol.
**Cause:** The asset protocol has a scope restricting which directories can be accessed.
**Solution:** Configure asset protocol scope:
```json
{
"identifier": "asset-capability",
"permissions": [
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$RESOURCE/**" }]
}
]
}
```
Use `convertFileSrc()` from `@tauri-apps/api/core` for proper path conversion.
---
### M2. `resource_dir()` Returns Unknown Path
**Error:** `PathResolver.resource_dir()` returns empty or unknown path.
**Cause:** A custom `target-dir` in `~/.cargo/config.toml` breaks resource resolution logic.
**Solution:** Remove global `target-dir` setting, or configure it per-project instead of globally.
---
### M3. Path Traversal Prevention Blocking Valid Paths
**Error:** `path traversal` error from `@tauri-apps/plugin-fs`.
**Cause:** The FS plugin blocks paths containing `..`.
**Solution:** Normalize paths before use:
```javascript
import { resolve } from '@tauri-apps/api/path';
const normalizedPath = await resolve('some', 'relative', 'path');
```
---
## Category N: Shell & Sidecar Errors
### N1. Sidecar Binary Not Found
**Error:**
```
Failed to spawn my-binary: Io(Os { code: 3, kind: NotFound })
```
**Cause:** The sidecar binary doesn't follow Tauri's naming convention. Tauri appends the target triple to the binary name.
**Solution:**
```json
// tauri.conf.json
{
"bundle": {
"externalBin": ["binaries/my-binary"]
}
}
```
Name the binary with the target triple suffix:
- Windows: `my-binary-x86_64-pc-windows-msvc.exe`
- macOS: `my-binary-aarch64-apple-darwin`
- Linux: `my-binary-x86_64-unknown-linux-gnu`
Reference in Rust without the suffix:
```rust
app.shell().sidecar("binaries/my-binary").unwrap()
```
---
## Category O: Mobile Build Errors (iOS & Android)
### O1. `xcodebuild exited with code 65`
**Error:**
```
error: failed to run custom build command for tauri v2 ... xcodebuild exited with code 65
```
**Cause:** iOS build failure. Common causes: code signing issues, stale build artifacts, Xcode version incompatibility.
**Solution:**
1. Open Xcode project: `open src-tauri/gen/apple/Project.xcworkspace`
2. Set Signing & Capabilities → Team
3. Clean build: `Cmd+Shift+K` in Xcode
4. Clean pods: `cd src-tauri/gen/apple && rm -rf build Pods && pod install`
---
### O2. `can't find crate for core` (Missing Mobile Target)
**Error:**
```
error[E0463]: can't find crate for `core`
```
When building for iOS or Android.
**Cause:** The Rust target for the mobile platform is not installed.
**Solution:**
```bash
# iOS
rustup target add aarch64-apple-ios
# Android (all targets)
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
cargo install cargo-ndk
```
---
### O3. `building for 'iOS', but linking in object file built for 'macOS'`
**Error:**
```
building for 'iOS', but linking in object file built for 'macOS'
```
**Cause:** A native dependency was compiled for the host platform instead of the iOS target.
**Solution:** Check which dependency causes the issue: `cargo build --target aarch64-apple-ios -v`. Ensure all dependencies support cross-compilation to iOS.
---
### O4. Android Build Fails — `Cannot find module 'tauri'`
**Error:**
```
Cannot find module 'tauri' during Gradle build
```
**Cause:** Node.js executable can't be found when Gradle's BuildTask runs.
**Solution:** Ensure Node.js is available system-wide. Use `npx tauri` or configure the Gradle BuildTask to use the correct node path.
---
### O5. Android App Crashes on Launch
**Error:** App immediately crashes after deployment to Android device/emulator.
**Cause:** Missing Android SDK components, incorrect NDK configuration, or incompatible Gradle/AGP versions.
**Solution:**
1. Ensure correct Android SDK and NDK are installed
2. Run `npx tauri android init` to set up properly
3. Check `android/app/build.gradle` has correct Tauri integration
4. Verify ADB connection: `adb devices`
---
### O6. `tauri android dev` Hangs Indefinitely
**Error:** Command hangs with no output or progress.
**Cause:** Emulator/device connection failure or Gradle build hanging.
**Solution:**
1. Verify ADB: `adb devices`
2. Clean Gradle cache: `cd android && ./gradlew clean`
3. Use a physical device instead of emulator
4. Increase Gradle memory in `android/gradle.properties`
---
### O7. `mobile_entry_point` Conflicts with `#[tokio::main]`
**Error:** App crashes on iOS when using `#[tokio::main]` with `mobile_entry_point`.
**Cause:** `mobile_entry_point` and `#[tokio::main]` generate conflicting function signatures.
**Solution:**
```rust
// ❌ WRONG
#[cfg_attr(mobile, tauri::mobile_entry_point)]
#[tokio::main]
pub async fn run() { /* ... */ }
// ✅ CORRECT — use async_runtime::block_on
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::async_runtime::block_on(async {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
});
}
```
---
### O8. No Code Signing Certificates Found (iOS)
**Error:**
```
No code signing certificates found. You must add one and set the certificate development team ID.
```
**Cause:** No Apple Developer certificate configured.
**Solution:** Open `src-tauri/gen/apple/Project.xcworkspace` in Xcode → Signing & Capabilities → select Team. Or use environment variables: `APPLE_SIGNING_IDENTITY`, `APPLE_TEAM_ID`.
---
### O9. Android SDK Not Found
**Error:**
```
Android SDK not found at ANDROID_HOME. Please set the ANDROID_HOME environment variable.
```
Or Gradle fails with `SDK location not found`.
**Cause:** The `ANDROID_HOME` (or `ANDROID_SDK_ROOT`) environment variable is not set, or points to a nonexistent directory. Tauri's Android build requires the Android SDK to be available.
**Solution:**
1. Install Android Studio, which bundles the SDK
2. Set the environment variable:
```bash
# macOS/Linux — add to ~/.zshrc or ~/.bashrc
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/platform-tools:$PATH"
# Windows — set via System Properties → Environment Variables
ANDROID_HOME=C:\Users\<you>\AppData\Local\Android\Sdk
```
3. Verify: `echo $ANDROID_HOME` and `adb version`
4. Re-run `npx tauri android init`
---
### O10. iOS Development Requires macOS
**Error:**
```
iOS is not supported on this platform
```
Or `xcodebuild: command not found` when running on Linux or Windows.
**Cause:** iOS builds require Xcode and the Apple toolchain, which are only available on macOS. Cross-compilation to iOS from Linux or Windows is not supported by Tauri.
**Solution:**
1. Use a Mac (physical or CI runner like GitHub Actions `macos-latest`)
2. Install Xcode from the Mac App Store
3. Accept the Xcode license: `sudo xcodebuild -license accept`
4. Install CocoaPods: `sudo gem install cocoapods` (or `brew install cocoapods`)
5. For CI, use GitHub Actions with `macos-latest` runner
---
### O11. `pod install` Failed (iOS)
**Error:**
```
Pod installation failed. CocoaPods could not find compatible versions for pod "TauriDriver"
```
Or `pod: command not found`.
**Cause:** CocoaPods is not installed, outdated, or the Podfile.lock is stale and conflicts with updated dependencies.
**Solution:**
1. Ensure CocoaPods is installed:
```bash
sudo gem install cocoapods
# Or via Homebrew:
brew install cocoapods
```
2. Clean and reinstall pods:
```bash
cd src-tauri/gen/apple
rm -rf Podfile.lock Pods
pod install --repo-update
```
3. If version conflicts persist, update CocoaPods to the latest version: `sudo gem install cocoapods --pre`
4. Ensure your Xcode Command Line Tools are up to date: `xcode-select --install`
---
## Category P: Platform-Specific Build Errors
### P1. Linux — Missing System Dependencies
**Error:** Build fails with GTK/WebKit-related errors.
**Cause:** Missing system libraries for GTK/WebKit development.
**Solution:**
```bash
# Ubuntu/Debian
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev \
librsvg2-dev libsoup-3.0-dev
# Fedora
sudo dnf install webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel \
librsvg2-devel libsoup3-devel
# Arch
sudo pacman -S webkit2gtk-4.1 gtk3 libappindicator-gtk3 librsvg libsoup3
```
---
### P2. Linux — Failed to Find ICNS Encoder
**Error:**
```
error: failed to compile tauri-bundler: failed to find ICNS encoder
```
**Cause:** Missing icon tools for macOS bundle creation on Linux.
**Solution:** `sudo apt install icnsutils` (Debian/Ubuntu) or equivalent package.
---
### P3. Linux — Failed to Run `linuxdeploy`
**Error:**
```
error: failed to bundle project: failed to run linuxdeploy
```
**Cause:** Missing AppImage bundling dependencies.
**Solution:** Install `linuxdeploy` and `linuxdeploy-plugin-gtk`. Ensure `appimage-builder` is available.
---
## Category Q: Panics & Runtime Crashes
### Q1. Generic "error while running tauri application"
**Error:**
```
thread 'main' panicked at src\main.rs:14:10:
error while running tauri application: <inner error>
```
**Cause:** Top-level error wrapper. The inner error reveals the actual problem — check plugin initialization, WebView creation, or config validation.
**Solution:** Replace `.expect()` with better error reporting:
```rust
if let Err(e) = tauri::Builder::default()
.plugin(tauri_plugin_http::init())
.run(tauri::generate_context!())
{
eprintln!("Failed to start: {e}");
std::process::exit(1);
}
```
---
### Q2. Panic in tauri-runtime-wry on Wayland
**Error:**
```
Panic in tauri-runtime-wry during init on Wayland
```
**Cause:** Wayland compositors missing `wlr_data_control_manager v1` protocol cause the clipboard crate to panic.
**Solution:** Use XWayland, or use a Wayland compositor that supports the protocol.
---
### Q3. `EXC_BAD_ACCESS` Crash on iOS
**Error:** App crashes on physical iPhone with `EXC_BAD_ACCESS` but works in simulator.
**Cause:** Memory access violations in native code bridging.
**Solution:** Enable address sanitizer in Xcode. Check for use-after-free or null pointer issues in Rust FFI code.
---
### Q4. Panic When `Builder::setup` Hook Fails
**Error:** App panics immediately when the `setup` callback returns an error.
**Cause:** `App::run()` panics when setup fails — no graceful error handling.
**Solution:** Wrap setup logic properly:
```rust
tauri::Builder::default()
.setup(|app| {
match critical_init(app) {
Ok(_) => Ok(()),
Err(e) => {
eprintln!("Setup failed: {e}");
Err(e.into())
}
}
})
.run(tauri::generate_context!())
```
---
## Category R: Error Handling Best Practices
### R1. Never Panic Inside Tauri Commands
Panic in a **synchronous** command crashes the app. Panic in an **asynchronous** command causes a Promise that never resolves (silent failure). Always return `Result`:
```rust
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Network(#[from] reqwest::Error),
#[error("{0}")]
Custom(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
s.serialize_str(&self.to_string())
}
}
#[tauri::command]
fn my_command() -> Result<String, AppError> {
let data = std::fs::read_to_string("config.toml")?;
Ok(data)
}
```
### R2. Frontend Error Handling
```javascript
import { invoke } from '@tauri-apps/api/core';
try {
const result = await invoke('my_command', { arg: 'value' });
console.log('Success:', result);
} catch (error) {
console.error('Command failed:', error);
// Show user-friendly error in UI
}
```
### R3. Structured Errors for the Frontend
```rust
#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
Io(String),
Network(String),
Custom(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where S: serde::ser::Serializer {
let kind = match self {
Self::Io(e) => ErrorKind::Io(e.to_string()),
Self::Network(e) => ErrorKind::Network(e.to_string()),
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
};
kind.serialize(s)
}
}
```
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
### R4. Cleanup Event Listeners
```javascript
import { listen } from '@tauri-apps/api/event';
// ❌ WRONG — memory leak
useEffect(() => {
listen('my-event', handler);
return () => {}; // No cleanup!
}, []);
// ✅ CORRECT — proper cleanup
useEffect(() => {
const unlisten = listen('my-event', handler);
return () => { unlisten.then(fn => fn()); };
}, []);
```
---
## Quick-Reference: Top 25 Most Common Errors
| # | Error Message | Category | Quick Fix |
|---|---|---|---|
| 1 | `X not allowed. Plugin not found` | Permission | Add permissions to `capabilities/default.json` |
| 2 | `Additional properties not allowed ('devPath', 'distDir')` | Config | Rename to `devUrl` / `frontendDist` |
| 3 | `__TAURI__ is not defined` | Frontend | Set `withGlobalTauri: true` or use npm imports |
| 4 | `AssetNotFound("index.html")` | Config | Fix `frontendDist` path to match bundler output |
| 5 | `unresolved import 'tauri::api'` | Migration | Replace with plugins (`tauri-plugin-*`) |
| 6 | `cannot find type 'Window' in 'tauri'` | Migration | Use `WebviewWindow` |
| 7 | `failed to create webview: WebView2 error` | Webview | Install WebView2; remove custom browser args |
| 8 | `Path not allowed on the configured scope` | Permission | Add FS scope permissions |
| 9 | `command not found` | IPC | Add command to `generate_handler![]` |
| 10 | `cannot find type 'SystemTray'` | Migration | Use `tauri::tray::TrayIconBuilder` |
| 11 | `lifetime not constrained` in async | Async | Use owned types (`String`, not `&str`) |
| 12 | `MutexGuard cannot be sent between threads` | Async | Use `tokio::sync::Mutex` instead of `std::sync::Mutex` |
| 13 | `xcodebuild exited with code 65` | Mobile | Set signing team in Xcode |
| 14 | `can't find crate for core` | Mobile | `rustup target add <target>` |
| 15 | `Additional properties not allowed ('allowlist')` | Config | Remove; use v2 capabilities system |
| 16 | `beforeDevCommand terminated with non-zero status` | Config | Fix npm scripts and port numbers |
| 17 | Vite infinite recompilation | Frontend | Add `src-tauri` to `watch.ignored` |
| 18 | Next.js `useEffect` broken in production | Frontend | Use `output: "export"` + `'use client'` |
| 19 | `program not allowed on shell scope` | Shell | Add program to shell capability scope |
| 20 | `Refused to connect to 'http://ipc.localhost'` | CSP | Add `ipc: http://ipc.localhost` to `connect-src` |
| 21 | Only last `invoke_handler` works | IPC | Combine all commands in one `generate_handler![]` |
| 22 | `GDK may only be used from the main thread` | Platform | Update Tauri; avoid cross-thread UI calls |
| 23 | `PluginInitialization("http", "invalid type")` | Config | Migrate old plugins config to v2 capabilities |
| 24 | Sidecar `Io(Os { code: 3 })` | Shell | Fix binary naming with target triple suffix |
| 25 | `error while running tauri application` | Generic | Check inner error; improve error handling |
---
**Back to**: [Part 1: Build, Config & Plugin Errors](./01-build-config-permission-plugin-errors.md)

View File

@ -1,19 +1,14 @@
# Tauri 2.0 Definitive Developer Guide
# Tauri 2.0 Developer Guide — Part 2: Windows, Plugins, WebSocket, State & Migration
> **Version**: 2.0 | **Last Updated**: 2026-05-31
> **Official Docs**: https://v2.tauri.app
>
> This file covers Sections 8-15 of the Definitive Guide. Part 1 covers Architecture, Config, Capabilities, Commands & IPC.
---
## Table of Contents
1. [Introduction & Architecture Overview](#1-introduction--architecture-overview)
2. [Project Structure & Scaffolding](#2-project-structure--scaffolding)
3. [Configuration System (`tauri.conf.json`)](#3-configuration-system-tauriconfjson)
4. [Capabilities & Permissions (Security Model)](#4-capabilities--permissions-security-model)
5. [Rust Backend: `lib.rs` & Commands](#5-rust-backend-librs--commands)
6. [Frontend Integration Patterns](#6-frontend-integration-patterns)
7. [IPC: Commands, Events & Channels](#7-ipc-commands-events--channels)
8. [Window Management](#8-window-management)
9. [Plugin Ecosystem](#9-plugin-ecosystem)
10. [Native WebSocket Server (Rust-side)](#10-native-websocket-server-rust-side)
@ -23,914 +18,9 @@
14. [Common Anti-Patterns to Avoid](#14-common-anti-patterns-to-avoid)
15. [Quick Reference Cheat Sheet](#15-quick-reference-cheat-sheet)
---
## 1. Introduction & Architecture Overview
Tauri 2.0 is a framework for building small, fast binaries for all major desktop and mobile platforms. It uses a **hybrid architecture** where a Rust backend handles system-level operations while a web frontend (HTML/CSS/JS, or any framework like React/Vue/Svelte) renders the UI inside a native OS webview.
### Core Architecture Principles
- **Rust Core (`src-tauri/`)**: Handles system access, file operations, network calls, window management, and any heavy computation. All system interactions MUST go through Rust to maintain security boundaries.
- **Web Frontend (`src/`)**: Renders the UI using standard web technologies. Communicates with Rust exclusively through Tauri's IPC mechanisms (`invoke`, events, channels).
- **Security Boundary**: The frontend runs in a sandboxed webview. It cannot directly access the filesystem, network, or OS APIs — it must request these through Tauri commands and plugins, which are governed by the **Capabilities & Permissions** system.
- **Mobile Support (NEW in v2)**: Tauri 2.0 adds first-class iOS and Android support. This requires a specific project structure (`lib.rs` + `main.rs` pattern) and platform-specific capability configurations.
### What Changed from v1 to v2
Tauri 2.0 is a **complete architectural overhaul**, not an incremental update. The most significant changes are:
| Area | v1 | v2 |
|------|----|----|
| Security | `allowlist` in config | **Capabilities + Permissions** (ACL-based) |
| APIs | Built-in `tauri::api` module | **Everything is a plugin** |
| Window type | `Window` / `WindowBuilder` | **`WebviewWindow` / `WebviewWindowBuilder`** |
| Config structure | Nested `tauri >` key | Flattened: `app`, `bundle`, `build` |
| Event system | Per-window scoped | **`emit()` is global; `emit_to()` for targeting** |
| JS imports | `@tauri-apps/api/tauri` | **`@tauri-apps/api/core`** |
| Mobile support | Community plugins | **First-class iOS/Android support** |
| Project structure | `main.rs` only | **`lib.rs` + `main.rs` pattern required** |
**See also**: [Part 1: Core Architecture, Config, Commands & IPC](./01-core-architecture-config-commands-ipc.md)
---
## 2. Project Structure & Scaffolding
### Standard Tauri 2.0 File Hierarchy
```text
my-app/
├── src/ # Frontend UI source
│ ├── index.html # Main HTML entry
│ ├── main.js # Frontend JavaScript
│ └── styles.css # Styles
├── src-tauri/ # Rust backend environment
│ ├── capabilities/ # Security capability definitions
│ │ └── default.json # Maps windows → permissions
│ ├── icons/ # App icons for all platforms
│ ├── src/
│ │ ├── main.rs # Minimal desktop entry-point
│ │ ├── lib.rs # Core application setup & commands
│ │ └── <modules>.rs # Additional Rust modules
│ ├── build.rs # Tauri build script
│ ├── Cargo.toml # Rust dependencies
│ ├── Cargo.lock # Lockfile (COMMIT THIS)
│ └── tauri.conf.json # Main Tauri configuration
├── package.json # Node.js dependencies & scripts
└── package-lock.json
```
### Creating a New Project
```bash
# Using the interactive CLI scaffolder
npm create tauri-app@latest
# Or with specific template
npm create tauri-app@latest -- --template vanilla-ts
# Cargo alternative
cargo create-tauri-app
```
Supported templates: Vanilla, Vue, Svelte, React, Solid, Angular, Preact, Yew, Leptos, Sycamore.
### Adding Tauri to an Existing Frontend
```bash
npm install -D @tauri-apps/cli@latest
npx tauri init
```
The `init` command will prompt for: app name, window title, web assets location, dev server URL, and frontend build/dev commands.
### The `lib.rs` + `main.rs` Pattern (Required in v2)
Tauri 2.0 requires your core application logic to live in `lib.rs`, with a minimal `main.rs` that calls into it. This is **mandatory** for mobile support because mobile platforms require a shared library entry-point rather than a standard `main()` function.
**`src-tauri/src/main.rs`** — Desktop-only entry-point:
```rust
// Prevents additional console window on Windows in release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
my_app_lib::run()
}
```
**`src-tauri/src/lib.rs`** — Core application:
```rust
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
// plugins, commands, setup, etc.
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
**`src-tauri/Cargo.toml`** — Must declare the library:
```toml
[lib]
name = "my_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
```
The `_lib` suffix in the crate name prevents naming conflicts with the binary on Windows. The three crate types cover: static linking (mobile), dynamic linking (mobile), and Rust library usage (desktop/tests).
### Development Commands
```bash
# Run in development mode (hot-reloads UI + auto-recompiles Rust)
npm run tauri dev
# or: pnpm tauri dev, bun tauri dev, cargo tauri dev
# Build production artifacts
npm run tauri build
```
---
## 3. Configuration System (`tauri.conf.json`)
### Main Configuration File
The primary configuration file is `src-tauri/tauri.conf.json`. Tauri 2.0 supports three formats:
| Format | Feature Flag Required |
|--------|-----------------------|
| **JSON** (default) | None |
| **JSON5** | `config-json5` on both `tauri` and `tauri-build` |
| **TOML** | `config-toml` on both `tauri` and `tauri-build` |
### Complete v2 Configuration Structure
```json
{
"$schema": "https://tauri.app",
"productName": "my-app",
"version": "1.0.0",
"identifier": "com.mycompany.myapp",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:1420",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"label": "main",
"title": "My App",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false,
"center": true,
"dragDropEnabled": true,
"useHttpsScheme": false
}
],
"security": {
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'",
"assetProtocol": {
"scope": ["$APPDATA/**", "$RESOURCE/**"]
}
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"licenseFile": "LICENSE",
"copyright": "",
"category": "Utility"
}
}
```
### Key Configuration Changes from v1
| v1 Path | v2 Path | Notes |
|---------|---------|-------|
| `package.productName` | Top-level `productName` | Moved out of `package` |
| `package.version` | Top-level `version` | Moved out of `package` |
| `package` | *(removed)* | Fields redistributed |
| `build.distDir` | `build.frontendDist` | Renamed |
| `build.devPath` | `build.devUrl` | Renamed |
| `build.withGlobalTauri` | `app.withGlobalTauri` | Moved to `app` section |
| `tauri.*` | `app.*` | Top-level key renamed |
| `tauri.allowlist` | *(removed)* | Replaced by capabilities |
| `tauri.windows.fileDropEnabled` | `app.windows.dragDropEnabled` | Renamed |
| `tauri.bundle` | Top-level `bundle` | Promoted to top-level |
| `tauri.updater` | `plugins.updater` | Moved to plugins |
| `tauri.systemTray` | `app.trayIcon` | Renamed |
| `tauri.cli` | `plugins.cli` | Moved to plugins |
| `bundle.identifier` | Top-level `identifier` | Promoted to top-level |
### Platform-Specific Configuration
Create platform override files that **merge** with the main config using JSON Merge Patch (RFC 7396). Arrays are replaced entirely (not element-by-element merged).
| Platform | File Pattern |
|----------|-------------|
| Linux | `tauri.linux.conf.json` or `Tauri.linux.toml` |
| Windows | `tauri.windows.conf.json` or `Tauri.windows.toml` |
| macOS | `tauri.macos.conf.json` or `Tauri.macos.toml` |
| Android | `tauri.android.conf.json` or `Tauri.android.toml` |
| iOS | `tauri.ios.conf.json` or `Tauri.ios.toml` |
### Cargo.toml Dependencies
```toml
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```
**Important**: Keep `tauri`, `tauri-build`, and `@tauri-apps/cli` on the same minor version. Always commit `Cargo.lock` for reproducible builds.
### package.json Scripts
```json
{
"scripts": {
"tauri": "tauri",
"dev": "tauri dev",
"build": "tauri build"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
}
}
```
The `"tauri"` script is only required when using `npm` (not needed for yarn/pnpm/bun).
---
## 4. Capabilities & Permissions (Security Model)
Tauri 2.0 introduces a strict **Access Control List (ACL)** security model that replaces the v1 `allowlist`. This is the most important new concept to understand.
### Core Concepts
- **Capabilities** define **which permissions** are granted to **which windows or webviews**.
- **Permissions** describe explicit privileges for specific commands (e.g., `fs:allow-read`, `http:allow-request`).
- **Scopes** further restrict what a permission can access (e.g., only certain file paths).
- Multiple capabilities can apply to the same window — permissions **merge**.
- Security boundaries are based on **window labels** (not titles).
### Capability File Location
```
src-tauri/capabilities/<identifier>.json (or .toml)
```
All files in this directory are **automatically loaded** by default.
### Basic Capability File
```json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-window-capability",
"description": "Permissions for the primary application window",
"platforms": ["linux", "macOS", "windows"],
"windows": ["main"],
"permissions": [
"core:default"
]
}
```
**Field reference:**
- `$schema` — Points to generated schema for IDE autocompletion. Use `desktop-schema.json` for desktop, `mobile-schema.json` for mobile.
- `identifier` — Unique capability name (ASCII lowercase, max 116 chars).
- `description` — Human-readable purpose.
- `windows` — Array of window labels this capability applies to. Use `["*"]` for all windows.
- `permissions` — Array of permission identifiers.
- `platforms` — Optional. Restricts to specific platforms. Defaults to all platforms if omitted.
### Permission Identifier Naming Convention
| Pattern | Meaning | Example |
|---------|---------|---------|
| `<plugin>:default` | Default permission set for a plugin | `fs:default` |
| `<plugin>:allow-<command>` | Allow a specific command | `fs:allow-read` |
| `<plugin>:deny-<command>` | Deny a specific command | `fs:deny-write` |
| `core:<module>:<permission>` | Core Tauri module permission | `core:event:allow-emit` |
### Common Permission Identifiers
**Core permissions:**
- `core:default` — Basic runtime permissions
- `core:path:default` — Path resolution
- `core:event:default` — Event system (includes listen/emit)
- `core:event:allow-listen` — Allow listening to events
- `core:event:allow-emit` — Allow emitting events
- `core:window:default` — Window management
- `core:window:allow-set-title` — Allow changing window title
**Plugin permissions:**
- `fs:allow-read`, `fs:allow-write`, `fs:allow-exists`, `fs:allow-mkdir`, `fs:allow-remove`
- `http:allow-request` — HTTP client requests
- `dialog:allow-open`, `dialog:allow-save`, `dialog:allow-message`
- `shell:allow-open`, `shell:allow-execute`
- `notification:allow-notify`, `notification:allow-is-permission-granted`
- `clipboard-manager:allow-read-text`, `clipboard-manager:allow-write-text`
- `websocket:allow-connect`, `websocket:allow-send`
- `store:default` — Persistent key-value store
### Fine-Grained Scopes
Scopes restrict what a permission can actually access:
```json
{
"identifier": "secure-fs-capability",
"windows": ["main"],
"permissions": [
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$APPDATA/logs/*" }, { "path": "$HOME/Documents/*.txt" }]
},
{
"identifier": "fs:allow-write",
"allow": [{ "path": "$APPDATA/logs/*.log" }]
},
{
"identifier": "fs:deny-write",
"deny": [{ "path": "$APPDATA/config/*" }]
}
]
}
```
Available scope variables: `$APPDATA`, `$HOME`, `$APPCONFIG`, `$APPCACHE`, `$APPLOG`, `$RESOURCE`, `$EXE`.
### Restricting Custom Commands
By default, all `#[tauri::command]` functions are accessible from all windows. To restrict a custom command to specific windows only, use `build.rs`:
```rust
fn main() {
tauri_build::try_build(
tauri_build::Attributes::new()
.app_manifest(
tauri_build::AppManifest::new()
.commands(&["my_restricted_command", "admin_only_command"])
),
)
.unwrap();
}
```
Then grant access in the capability:
```json
{
"identifier": "admin-capability",
"windows": ["admin-panel"],
"permissions": [
{ "identifier": "allow-my-restricted-command" },
{ "identifier": "allow-admin-only-command" }
]
}
```
### Referencing Capabilities Explicitly
By default, all capability files in `src-tauri/capabilities/` are auto-discovered. To control which ones are used explicitly:
```json
{
"app": {
"security": {
"capabilities": ["my-capability", "admin-capability"]
}
}
}
```
### Inline Capabilities
You can define capabilities directly in `tauri.conf.json` instead of separate files:
```json
{
"app": {
"security": {
"capabilities": [
{
"identifier": "inline-cap",
"windows": ["*"],
"permissions": ["core:default"]
}
]
}
}
}
```
---
## 5. Rust Backend: `lib.rs` & Commands
### Application Builder Pattern
All Tauri 2.0 applications follow the `tauri::Builder` pattern:
```rust
use tauri::Manager;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) // Register plugins
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.setup(|app| { // Setup hook (runs once at startup)
// Initialize resources, spawn servers, etc.
Ok(())
})
.manage(AppState { /* ... */ }) // Inject managed state
.invoke_handler(tauri::generate_handler![ // Register commands
my_command,
another_command,
module::command_in_module
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
**Critical rules:**
- `invoke_handler` can only be called **once**. All commands must be in a single `generate_handler![]` call.
- Commands defined in `lib.rs` **cannot** be marked `pub`.
- Commands defined in separate modules **must** be marked `pub`.
- The `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is required for the `run()` function.
### Defining Commands
Commands are the primary IPC mechanism. They are Rust functions annotated with `#[tauri::command]` and callable from the frontend via `invoke()`.
#### Basic Command
```rust
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}! Welcome from Rust.", name)
}
```
#### Command with Error Handling
```rust
#[tauri::command]
fn login(user: String, password: String) -> Result<String, String> {
if user == "admin" && password == "secret" {
Ok("authenticated".to_string())
} else {
Err("invalid credentials".to_string())
}
}
```
#### Async Command
```rust
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url)
.await
.map_err(|e| e.to_string())?;
response.text().await.map_err(|e| e.to_string())
}
```
**IMPORTANT limitation**: Async commands cannot accept borrowed references (`&str`, `&Path`) directly. Convert to owned types (`String`, `PathBuf`) or wrap the return in `Result`:
```rust
// Option A: Use owned types
#[tauri::command]
async fn process(value: String) -> String { /* ... */ }
// Option B: Wrap return in Result
#[tauri::command]
async fn process(value: &str) -> Result<String, ()> { /* ... */ }
```
#### Custom Error Types (Recommended)
```rust
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
}
// Implement Serialize so the error can cross the IPC boundary
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 read_config() -> Result<String, AppError> {
let content = std::fs::read_to_string("config.toml")?;
Ok(content)
}
```
#### Structured Error with Kind/Tag
```rust
#[derive(Debug, Error)]
enum AppError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("{0}")]
Custom(String),
}
#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
Io(String),
Custom(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let kind = match self {
Self::Io(e) => ErrorKind::Io(e.to_string()),
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
};
kind.serialize(serializer)
}
}
```
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
#### Accessing Special Objects in Commands
**AppHandle** — Access to the application instance:
```rust
#[tauri::command]
fn do_something(app_handle: tauri::AppHandle) {
let app_dir = app_handle.path().app_dir();
// Spawn windows, emit events, access state...
}
```
**WebviewWindow** — The window that invoked the command:
```rust
#[tauri::command]
fn get_window_info(webview_window: tauri::WebviewWindow) -> String {
webview_window.label().to_string()
}
```
**Managed State** — Shared application state:
```rust
use tauri::State;
use std::sync::Mutex;
struct DbConnection(Mutex<String>);
#[tauri::command]
fn query_database(state: State<'_, DbConnection>) -> String {
let conn = state.0.lock().unwrap();
conn.clone()
}
```
**Raw Request** — Access headers and raw body:
```rust
#[tauri::command]
fn upload(request: tauri::ipc::Request) -> Result<(), String> {
let tauri::ipc::InvokeBody::Raw(data) = request.body() else {
return Err("Expected raw body".to_string());
};
let auth = request.headers().get("Authorization")
.ok_or("Missing auth header")?;
Ok(())
}
```
#### Commands in Separate Modules
```rust
// src-tauri/src/commands.rs
#[tauri::command]
pub fn create_user(name: String) -> String {
format!("Created user: {}", name)
}
#[tauri::command]
pub fn delete_user(id: u32) -> bool {
// ...
true
}
```
```rust
// src-tauri/src/lib.rs
mod commands;
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
commands::create_user,
commands::delete_user
])
// ...
}
```
Note: The `commands::` prefix is Rust path resolution only — the frontend still calls `invoke('create_user', ...)` without any prefix.
### Returning Large Data (ArrayBuffers)
For large binary data like file contents, use `tauri::ipc::Response`:
```rust
use tauri::ipc::Response;
#[tauri::command]
fn read_file() -> Response {
let data = std::fs::read("/path/to/file").unwrap();
Response::new(data)
}
```
### Streaming Data (Channels)
For streaming large amounts of data to the frontend:
```rust
use tauri::ipc::Channel;
use tokio::io::AsyncReadExt;
#[tauri::command]
async fn stream_file(path: std::path::PathBuf, on_chunk: Channel<Vec<u8>>) {
let mut file = tokio::fs::File::open(path).await.unwrap();
let mut buf = vec![0u8; 4096];
loop {
let n = file.read(&mut buf).await.unwrap();
if n == 0 { break; }
on_chunk.send(&buf[..n]).unwrap();
}
}
```
---
## 6. Frontend Integration Patterns
### Using `@tauri-apps/api` npm Package (Recommended)
```bash
npm install @tauri-apps/api
```
```javascript
// Imports in v2 — NOTE the path changes from v1
import { invoke } from '@tauri-apps/api/core';
import { listen, emit, emitTo, once } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow, WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { Channel } from '@tauri-apps/api/core';
```
### Using Global `window.__TAURI__` (No npm package needed)
Requires `app.withGlobalTauri: true` in `tauri.conf.json`:
```javascript
// No import needed — accessed via the global object
const { invoke } = window.__TAURI__.core;
const { listen, emit } = window.__TAURI__.event;
```
This pattern is ideal for vanilla JS projects that don't use a bundler, avoiding the need for any npm packages beyond `@tauri-apps/cli`.
### Import Path Changes from v1
| v1 Import | v2 Import |
|-----------|-----------|
| `@tauri-apps/api/tauri` | `@tauri-apps/api/core` |
| `@tauri-apps/api/window` | `@tauri-apps/api/webviewWindow` |
| `@tauri-apps/api/cli` | `@tauri-apps/plugin-cli` |
| `@tauri-apps/api/clipboard` | `@tauri-apps/plugin-clipboard-manager` |
| `@tauri-apps/api/dialog` | `@tauri-apps/plugin-dialog` |
| `@tauri-apps/api/fs` | `@tauri-apps/plugin-fs` |
| `@tauri-apps/api/global-shortcut` | `@tauri-apps/plugin-global-shortcut` |
| `@tauri-apps/api/http` | `@tauri-apps/plugin-http` |
| `@tauri-apps/api/notification` | `@tauri-apps/plugin-notification` |
| `@tauri-apps/api/shell` | `@tauri-apps/plugin-shell` |
| `@tauri-apps/api/updater` | `@tauri-apps/plugin-updater` |
| `@tauri-apps/api/os` | `@tauri-apps/plugin-os` |
| `@tauri-apps/api/process` | `@tauri-apps/plugin-process` |
**Rule of thumb in v2**: If it's not `core`, `event`, or `webviewWindow`, it's a plugin.
### Plugin JS Package Naming
All v2 plugin JS packages follow: `@tauri-apps/plugin-<name>`
```bash
npm install @tauri-apps/plugin-store
npm install @tauri-apps/plugin-fs
npm install @tauri-apps/plugin-http
```
---
## 7. IPC: Commands, Events & Channels
### Commands (Frontend → Rust)
The primary request-response mechanism:
```javascript
// Frontend (v2 import)
import { invoke } from '@tauri-apps/api/core';
// Simple invocation
const result = await invoke('greet', { name: 'World' });
console.log(result); // "Hello, World! Welcome from Rust."
// With error handling
try {
const token = await invoke('login', { user: 'admin', password: 'secret' });
console.log('Authenticated:', token);
} catch (error) {
console.error('Login failed:', error);
}
```
**Argument naming convention**: Arguments are passed as a JSON object with **camelCase** keys by default. Use `#[tauri::command(rename_all = "snake_case")]` to accept snake_case from the frontend.
### Events (Bidirectional, Multi-Consumer)
Events are fire-and-forget messages. Use them for notifications, streaming status updates, or any data that multiple components might need.
#### Emitting Events from Rust
```rust
use tauri::{AppHandle, Emitter};
// Global event (all listeners receive it)
app_handle.emit("download-progress", 42)?;
// Target a specific window
app_handle.emit_to("settings-panel", "config-changed", new_config)?;
// Filter to specific windows
use tauri::EventTarget;
app_handle.emit_filter("notification", payload, |target| {
matches!(target, EventTarget::WebviewWindow { label } if label == "main")
})?;
```
#### Listening for Events on the Frontend
```javascript
import { listen, once, emit, emitTo } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
// Global listen (receives ALL events with this name)
const unlisten = await listen('download-progress', (event) => {
console.log(`Progress: ${event.payload}%`);
});
// Targeted listen (only events emitted to THIS window)
const appWebview = getCurrentWebviewWindow();
const unlisten2 = await appWebview.listen('config-changed', (event) => {
console.log('New config:', event.payload);
});
// Listen once, then auto-cleanup
await once('initialization-complete', (event) => {
console.log('App ready!', event.payload);
});
// Emit from frontend to Rust
await emit('user-action', { type: 'click', target: 'button' });
// Emit to a specific window from frontend
const target = new WebviewWindow('settings-panel');
await target.emit('settings-request', { key: 'theme' });
```
**Always call `unlisten()`** when the component unmounts or the listener is no longer needed to prevent memory leaks.
#### Listening for Events on the Rust Side
```rust
use tauri::{Listener, Manager};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
// Listen globally
app.listen("user-action", |event| {
println!("Received: {}", event.payload());
});
// Listen on a specific window
let main = app.get_webview_window("main").unwrap();
main.listen("config-changed", |event| {
println!("Config updated: {}", event.data);
});
// Listen once
app.once("ready", |event| {
println!("App is ready!");
});
Ok(())
})
// ...
}
```
### Channels (High-Throughput Streaming)
Channels are the recommended mechanism for streaming large amounts of ordered data from Rust to the frontend. They are faster and more memory-efficient than events for high-throughput scenarios.
```rust
use tauri::ipc::Channel;
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(tag = "event", content = "data")]
enum StreamEvent {
Started { total: usize },
Progress { current: usize, chunk: Vec<u8> },
Finished { total: usize },
}
#[tauri::command]
fn download_file(url: String, on_event: Channel<StreamEvent>) {
on_event.send(StreamEvent::Started { total: 1000 }).unwrap();
// ... streaming logic ...
on_event.send(StreamEvent::Finished { total: 1000 }).unwrap();
}
```
```javascript
import { invoke, Channel } from '@tauri-apps/api/core';
const onEvent = new Channel();
onEvent.onmessage = (event) => {
switch (event.event) {
case 'started': console.log(`Starting: ${event.data.total} bytes`); break;
case 'progress': /* handle chunk */ break;
case 'finished': console.log('Done!'); break;
}
};
await invoke('download_file', { url: 'https://...', onEvent });
```
### Evaluating JavaScript from Rust
```rust
use tauri::Manager;
// In a command or setup hook
let webview = app.get_webview_window("main").unwrap();
webview.eval("document.getElementById('status').textContent = 'Loaded!'")?;
```
For complex data passing, use the `serialize-to-javascript` crate.
---
## 8. Window Management
### Creating Windows Dynamically
@ -1814,3 +904,7 @@ app.get_webview_window("label")?.set_focus()?;
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
- Plugin Index: https://v2.tauri.app/plugin/
- Rust Docs: https://docs.rs/tauri/latest/tauri/
---
**Back to**: [Part 1: Core Architecture, Config, Commands & IPC](./01-core-architecture-config-commands-ipc.md)