tauri-core-architecture
Quick Reference
Architecture Layers (Tauri 2.x)
| Layer |
Technology |
Location |
Role |
| Rust Backend |
Rust + Tokio |
src-tauri/src/ |
Application logic, system access, state management |
| Webview Layer |
Platform-native webview |
Embedded |
Renders HTML/CSS/JS frontend |
| IPC Bridge |
JSON-serialized messages |
Internal |
Connects frontend to backend via invoke() and events |
| Plugin System |
Rust crate + npm package |
Cargo.toml + package.json |
Extends capabilities (fs, dialogs, HTTP, etc.) |
| Permission System |
Capabilities + permissions |
src-tauri/capabilities/ |
Fine-grained access control (replaces v1 allowlist) |
Platform Webview Engines
| Platform |
Webview Engine |
| Windows |
WebView2 (Chromium-based) |
| macOS |
WKWebView |
| Linux |
WebKitGTK |
| iOS |
WKWebView |
| Android |
Android WebView |
Key Dependencies
| Package |
Type |
Purpose |
@tauri-apps/cli |
devDependency (npm) |
CLI tooling for dev/build |
@tauri-apps/api |
dependency (npm) |
Frontend API for invoke, events, paths |
tauri |
dependency (Cargo) |
Core Rust framework |
tauri-build |
build-dependency (Cargo) |
Build script helpers |
serde + serde_json |
dependency (Cargo) |
Serialization for IPC |
Critical Warnings
NEVER block the main thread with synchronous I/O in commands -- ALWAYS use async for file, network, or long-running operations. Blocking freezes the entire UI.
NEVER call .invoke_handler() more than once on Builder -- only the LAST call takes effect. Put ALL commands in a single generate_handler![] macro.
NEVER mark command functions as pub when defined directly in lib.rs -- the glue code generation prevents it. Move commands to a separate module if they need to be public.
NEVER wrap managed state in Arc -- Tauri wraps state in Arc internally. Using app.manage(Arc::new(data)) adds a redundant layer.
NEVER use &str in async command parameters -- borrowed references do not work with async command spawning. ALWAYS use String instead.
NEVER use State<'_, T> when you registered Mutex<T> -- this causes a runtime panic, not a compile error. ALWAYS match the exact registered type: State<'_, Mutex<T>>.
Process Model
Tauri 2 uses a two-process architecture:
Main Process (Rust)
- Runs the Rust backend on the main thread + Tokio async runtime
- Has full system access (filesystem, network, OS APIs)
- Manages application lifecycle, windows, menus, tray icons
- Exposes
#[tauri::command] functions as IPC endpoints
- Manages state via
app.manage() with Arc-wrapped storage
Webview Process (Frontend)
- Runs in a platform-native webview (NOT Electron/Chromium bundle)
- Renders HTML/CSS/JS like a browser tab
- Communicates with Rust ONLY through the IPC bridge
- Has NO direct system access -- all system operations go through
invoke()
- Event listeners enable bidirectional pub/sub messaging
IPC Bridge
The bridge between processes uses JSON-serialized messages:
Frontend Rust Backend
| |
|--- invoke('cmd', {args}) -------->| (JSON request)
| |--- execute command
|<-- Result<T, E> (JSON) ----------| (JSON response)
| |
|--- emit('event', payload) ------->| (event pub/sub)
|<-- emit('event', payload) --------| (event pub/sub)
| |
|<-- Channel.send(data) -----------| (streaming)
invoke() sends a JSON object with camelCase keys; Rust receives snake_case parameters
- Events use
Serialize + Clone payloads
- Channels enable streaming multiple messages from Rust to JS
- Binary data can bypass JSON via
tauri::ipc::Response
Project Structure
my-tauri-app/
├── src/ # Frontend source (React/Vue/Svelte/vanilla)
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── lib.rs # Main app logic (mobile entry point)
│ │ └── main.rs # Desktop entry point (calls lib.rs)
│ ├── capabilities/ # Permission capability files (JSON/TOML)
│ │ └── default.json # Default capabilities
│ ├── permissions/ # Custom command permissions (TOML only)
│ ├── icons/ # Application icons (all required sizes)
│ ├── gen/ # Generated files (DO NOT edit manually)
│ │ └── schemas/
│ │ ├── desktop-schema.json
│ │ ├── mobile-schema.json
│ │ └── remote-schema.json
│ ├── Cargo.toml # Rust dependencies
│ ├── Cargo.lock # Deterministic build lock
│ ├── tauri.conf.json # Main configuration
│ ├── build.rs # Cargo build script
│ └── .taurignore # Exclude files from dev watcher
├── package.json # Frontend dependencies
├── tsconfig.json # TypeScript config (if applicable)
└── vite.config.ts # Bundler config (if Vite)
Source Control Rules
- ALWAYS commit:
src-tauri/Cargo.lock (deterministic builds)
- ALWAYS ignore:
src-tauri/target/ (build artifacts)
- NEVER edit:
src-tauri/gen/ (auto-generated schemas)
Entry Point Pattern (Tauri 2.x)
Desktop and mobile share logic through lib.rs:
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![/* commands */])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// src-tauri/src/main.rs (desktop only)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}
ALWAYS use the lib.rs + main.rs split pattern for Tauri 2 -- this is required for mobile support and is the standard project layout.
Type Hierarchy
Core Types
| Type |
Description |
Traits |
App |
Full application instance (only in setup()) |
Manager, Emitter, Listener |
AppHandle |
Lightweight clone-safe handle to the app |
Manager, Emitter, Listener, Clone + Send + Sync |
WebviewWindow |
Combined window + webview (most common) |
Manager, Emitter, Listener |
Window |
OS-level window (without webview) |
Manager, Emitter, Listener |
Webview |
Webview inside a window |
Manager, Emitter, Listener |
State<'_, T> |
Managed state accessor in commands |
-- |
Builder |
Application configuration builder |
-- |
The Manager Trait
The Manager trait is the unifying interface. It is implemented by App, AppHandle, Webview, WebviewWindow, and Window. Key methods:
| Method |
Return Type |
Purpose |
app_handle() |
&AppHandle<R> |
Get the AppHandle |
config() |
&Config |
Access tauri.conf.json |
state::<T>() |
State<'_, T> |
Access managed state |
try_state::<T>() |
Option<State<'_, T>> |
Access state without panic |
manage(state) |
bool |
Register new state |
get_webview_window(label) |
Option<WebviewWindow<R>> |
Get window by label |
webview_windows() |
HashMap<String, WebviewWindow<R>> |
Get all windows |
get_window(label) |
Option<Window<R>> |
Get OS window by label |
path() |
&PathResolver<R> |
Access path resolver |
package_info() |
&PackageInfo |
Get package metadata |
The Emitter Trait
| Method |
Purpose |
emit(event, payload) |
Broadcast to ALL targets |
emit_to(target, event, payload) |
Emit to a specific target |
emit_filter(event, payload, filter_fn) |
Emit to targets matching a filter |
emit_str(event, json_string) |
Emit with pre-serialized JSON |
The Listener Trait
| Method |
Purpose |
listen(event, handler) |
Listen for events (returns EventId) |
once(event, handler) |
Listen once, auto-remove after first event |
unlisten(id) |
Remove a listener |
listen_any(event, handler) |
Listen from any source |
Runtime Generic
Most types are generic over R: Runtime. With the default wry feature, R resolves to Wry. Use the generic form in plugins or for test mocking:
#[tauri::command]
async fn my_command<R: Runtime>(
app: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<(), String> {
Ok(())
}
Builder Pattern
The Builder is the single entry point for configuring a Tauri application:
tauri::Builder::default()
.manage(MyState::default()) // state
.plugin(tauri_plugin_shell::init()) // plugins
.invoke_handler(tauri::generate_handler![cmd1, cmd2]) // commands
.setup(|app| { // init hook
let handle = app.handle().clone();
// app is &mut App -- full access, runs once
Ok(())
})
.on_window_event(|window, event| { /* ... */ }) // window events
.on_menu_event(|app, event| { /* ... */ }) // menu events
.menu(|app| { /* build menu */ }) // app menu
.run(tauri::generate_context!())
.expect("error running app");
Builder Method Reference
| Method |
Purpose |
manage(T) |
Register managed state |
plugin(P) |
Register a plugin |
invoke_handler(F) |
Register ALL command handlers |
setup(F) |
App initialization hook |
menu(F) |
Set the application menu |
on_menu_event(F) |
Menu event handler |
on_window_event(F) |
Window event handler |
on_webview_event(F) |
Webview event handler |
on_page_load(F) |
Page load handler |
on_tray_icon_event(F) |
Tray icon event handler |
build(Context) |
Build without running (returns App) |
run(Context) |
Build and run the app |
any_thread(self) |
Allow running on any thread |
Frontend API Overview
| API Area |
Key Functions |
Import Path |
| Commands |
invoke<T>(), Channel<T> |
@tauri-apps/api/core |
| Events |
listen(), emit(), emitTo(), once() |
@tauri-apps/api/event |
| Windows |
getCurrentWindow(), getAllWindows() |
@tauri-apps/api/window |
| Webviews |
getCurrentWebview() |
@tauri-apps/api/webview |
| Paths |
appDataDir(), join(), BaseDirectory |
@tauri-apps/api/path |
| Utilities |
convertFileSrc(), isTauri() |
@tauri-apps/api/core |
| Testing |
mockIPC(), mockWindows(), clearMocks() |
@tauri-apps/api/mocks |
Reference Links
- references/methods.md -- API signatures for App, AppHandle, Manager, Runtime, Webview, WebviewWindow
- references/examples.md -- Working code examples verified against Tauri 2.x documentation
- references/anti-patterns.md -- What NOT to do, with WHY explanations
Official Sources
1---2name: tauri-core-architecture3description: Use when creating new Tauri 2 apps, understanding project structure, or reasoning about the component model. Prevents mixing Tauri 1.x architecture assumptions with the v2 multi-webview and capability-based model. Covers Rust backend structure, webview layer, IPC bridge model, process model, project layout, and type hierarchy. Keywords: tauri architecture, project structure, IPC bridge, webview layer, process model, Rust backend, how Tauri works, project layout, frontend backend split, getting started, what is IPC..4license: MIT5---67# tauri-core-architecture89## Quick Reference1011### Architecture Layers (Tauri 2.x)1213| Layer | Technology | Location | Role |14|-------|-----------|----------|------|15| Rust Backend | Rust + Tokio | `src-tauri/src/` | Application logic, system access, state management |16| Webview Layer | Platform-native webview | Embedded | Renders HTML/CSS/JS frontend |17| IPC Bridge | JSON-serialized messages | Internal | Connects frontend to backend via `invoke()` and events |18| Plugin System | Rust crate + npm package | `Cargo.toml` + `package.json` | Extends capabilities (fs, dialogs, HTTP, etc.) |19| Permission System | Capabilities + permissions | `src-tauri/capabilities/` | Fine-grained access control (replaces v1 allowlist) |2021### Platform Webview Engines2223| Platform | Webview Engine |24|----------|---------------|25| Windows | WebView2 (Chromium-based) |26| macOS | WKWebView |27| Linux | WebKitGTK |28| iOS | WKWebView |29| Android | Android WebView |3031### Key Dependencies3233| Package | Type | Purpose |34|---------|------|---------|35| `@tauri-apps/cli` | devDependency (npm) | CLI tooling for dev/build |36| `@tauri-apps/api` | dependency (npm) | Frontend API for invoke, events, paths |37| `tauri` | dependency (Cargo) | Core Rust framework |38| `tauri-build` | build-dependency (Cargo) | Build script helpers |39| `serde` + `serde_json` | dependency (Cargo) | Serialization for IPC |4041### Critical Warnings4243**NEVER** block the main thread with synchronous I/O in commands -- ALWAYS use `async` for file, network, or long-running operations. Blocking freezes the entire UI.4445**NEVER** call `.invoke_handler()` more than once on Builder -- only the LAST call takes effect. Put ALL commands in a single `generate_handler![]` macro.4647**NEVER** mark command functions as `pub` when defined directly in `lib.rs` -- the glue code generation prevents it. Move commands to a separate module if they need to be public.4849**NEVER** wrap managed state in `Arc` -- Tauri wraps state in `Arc` internally. Using `app.manage(Arc::new(data))` adds a redundant layer.5051**NEVER** use `&str` in async command parameters -- borrowed references do not work with async command spawning. ALWAYS use `String` instead.5253**NEVER** use `State<'_, T>` when you registered `Mutex<T>` -- this causes a runtime panic, not a compile error. ALWAYS match the exact registered type: `State<'_, Mutex<T>>`.5455---5657## Process Model5859Tauri 2 uses a **two-process architecture**:6061### Main Process (Rust)6263- Runs the Rust backend on the main thread + Tokio async runtime64- Has full system access (filesystem, network, OS APIs)65- Manages application lifecycle, windows, menus, tray icons66- Exposes `#[tauri::command]` functions as IPC endpoints67- Manages state via `app.manage()` with `Arc`-wrapped storage6869### Webview Process (Frontend)7071- Runs in a platform-native webview (NOT Electron/Chromium bundle)72- Renders HTML/CSS/JS like a browser tab73- Communicates with Rust ONLY through the IPC bridge74- Has NO direct system access -- all system operations go through `invoke()`75- Event listeners enable bidirectional pub/sub messaging7677### IPC Bridge7879The bridge between processes uses JSON-serialized messages:8081```82Frontend Rust Backend83 | |84 |--- invoke('cmd', {args}) -------->| (JSON request)85 | |--- execute command86 |<-- Result<T, E> (JSON) ----------| (JSON response)87 | |88 |--- emit('event', payload) ------->| (event pub/sub)89 |<-- emit('event', payload) --------| (event pub/sub)90 | |91 |<-- Channel.send(data) -----------| (streaming)92```9394- `invoke()` sends a JSON object with camelCase keys; Rust receives snake_case parameters95- Events use `Serialize + Clone` payloads96- Channels enable streaming multiple messages from Rust to JS97- Binary data can bypass JSON via `tauri::ipc::Response`9899---100101## Project Structure102103```104my-tauri-app/105├── src/ # Frontend source (React/Vue/Svelte/vanilla)106│ ├── index.html107│ ├── main.ts108│ └── styles.css109├── src-tauri/ # Rust backend110│ ├── src/111│ │ ├── lib.rs # Main app logic (mobile entry point)112│ │ └── main.rs # Desktop entry point (calls lib.rs)113│ ├── capabilities/ # Permission capability files (JSON/TOML)114│ │ └── default.json # Default capabilities115│ ├── permissions/ # Custom command permissions (TOML only)116│ ├── icons/ # Application icons (all required sizes)117│ ├── gen/ # Generated files (DO NOT edit manually)118│ │ └── schemas/119│ │ ├── desktop-schema.json120│ │ ├── mobile-schema.json121│ │ └── remote-schema.json122│ ├── Cargo.toml # Rust dependencies123│ ├── Cargo.lock # Deterministic build lock124│ ├── tauri.conf.json # Main configuration125│ ├── build.rs # Cargo build script126│ └── .taurignore # Exclude files from dev watcher127├── package.json # Frontend dependencies128├── tsconfig.json # TypeScript config (if applicable)129└── vite.config.ts # Bundler config (if Vite)130```131132### Source Control Rules133134- **ALWAYS commit**: `src-tauri/Cargo.lock` (deterministic builds)135- **ALWAYS ignore**: `src-tauri/target/` (build artifacts)136- **NEVER edit**: `src-tauri/gen/` (auto-generated schemas)137138### Entry Point Pattern (Tauri 2.x)139140Desktop and mobile share logic through `lib.rs`:141142```rust143// src-tauri/src/lib.rs144#[cfg_attr(mobile, tauri::mobile_entry_point)]145pub fn run() {146 tauri::Builder::default()147 .invoke_handler(tauri::generate_handler![/* commands */])148 .run(tauri::generate_context!())149 .expect("error while running tauri application");150}151152// src-tauri/src/main.rs (desktop only)153#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]154fn main() {155 app_lib::run();156}157```158159**ALWAYS** use the `lib.rs` + `main.rs` split pattern for Tauri 2 -- this is required for mobile support and is the standard project layout.160161---162163## Type Hierarchy164165### Core Types166167| Type | Description | Traits |168|------|------------|--------|169| `App` | Full application instance (only in `setup()`) | `Manager`, `Emitter`, `Listener` |170| `AppHandle` | Lightweight clone-safe handle to the app | `Manager`, `Emitter`, `Listener`, `Clone + Send + Sync` |171| `WebviewWindow` | Combined window + webview (most common) | `Manager`, `Emitter`, `Listener` |172| `Window` | OS-level window (without webview) | `Manager`, `Emitter`, `Listener` |173| `Webview` | Webview inside a window | `Manager`, `Emitter`, `Listener` |174| `State<'_, T>` | Managed state accessor in commands | -- |175| `Builder` | Application configuration builder | -- |176177### The Manager Trait178179The `Manager` trait is the unifying interface. It is implemented by `App`, `AppHandle`, `Webview`, `WebviewWindow`, and `Window`. Key methods:180181| Method | Return Type | Purpose |182|--------|------------|---------|183| `app_handle()` | `&AppHandle<R>` | Get the AppHandle |184| `config()` | `&Config` | Access tauri.conf.json |185| `state::<T>()` | `State<'_, T>` | Access managed state |186| `try_state::<T>()` | `Option<State<'_, T>>` | Access state without panic |187| `manage(state)` | `bool` | Register new state |188| `get_webview_window(label)` | `Option<WebviewWindow<R>>` | Get window by label |189| `webview_windows()` | `HashMap<String, WebviewWindow<R>>` | Get all windows |190| `get_window(label)` | `Option<Window<R>>` | Get OS window by label |191| `path()` | `&PathResolver<R>` | Access path resolver |192| `package_info()` | `&PackageInfo` | Get package metadata |193194### The Emitter Trait195196| Method | Purpose |197|--------|---------|198| `emit(event, payload)` | Broadcast to ALL targets |199| `emit_to(target, event, payload)` | Emit to a specific target |200| `emit_filter(event, payload, filter_fn)` | Emit to targets matching a filter |201| `emit_str(event, json_string)` | Emit with pre-serialized JSON |202203### The Listener Trait204205| Method | Purpose |206|--------|---------|207| `listen(event, handler)` | Listen for events (returns EventId) |208| `once(event, handler)` | Listen once, auto-remove after first event |209| `unlisten(id)` | Remove a listener |210| `listen_any(event, handler)` | Listen from any source |211212### Runtime Generic213214Most types are generic over `R: Runtime`. With the default `wry` feature, `R` resolves to `Wry`. Use the generic form in plugins or for test mocking:215216```rust217#[tauri::command]218async fn my_command<R: Runtime>(219 app: AppHandle<R>,220 window: WebviewWindow<R>,221) -> Result<(), String> {222 Ok(())223}224```225226---227228## Builder Pattern229230The `Builder` is the single entry point for configuring a Tauri application:231232```rust233tauri::Builder::default()234 .manage(MyState::default()) // state235 .plugin(tauri_plugin_shell::init()) // plugins236 .invoke_handler(tauri::generate_handler![cmd1, cmd2]) // commands237 .setup(|app| { // init hook238 let handle = app.handle().clone();239 // app is &mut App -- full access, runs once240 Ok(())241 })242 .on_window_event(|window, event| { /* ... */ }) // window events243 .on_menu_event(|app, event| { /* ... */ }) // menu events244 .menu(|app| { /* build menu */ }) // app menu245 .run(tauri::generate_context!())246 .expect("error running app");247```248249### Builder Method Reference250251| Method | Purpose |252|--------|---------|253| `manage(T)` | Register managed state |254| `plugin(P)` | Register a plugin |255| `invoke_handler(F)` | Register ALL command handlers |256| `setup(F)` | App initialization hook |257| `menu(F)` | Set the application menu |258| `on_menu_event(F)` | Menu event handler |259| `on_window_event(F)` | Window event handler |260| `on_webview_event(F)` | Webview event handler |261| `on_page_load(F)` | Page load handler |262| `on_tray_icon_event(F)` | Tray icon event handler |263| `build(Context)` | Build without running (returns `App`) |264| `run(Context)` | Build and run the app |265| `any_thread(self)` | Allow running on any thread |266267---268269## Frontend API Overview270271| API Area | Key Functions | Import Path |272|----------|--------------|-------------|273| Commands | `invoke<T>()`, `Channel<T>` | `@tauri-apps/api/core` |274| Events | `listen()`, `emit()`, `emitTo()`, `once()` | `@tauri-apps/api/event` |275| Windows | `getCurrentWindow()`, `getAllWindows()` | `@tauri-apps/api/window` |276| Webviews | `getCurrentWebview()` | `@tauri-apps/api/webview` |277| Paths | `appDataDir()`, `join()`, `BaseDirectory` | `@tauri-apps/api/path` |278| Utilities | `convertFileSrc()`, `isTauri()` | `@tauri-apps/api/core` |279| Testing | `mockIPC()`, `mockWindows()`, `clearMocks()` | `@tauri-apps/api/mocks` |280281---282283## Reference Links284285- [references/methods.md](references/methods.md) -- API signatures for App, AppHandle, Manager, Runtime, Webview, WebviewWindow286- [references/examples.md](references/examples.md) -- Working code examples verified against Tauri 2.x documentation287- [references/anti-patterns.md](references/anti-patterns.md) -- What NOT to do, with WHY explanations288289### Official Sources290291- https://v2.tauri.app/develop/292- https://docs.rs/tauri/latest/tauri/293- https://v2.tauri.app/reference/javascript/api/294- https://v2.tauri.app/start/create-project/