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 ```text
tauri2-vin-ws-temp/ tauri2-vin-ws-temp/
├── tauri2-guide/ # AUTHORITATIVE GUIDES — start here ├── 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 │ │ ── Definitive Developer Guide (2 parts) ──────────────────────
│ └── error-resolution-guide.md # Every known error category with exact messages, root causes, and solutions │ ├── 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 ├── tauri2-docs/ # SUPPLEMENTARY REFERENCES
│ ├── cheatsheet.md # Quick-reference cheat sheet (commands, config, IPC, WebSocket patterns) │ ├── cheatsheet.md # Quick-reference cheat sheet
│ ├── ws_server_client_inject_guide.md # Deep-dive: native WebSocket server, frontend clients, script injection │ ├── ws_server_client_inject_guide.md # WebSocket server, client, injection deep-dive
│ └── tauri2-links.md # Curated links to official Tauri 2.0 documentation pages │ └── 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 ## How to Use This Repository
### For LLMs / AI Agents ### 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:** **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. - 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. - 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 ### 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 ## Document Index — What to Read for Each Topic
| Topic | File | Key Sections | ### Definitive Developer Guide
|-------|------|-------------|
| **Architecture overview, project structure, scaffolding** | `tauri2-guide/definitive-guide.md` | Sections 12 | | Topic | File | Sections |
| **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 | | Architecture overview, project structure, scaffolding | `01-core-architecture-config-commands-ipc.md` | 12 |
| **Rust backend: `lib.rs`, commands, `Builder` pattern** | `tauri2-guide/definitive-guide.md` | Section 5 | | Configuration (`tauri.conf.json` v2 schema) | `01-core-architecture-config-commands-ipc.md` | 3 |
| **Frontend integration (JS imports, `invoke`, plugins)** | `tauri2-guide/definitive-guide.md` | Section 6 | | Capabilities & permissions (ACL security model) | `01-core-architecture-config-commands-ipc.md` | 4 |
| **IPC: commands, events, channels, streaming** | `tauri2-guide/definitive-guide.md` | Section 7 | | Rust backend: `lib.rs`, commands, Builder pattern | `01-core-architecture-config-commands-ipc.md` | 5 |
| **Window management (multi-window, external URLs)** | `tauri2-guide/definitive-guide.md` | Section 8 | | Frontend integration (JS imports, invoke, plugins) | `01-core-architecture-config-commands-ipc.md` | 6 |
| **Plugin ecosystem (all 33+ official plugins)** | `tauri2-guide/definitive-guide.md` | Section 9 | | IPC: commands, events, channels, streaming | `01-core-architecture-config-commands-ipc.md` | 7 |
| **Native WebSocket server (Tokio + broadcast)** | `tauri2-guide/definitive-guide.md` Section 10 / `tauri2-docs/ws_server_client_inject_guide.md` Part 1 | | Window management (multi-window, external URLs) | `02-windows-plugins-websocket-state-migration.md` | 8 |
| **Script injection into webview windows** | `tauri2-guide/definitive-guide.md` Section 11 / `tauri2-docs/ws_server_client_inject_guide.md` Part 3 | | Plugin ecosystem (all 33+ official plugins) | `02-windows-plugins-websocket-state-migration.md` | 9 |
| **State management (Mutex, managed state)** | `tauri2-guide/definitive-guide.md` | Section 12 | | Native WebSocket server (Tokio + broadcast) | `02-windows-plugins-websocket-state-migration.md` / `tauri2-docs/ws_server_client_inject_guide.md` | 10 |
| **v1 to v2 migration (full API diff)** | `tauri2-guide/definitive-guide.md` Section 13 / `tauri2-guide/agent-prompts.md` Section 14 | | Script injection into webview windows | `02-windows-plugins-websocket-state-migration.md` / `tauri2-docs/ws_server_client_inject_guide.md` | 11 |
| **Anti-patterns to avoid** | `tauri2-guide/definitive-guide.md` | Section 14 | | State management (Mutex, managed state) | `02-windows-plugins-websocket-state-migration.md` | 12 |
| **Quick reference cheat sheet** | `tauri2-docs/cheatsheet.md` | All sections | | v1 to v2 migration (full API diff) | `02-windows-plugins-websocket-state-migration.md` | 13 |
| **WebSocket server + client + injection deep-dive** | `tauri2-docs/ws_server_client_inject_guide.md` | Parts 13 | | Anti-patterns to avoid | `02-windows-plugins-websocket-state-migration.md` | 14 |
| **Links to official docs (migration, security, config)** | `tauri2-docs/tauri2-links.md` | All entries | | Quick reference cheat sheet | `02-windows-plugins-websocket-state-migration.md` | 15 |
| **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 Prompts
| **Agent verification checklist** | `tauri2-guide/agent-prompts.md` | Section 13 |
| **Rust compilation errors (Cargo build failures)** | `tauri2-guide/error-resolution-guide.md` | Category A | | Topic | File | Section |
| **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 | | Universal system prompt (REQUIRED prefix) | `01-system-prompt-and-common-prompts.md` | 1 |
| **Config errors (`tauri.conf.json` validation)** | `tauri2-guide/error-resolution-guide.md` | Category D | | Scaffold a new Tauri 2.0 project | `01-system-prompt-and-common-prompts.md` | 2 |
| **Permission/capability errors ("not allowed")** | `tauri2-guide/error-resolution-guide.md` | Category E | | Add a Tauri plugin | `01-system-prompt-and-common-prompts.md` | 3 |
| **Plugin integration errors** | `tauri2-guide/error-resolution-guide.md` | Category F | | Create a custom command (IPC) | `01-system-prompt-and-common-prompts.md` | 4 |
| **IPC / command runtime errors** | `tauri2-guide/error-resolution-guide.md` | Category G | | Add state management | `01-system-prompt-and-common-prompts.md` | 5 |
| **Webview & window runtime errors** | `tauri2-guide/error-resolution-guide.md` | Category H | | Implement event-based communication | `01-system-prompt-and-common-prompts.md` | 6 |
| **Event system errors** | `tauri2-guide/error-resolution-guide.md` | Category I | | Create multi-window app | `01-system-prompt-and-common-prompts.md` | 7 |
| **Content Security Policy (CSP) errors** | `tauri2-guide/error-resolution-guide.md` | Category J | | Build a WebSocket server in Rust | `02-advanced-prompts-checklist-migration.md` | 8 |
| **Frontend integration errors (Vite, Next.js, SvelteKit)** | `tauri2-guide/error-resolution-guide.md` | Category K | | Implement script injection | `02-advanced-prompts-checklist-migration.md` | 9 |
| **WebSocket errors** | `tauri2-guide/error-resolution-guide.md` | Category L | | Set up capabilities & permissions | `02-advanced-prompts-checklist-migration.md` | 10 |
| **Path & asset protocol errors** | `tauri2-guide/error-resolution-guide.md` | Category M | | Migrate a v1 app to v2 | `02-advanced-prompts-checklist-migration.md` | 11 |
| **Shell & sidecar errors** | `tauri2-guide/error-resolution-guide.md` | Category N | | Full app build (end-to-end) | `02-advanced-prompts-checklist-migration.md` | 12 |
| **Mobile build errors (iOS & Android)** | `tauri2-guide/error-resolution-guide.md` | Category O | | Verification checklist for agents | `02-advanced-prompts-checklist-migration.md` | 13 |
| **Platform-specific build errors** | `tauri2-guide/error-resolution-guide.md` | Category P | | v1 vs v2 API map | `02-advanced-prompts-checklist-migration.md` | 14 |
| **Panics & runtime crashes** | `tauri2-guide/error-resolution-guide.md` | Category Q | | Debug a Tauri 2.0 build/compile error | `02-advanced-prompts-checklist-migration.md` | 15 |
| **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 | ### 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) | | Event emit | `window.emit()` | `app.emit()` (global) / `app.emit_to()` (targeted) |
| Rust APIs | `tauri::api::*` modules | Everything moved to plugins | | 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 ### 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: 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` - **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 `definitive-guide.md` - **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 `definitive-guide.md` Section 3 - **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 `definitive-guide.md` Section 9 - **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 - **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` - **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 `definitive-guide.md` Section 4 and `error-resolution-guide.md` Category E - **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 > **Version**: 2.0 | **Last Updated**: 2026-05-31
> **Official Docs**: https://v2.tauri.app > **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) 6. [Category F: Plugin Integration Errors](#category-f-plugin-integration-errors)
7. [Category G: IPC / Command Runtime Errors](#category-g-ipc--command-runtime-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) 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) **See also**: [Part 2: Runtime, Platform & Mobile Errors](./02-runtime-platform-mobile-errors.md)
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)
--- ---
@ -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 **Continue to**: [Part 2: Runtime, Platform & Mobile Errors](./02-runtime-platform-mobile-errors.md)
**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 |

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. > **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. > **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 ## 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) 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) 9. [Prompt: Implement Script Injection](#9-prompt-implement-script-injection)
10. [Prompt: Set Up Capabilities & Permissions](#10-prompt-set-up-capabilities--permissions) 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) 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) 15. [Prompt: Debug a Tauri 2.0 Build/Compile Error](#15-prompt-debug-a-tauri-20-buildcompile-error)
--- **See also**: [Part 1: System Prompt & Common Task Prompts](./01-system-prompt-and-common-prompts.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()`).
```
--- ---
@ -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. 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 > **Version**: 2.0 | **Last Updated**: 2026-05-31
> **Official Docs**: https://v2.tauri.app > **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 ## 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) 8. [Window Management](#8-window-management)
9. [Plugin Ecosystem](#9-plugin-ecosystem) 9. [Plugin Ecosystem](#9-plugin-ecosystem)
10. [Native WebSocket Server (Rust-side)](#10-native-websocket-server-rust-side) 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) 14. [Common Anti-Patterns to Avoid](#14-common-anti-patterns-to-avoid)
15. [Quick Reference Cheat Sheet](#15-quick-reference-cheat-sheet) 15. [Quick Reference Cheat Sheet](#15-quick-reference-cheat-sheet)
--- **See also**: [Part 1: Core Architecture, Config, Commands & IPC](./01-core-architecture-config-commands-ipc.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.
---
## 8. Window Management ## 8. Window Management
### Creating Windows Dynamically ### Creating Windows Dynamically
@ -1814,3 +904,7 @@ app.get_webview_window("label")?.set_focus()?;
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/ - Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
- Plugin Index: https://v2.tauri.app/plugin/ - Plugin Index: https://v2.tauri.app/plugin/
- Rust Docs: https://docs.rs/tauri/latest/tauri/ - Rust Docs: https://docs.rs/tauri/latest/tauri/
---
**Back to**: [Part 1: Core Architecture, Config, Commands & IPC](./01-core-architecture-config-commands-ipc.md)