A reference for engineers, architects, contributors, and operators of the Tauri framework.
1. Executive Summary
Tauri is an authoring system for native desktop and mobile applications whose UI is rendered by the host operating system's webview and whose application logic runs in a Rust binary. The two halves communicate over a typed IPC bridge gated by a capability-based access control system (ACL).
The repository at tauri-apps/tauri is a Cargo workspace of 14 crates plus 2 npm packages that, together, implement the runtime, the build pipeline, the bundler, the mobile glue, the JS bindings, and the developer CLI. It is licensed under MIT or Apache-2.0 (dual). It is governed by a programme inside The Commons Conservancy and funded through Open Collective.
This guide covers the architecture, the build pipeline, every configuration knob, the ACL engine, the security model, the operations playbook, and the contribution workflow. By the end, you should have enough information to deploy Tauri at scale, fork it for a derivative product, or understand any production incident a Tauri app produces.
Repository at a glance (commit df3fb97, 2026-04-16):
| Metric | Value |
|---|---|
| Workspace crates | 14 (+ examples and integration tests) |
| npm packages | 2 (@tauri-apps/api, @tauri-apps/cli) |
| Source files | 959 |
| Source size | 2.6 MB of Rust + TypeScript |
| Rust edition / MSRV | 2021 / 1.77.2 |
| Default platforms | macOS 10.15+, Windows 7+, Linux (webkit2gtk-4.1), iOS 9+, Android 7+ |
| License | MIT OR Apache-2.0 |
Tagged as dev branch |
yes (production releases cut from release branch tags) |
Important
The default branch is
dev. Releases are tagged fromrelease/*branches. If you fork for a downstream distribution, base on a release tag, not ondev.
2. Architecture
2.1 The big picture
┌──────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │
│ ┌────────────────────────┐ ┌─────────────────────────┐ │
│ │ Frontend │ │ Backend │ │
│ │ HTML / CSS / JS / TS │ │ Rust (your code) │ │
│ │ React / Svelte / Vue │ │ #[tauri::command] │ │
│ │ (or Yew / Leptos) │ │ tauri::Builder │ │
│ └─────────┬──────────────┘ └────────────┬────────────┘ │
│ │ window.__TAURI_INTERNALS__ │ │
│ │ invoke(cmd, args) │ │
│ v v │
│ ╔═══════════════════════════════════════════════════════════╗ │
│ ║ IPC Bridge (custom protocol) ║ │
│ ║ Webview <─ ipc:// ─> RuntimeAuthority <─> Command Handler║ │
│ ║ ^ ║ │
│ ║ │ ACL check (capability+permission)║ │
│ ╚═══════════════════════════════════════════════════════════╝ │
└──────────────────────────────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
v v v
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ tauri-runtime│ │ plugins │ │ bundler │
│ (trait) │ │ (fs/http/... │ │ (build-time) │
└───────┬──────┘ └──────────────┘ └──────────────┘
│
v
┌──────────────────────────────────────────────────┐
│ tauri-runtime-wry (impl) │
│ wraps WRY + TAO │
└─────────────┬─────────────────────┬──────────────┘
v v
┌─────────────┐ ┌──────────────┐
│ TAO │ │ WRY │
│ (windowing) │ │ (webview) │
└──────┬──────┘ └──────┬───────┘
v v
┌──────────────────────────────────┐
│ OS-native windowing + webview │
│ WebView2 / WKWebView / │
│ WebKitGTK / Android WebView │
└──────────────────────────────────┘
Note
The IPC bridge is the most security-critical path in the architecture. Every frontend-to-backend call traverses
RuntimeAuthority::resolve_access— if the ACL rejects it, the command handler never executes. This default-deny design means a compromised webview can only reach commands explicitly whitelisted for its window label.
2.2 Workspace layout
The Cargo workspace at Cargo.toml defines the crate graph:
tauri/
├── crates/
│ ├── tauri/ ← THE main runtime crate (downstream apps depend on this)
│ ├── tauri-runtime/ ← Trait-only "what is a runtime"
│ ├── tauri-runtime-wry/ ← Default impl: WRY + TAO
│ ├── tauri-build/ ← build.rs helpers
│ ├── tauri-codegen/ ← Compile-time codegen (asset embedding, config parse)
│ ├── tauri-macros/ ← `#[command]`, `generate_handler!`, `generate_context!`
│ ├── tauri-utils/ ← Shared utilities, config types, ACL types, platform detect
│ ├── tauri-plugin/ ← Plugin author SDK
│ ├── tauri-cli/ ← The `tauri` Rust binary (clap)
│ ├── tauri-bundler/ ← Cross-platform bundler (DMG / NSIS / MSI / DEB / RPM / AppImage)
│ ├── tauri-driver/ ← WebDriver server for E2E tests
│ ├── tauri-macos-sign/ ← macOS keychain + codesign helpers
│ ├── tauri-schema-generator/ ← JSON schema generation for tauri.conf.json
│ ├── tauri-schema-worker/ ← Cloudflare Worker that serves schemas (docs site)
│ └── tests/
│ ├── acl/ ← ACL fixture-based tests
│ ├── app-updater/ ← Updater integration tests
│ └── restart/ ← Process relaunch tests
├── packages/
│ ├── api/ ← @tauri-apps/api (TS bindings)
│ └── cli/ ← @tauri-apps/cli (napi-rs wrapper)
├── examples/ ← Reference apps (helloworld, api, isolation, multiwindow, ...)
├── bench/ ← Performance benchmarks
├── audits/ ← cargo-vet supply-chain audits
└── supply-chain/ ← cargo-vet config
2.3 Crate-by-crate
tauri (core)
The crate users write tauri = "2" in their Cargo.toml to depend on. Re-exports tauri-macros, tauri-utils, and the runtime traits. Modules:
| Module | Responsibility |
|---|---|
app |
App, AppHandle, Builder. The lifecycle root. |
manager |
The internal state manager that ties windows, webviews, plugins, IPC, and resources together. |
webview |
Webview, WebviewBuilder. |
window |
Window, WindowBuilder. (Desktop window vs webview is separated for multi-webview windows.) |
webviewWindow |
The convenience type that combines the two for the common case. |
event |
Pub/sub event system. |
ipc |
IPC core: Channel, Invoke, RuntimeAuthority, command resolution. |
menu |
(desktop-only) Menus and submenus. |
tray |
(desktop-only, behind tray-icon feature) Tray icons and tray menus. |
path |
Cross-platform path resolution (AppData, Cache, Resource, etc.). |
plugin |
The plugin runtime: Plugin trait, Builder, lifecycle. |
protocol |
Custom URI protocol handlers (the tauri:// and asset:// schemes). |
pattern |
The Brownfield / Isolation patterns. |
process |
Restart, exit, current binary path. |
resources |
Resource, ResourceTable for sharing typed handles between commands. |
scope |
Filesystem and shell scopes (used by ACL). |
state |
State<T> extractor for command arguments. |
image |
Image type and decoding (gated by image-ico/image-png features). |
vibrancy |
macOS vibrancy effect for transparent windows. |
async_runtime |
Tokio runtime accessor, used internally and exposed to plugin authors. |
tauri-runtime
A trait-only crate that defines what a runtime must provide. The actual implementations (currently only tauri-runtime-wry) plug in here. The split exists so a third party could swap out WRY for a different webview engine without forking core.
tauri-runtime-wry
The default runtime. Implements tauri_runtime::Runtime by wrapping WRY (the webview library) and TAO (the windowing library, a fork of winit with menu/tray support). All the OS-specific webview wiring lives here:
- macOS: WKWebView + Cocoa menus + AppKit windows
- Windows: WebView2 + Win32 windows + WPF tray
- Linux: WebKitGTK + GTK windows + ayatana app indicator
- iOS: WKWebView + UIWindow
- Android: System WebView + Activity
tauri-build
Provides tauri_build::build() to be called from your app's build.rs. It:
- Parses
tauri.conf.jsonat build time and emits cargorustc-cfgflags - Generates the codegen context (icon embedding, config struct, asset map)
- On mobile, generates the JNI bindings and Swift bridge
- Validates the version of
tauri-plugin-*dependencies match each other
tauri-codegen
The compile-time half of tauri-build. Reads tauri.conf.json, hashes assets, and generates the Context struct that tauri::generate_context!() returns at compile time. This is why your binary embeds your icons, your assets, and your config without any runtime file reads.
tauri-macros
The procedural macros: #[tauri::command], tauri::generate_handler![], tauri::generate_context!(), #[tauri::mobile_entry_point]. All Tauri's compile-time magic flows through here.
tauri-utils
Shared types used by everything else — primarily Config, the deserialized tauri.conf.json schema. Also Platform, Target enums; the acl submodule with the permission/capability data structures; pattern detection; CSP injection helpers; and the assets trait that backs both filesystem assets and embedded ones.
tauri-plugin
The SDK plugin authors use. Provides the Builder, lifecycle hooks, and the helper types for declaring plugin-scoped commands and permissions.
tauri-cli
The tauri binary. A clap-derive Rust app with 17 top-level subcommands (see User Guide §4 for the user-facing list). Each subcommand lives in its own module under crates/tauri-cli/src/:
| Module | Subcommand |
|---|---|
init |
tauri init — scaffold a new project. |
dev |
tauri dev — debug build + run with hot-reload. |
build |
tauri build — release build + bundle. |
bundle |
tauri bundle — re-bundle without rebuilding. |
add / remove |
tauri add/tauri remove — plugin install/uninstall. |
plugin |
tauri plugin new/init/android/ios — author a plugin. |
mobile |
tauri ios, tauri android — mobile lifecycle. |
migrate |
tauri migrate — v1 → v2 upgrade. |
info |
tauri info — diagnostics dump. |
icon |
tauri icon — multi-platform icon generation. |
signer |
tauri signer generate/sign — updater signing. |
acl |
tauri permission, tauri capability — manage the ACL. |
completions |
tauri completions — shell completions. |
inspect |
tauri inspect — print derived bundler values. |
interface |
(internal) Interface trait for dev/build to abstract over Rust vs other interfaces. |
helpers |
(internal) Shared helpers: app_paths, cargo, npm, prompts, template, framework. |
The CLI entry point is crates/tauri-cli/src/main.rs, which detects whether it was invoked as cargo-tauri tauri ... or directly and forwards to tauri_cli::run() in lib.rs.
tauri-bundler
The cross-platform bundler. Each format lives in crates/tauri-bundler/src/bundle/:
| Module | Output |
|---|---|
bundle/macos/app |
.app directory bundle. |
bundle/macos/dmg |
.dmg disk image (calls hdiutil). |
bundle/macos/sign |
macOS code signing (calls codesign). |
bundle/macos/notarize |
Notarization via xcrun notarytool. |
bundle/windows/nsis |
NSIS installer (downloads NSIS, runs makensis). |
bundle/windows/msi |
WiX installer (downloads WiX Toolset, runs candle + light). |
bundle/linux/deb |
Debian package (no external tool needed). |
bundle/linux/rpm |
RPM package (uses rpmbuild if present; falls back to in-process). |
bundle/linux/appimage |
AppImage (downloads appimagetool). |
bundle/linux/freedesktop |
.desktop file generation. |
bundle/updater |
.tar.gz + .sig for the updater. |
bundle/kmp |
Kotlin Multiplatform XCFramework (mobile plugin packaging). |
Bundlers download their tools on demand (NSIS, WiX, appimagetool) and cache them under the user's cache directory. The downloads can be redirected to a mirror via TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE.
tauri-driver
A WebDriver server that lets you E2E-test Tauri apps using standard Selenium / WebDriver clients. Spawns the app, attaches a WebDriver session per webview. Documented in the crates/tauri-driver/README.md.
tauri-macos-sign
Helpers for macOS keychain operations: importing certificates, signing arbitrary files, managing the build-time keychain. Used by the bundler's macOS code signing.
tauri-schema-generator and tauri-schema-worker
The schema generator produces a JSON Schema document from the tauri-utils::Config Rust type via schemars. It's invoked at compile time of the CLI to emit crates/tauri-cli/config.schema.json. The schema worker is a Cloudflare Worker that serves the schemas to the docs site so editors can fetch them at runtime.
packages/api
The TypeScript bindings users import from @tauri-apps/api/.... Build artifacts go to packages/api/dist/ via Rollup. The package exposes:
| Module | Purpose |
|---|---|
@tauri-apps/api/core |
invoke, Channel, convertFileSrc, transformCallback. |
@tauri-apps/api/event |
listen, emit, once, TauriEvent enum, targeted variants. |
@tauri-apps/api/window |
getCurrentWindow, Window, LogicalSize, PhysicalSize, monitor info. |
@tauri-apps/api/webview |
Webview, getCurrentWebview. |
@tauri-apps/api/webviewWindow |
WebviewWindow (the combined type). |
@tauri-apps/api/menu |
Menu / Submenu / MenuItem builders. |
@tauri-apps/api/tray |
TrayIcon. |
@tauri-apps/api/path |
appDataDir, appConfigDir, resolveResource, etc. |
@tauri-apps/api/dpi |
LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize. |
@tauri-apps/api/image |
Image factory + serialization helpers. |
@tauri-apps/api/app |
App version, name, hide, show, exit. |
@tauri-apps/api/mocks |
mockIPC, mockWindows for unit-testing frontends without a real Tauri runtime. |
packages/cli
The npm distribution of the CLI. Wraps the Rust tauri-cli binary using napi-rs and ships precompiled binaries per (os, arch) combo under packages/cli/npm/<target-triple>/. The N-API binding is in packages/cli/src/.
2.4 The IPC bridge
The IPC is the crown jewel — every command call, event, channel message, and plugin invocation goes through it.
[ Webview JS ]
│
│ window.__TAURI_INTERNALS__.invoke('cmd_name', args)
v
[ JS shim that POSTs to ipc:// or postMessage to the host ]
│
v
[ tauri-runtime-wry IPC handler ] ← OS-specific intercept
│
v
[ Manager::run_invoke_handler ]
│
v
[ RuntimeAuthority::resolve_access ] ← ACL check
│ YES → continue
│ NO → reject with "command not allowed by ACL"
v
[ Command resolver ] ← parameter extraction
│
v
[ Your #[tauri::command] fn ]
│
v
[ Serialize result ]
│
v
[ Webview JS Promise resolves with result ]
Source layout under crates/tauri/src/ipc/:
| File | Role |
|---|---|
mod.rs |
Public API: Invoke, InvokeBody, InvokeResponseBody. |
authority.rs |
RuntimeAuthority — owns the resolved capability set and answers "may window X call command Y on plugin Z?". |
capability_builder.rs |
Programmatic capability construction (used by add_capability). |
command.rs |
Command resolver — extracts parameters from the IPC payload. |
channel.rs |
Channel<T> for streaming responses back to JS. |
format_callback.rs |
Legacy callback formatter for early IPC API. |
protocol.rs |
The ipc:// custom URI protocol. |
The IPC payload is JSON by default but can be raw bytes via InvokeBody::Raw for performance-sensitive commands (file uploads, image encoding).
2.5 The plugin runtime
A plugin is a struct that implements tauri::plugin::Plugin:
pub trait Plugin<R: Runtime>: Send {
fn name(&self) -> &'static str;
fn initialize(&mut self, app: &AppHandle<R>, config: serde_json::Value) -> Result<()>;
fn extend_api(&mut self, invoke: Invoke<R>) -> bool;
fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent);
fn on_navigation(&mut self, window: &Window<R>, url: &Url) -> bool;
fn on_webview_ready(&mut self, window: Window<R>);
fn on_drop(&mut self, app: AppHandle<R>);
fn js_init_script(&self) -> Option<String>;
}
Most plugins use tauri_plugin::Builder to assemble the trait impl declaratively. The builder also handles the permission file discovery (looking for permissions/*.toml) and the JS init script injection.
2.6 The build pipeline (compile time)
When you run cargo build (directly or via tauri build), this happens:
1. cargo runs build.rs in your src-tauri/
└─> tauri_build::build()
├─> Reads tauri.conf.json
├─> Validates plugin version compatibility (across tauri/tauri-plugin-*)
├─> Emits cargo:rustc-cfg=mobile|desktop|tauri_*=value
├─> On mobile: generates JNI/Swift bindings
└─> Triggers codegen for the Context struct
2. cargo compiles your code
└─> tauri::generate_context!() macro
├─> Calls tauri_codegen::context()
├─> Embeds icons, assets, config struct
└─> Returns a Context<R> at runtime
3. cargo links the binary
4. (only via `tauri build`) tauri_bundler::bundle_project()
├─> Detects target triple
├─> Picks bundle formats from config + flags
├─> Per format: download tools, build artifact, sign
└─> Emits target/release/bundle/<format>/<files>
2.7 Frontend asset serving
By default, Tauri uses the tauri:// custom protocol (or asset:// for filesystem assets):
- Webview navigates to
tauri://localhost - The
tauri-runtime-wryIPC handler intercepts and serves the embedded asset - No localhost HTTP server runs (that's the
tauri-plugin-localhostopt-in)
This is why your bundle is small and there's no port to clash with. It's also why you have to declare connect-src in CSP to include ipc: and http://ipc.localhost (the IPC reply channel).
3. Requirements and Installation
3.1 Build host requirements
| Component | Required Version | Notes |
|---|---|---|
| Rust | 1.77.2 or newer | Workspace rust-version. Older versions fail at compile time. |
| Cargo | matches Rust | rustup-managed. |
| Node.js | 18+ | Only if using the npm path or a JS frontend. |
| pnpm / npm / yarn / bun | latest | Package manager for @tauri-apps/cli and @tauri-apps/api. |
| Xcode CLI Tools | 14+ | macOS only, required for tauri build. |
| Visual Studio Build Tools | 2019+ | Windows only, "Desktop development with C++" workload. |
| WebView2 Runtime | 95+ | Windows only, preinstalled on Win11. |
webkit2gtk-4.1-dev |
4.1+ | Linux only (Tauri v2). v1 wants 4.0. |
libxdo-dev |
any | Linux only, optional, for menu Cut/Copy/Paste. |
| Android Studio + SDK 36 + NDK 29.0.13846066 | exact | Android targets only. CLI installs them on prompt. |
| Xcode | 15+ | iOS targets only, macOS host. |
ldid |
any | macOS bundler dependency for codesign. |
3.2 Building Tauri itself (contributor / fork)
git clone https://github.com/tauri-apps/tauri
cd tauri
# Install pnpm
corepack enable
# Build the workspace
cargo check --workspace --all-features
# Build npm packages
pnpm install
pnpm run build
# Run a smoke test
cd examples/api/src-tauri
cargo tauri dev
The workspace patches schemars and re-points all internal dependencies to local paths via [patch.crates-io] in the root Cargo.toml:
[patch.crates-io]
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
tauri = { path = "./crates/tauri" }
tauri-plugin = { path = "./crates/tauri-plugin" }
tauri-utils = { path = "./crates/tauri-utils" }
3.3 Release profiles
The workspace ships two release profiles:
[profile.release]
panic = "abort" # No unwinding → smaller binary
codegen-units = 1 # Single codegen unit → better LLVM optimization
lto = true # Link-time optimization
incremental = false # Disable incremental for max optimization
opt-level = "s" # Optimize for size
strip = true # Strip symbols
[profile.release-size-optimized]
inherits = "release"
codegen-units = 1
lto = true
incremental = false
opt-level = "s"
User apps inherit [profile.release] defaults but can override in their own Cargo.toml. For maximum size optimization use opt-level = "z" (smaller, slower) and add strip = "symbols".
4. Configuration Mastery — tauri.conf.json
The configuration file is the source of truth for what your app is. It's parsed at compile time (frozen into the binary) and at run time (for non-build settings). Every field maps to a Rust type in tauri_utils::config::Config.
4.1 Top-level keys
| Key | Type | Purpose |
|---|---|---|
$schema |
string | URL or path to the JSON schema. Enables editor completions. |
productName |
string | Human display name. |
version |
string | object | App version. Either a SemVer string or { major, minor, patch, ... }. |
identifier |
string | Reverse-DNS bundle ID. Cannot be com.tauri.dev in production. |
mainBinaryName |
string | Override the binary name (defaults to productName slugified). |
app |
object | Runtime app configuration (windows, security, tray). |
build |
object | Build-time configuration (dev URL, frontend dist, before commands). |
bundle |
object | Bundling configuration (per-format settings, icons, signing). |
plugins |
object | Per-plugin configuration. |
4.2 build block
| Field | Type | Default | Purpose |
|---|---|---|---|
frontendDist |
string | string[] | null |
Path(s) to built frontend, relative to tauri.conf.json. |
devUrl |
string | null |
Dev server URL loaded by tauri dev. |
beforeDevCommand |
string | object | null |
Shell command before tauri dev. Object form: { cwd, script, wait }. |
beforeBuildCommand |
string | null |
Shell command before tauri build compiles Rust. |
beforeBundleCommand |
string | null |
Shell command before bundling step. |
runner |
string | cargo |
Override the cargo binary (e.g. cross). |
features |
string[] | [] |
Cargo features to enable for tauri dev and tauri build. |
removeUnusedCommands |
boolean | false |
Strip uncalled IPC commands at build time (smaller binaries). |
4.3 app block
| Field | Type | Default | Purpose |
|---|---|---|---|
windows |
array | [default] |
Window definitions. |
security.csp |
string | object | null |
Content Security Policy. Object form per-directive. |
security.devCsp |
string | null |
CSP override for tauri dev. |
security.freezePrototype |
boolean | false |
Freeze JS prototypes to block prototype pollution. |
security.dangerousDisableAssetCspModification |
boolean | false |
DO NOT USE unless you know what you're doing. |
security.pattern.use |
"brownfield" | "isolation" |
brownfield |
Security pattern selection. |
security.pattern.options |
object | depends | Pattern-specific options (e.g. dir for isolation). |
security.assetProtocol.enable |
boolean | false |
Enable the asset:// custom protocol. |
security.assetProtocol.scope |
object | empty | Glob allow/deny lists for asset access. |
security.headers |
object | {} |
Per-route HTTP headers injected by the IPC server. |
security.capabilities |
array | [] |
Inline capabilities (alternative to capabilities/*.json files). |
withGlobalTauri |
boolean | false |
Expose window.__TAURI__ for vanilla JS. |
macOSPrivateApi |
boolean | false |
Enable transparent windows + fullscreenEnabled on macOS. |
trayIcon |
object | null |
Static tray icon definition. Auto-enables tray-icon cargo feature. |
enableGTKAppId |
boolean | false |
Use identifier as the GTK app ID on Linux. |
4.4 app.windows[] schema
Each window object:
| Field | Type | Default | Purpose |
|---|---|---|---|
label |
string | "main" |
Unique window ID. Critical — used in capability targeting and Rust APIs. |
url |
string | "index.html" |
URL or path loaded into the webview. |
title |
string | productName |
Initial window title. |
width / height |
number | 800 / 600 |
Initial size. |
x / y |
number | (centered) | Initial position. |
minWidth / minHeight |
number | none | Minimum size constraints. |
maxWidth / maxHeight |
number | none | Maximum size constraints. |
resizable |
boolean | true |
Allow user resize. |
maximizable / minimizable / closable |
boolean | true |
Window button enables. |
fullscreen |
boolean | false |
Start fullscreen. |
transparent |
boolean | false |
Transparent window. Requires macos-private-api on macOS. |
decorations |
boolean | true |
Show OS chrome. false = chromeless window. |
alwaysOnTop |
boolean | false |
Stay above other windows. |
skipTaskbar |
boolean | false |
Hide from taskbar/dock. |
theme |
"Light" | "Dark" | null |
system | Force a theme. |
titleBarStyle |
"Visible" | "Transparent" | "Overlay" |
Visible |
macOS-only. |
hiddenTitle |
boolean | false |
macOS hide title text but keep buttons. |
acceptFirstMouse |
boolean | false |
macOS accept clicks that bring window to front. |
tabbingIdentifier |
string | none | macOS tab grouping ID. |
additionalBrowserArgs |
string | none | Windows: WebView2 command-line flags. |
windowEffects |
object | none | macOS vibrancy / Windows acrylic. |
incognito |
boolean | false |
Don't persist cookies/storage. |
parent |
string | none | Parent window label (modal child). |
proxyUrl |
string | none | HTTP/SOCKS proxy URL for the webview. |
zoomHotkeysEnabled |
boolean | false |
Allow Ctrl+/Ctrl-/Ctrl0 zoom. |
useHttpsScheme |
boolean | false |
Serve via https://tauri.localhost (better for cookie compat). |
browserExtensionsEnabled |
boolean | false |
Allow installed browser extensions (Windows WebView2). |
devtools |
boolean | (debug=true) |
Enable webview devtools. |
userAgent |
string | (default) | Override the webview User-Agent. |
dragDropEnabled |
boolean | true |
Receive tauri://drag-* events. |
visible |
boolean | true |
Start visible. Set false to show after data loads. |
focus |
boolean | true |
Start focused. |
center |
boolean | false |
Ignore x/y, center on screen. |
4.5 bundle block
| Field | Type | Default | Purpose |
|---|---|---|---|
active |
boolean | true |
Enable bundling. |
targets |
array | "all" |
"all" |
Restrict bundle formats. |
icon |
string[] | [] |
Paths to icon files. Required for production. |
category |
string | null |
macOS LSApplicationCategoryType. |
copyright |
string | null |
Copyright string in installers. |
licenseFile |
string | null |
Path to LICENSE for installer EULA pages. |
shortDescription |
string | null |
Short description for installers. |
longDescription |
string | null |
Long description for installers. |
homepage |
string | null |
Homepage URL. |
publisher |
string | null |
Publisher name (Windows installers). |
resources |
array | object | null |
Files to copy into bundle. |
externalBin |
string[] | null |
Sidecar binaries to bundle. |
fileAssociations |
array | null |
Register file extensions with the OS. |
protocol |
array | null |
Register URL schemes (alternative to deep-link plugin for static schemes). |
createUpdaterArtifacts |
boolean | "v1Compatible" |
false |
Generate updater .tar.gz + .sig. |
linux |
object | {} |
Linux-specific bundle settings (Deb, Rpm, AppImage subblocks). |
macOS |
object | {} |
macOS-specific (frameworks, signing, notarization, dmg config). |
windows |
object | {} |
Windows-specific (NSIS, WiX, certificate). |
iOS |
object | {} |
iOS-specific (development team, min version). |
android |
object | {} |
Android-specific (min SDK, version code, signing). |
4.6 bundle.macOS deep-dive
| Field | Type | Purpose |
|---|---|---|
frameworks |
string[] | Embed frameworks in .app/Contents/Frameworks/. |
minimumSystemVersion |
string | LSMinimumSystemVersion. Default 10.15. |
exceptionDomain |
string | App Transport Security exception. |
signingIdentity |
string | Codesign identity. Use "-" for ad-hoc signing. |
hardenedRuntime |
boolean | Enable hardened runtime (required for notarization). |
entitlements |
string | Path to .entitlements plist. |
providerShortName |
string | Apple Provider short name (for notarytool). |
dmg |
object | DMG-specific: background, windowSize, windowPosition, appPosition, applicationFolderPosition. |
Notarization environment variables:
| Variable | Required for notarization |
|---|---|
APPLE_ID |
yes (Apple ID email) |
APPLE_PASSWORD |
yes (app-specific password) |
APPLE_TEAM_ID |
yes (10-char team ID) |
APPLE_API_KEY |
alternative to APPLE_PASSWORD (App Store Connect API key) |
APPLE_API_ISSUER |
with APPLE_API_KEY |
APPLE_API_KEY_PATH |
with APPLE_API_KEY |
4.7 bundle.windows deep-dive
| Field | Purpose |
|---|---|
digestAlgorithm |
"sha256" (recommended) or "sha1". |
certificateThumbprint |
Cert thumbprint in the Windows cert store. |
timestampUrl |
Timestamp server URL for code signing. |
tsp |
Use RFC 3161 timestamp (true) vs Authenticode (false). |
webviewInstallMode |
"downloadBootstrapper", "embedBootstrapper", "offlineInstaller", "fixedRuntime", "skip". |
wix |
WiX-specific config: language, template, fragmentPaths, componentRefs, featureRefs, mergeRefs, enableElevatedUpdateTask, bannerPath, dialogImagePath, upgradeCode. |
nsis |
NSIS-specific: template, installerIcon, installMode, languages, displayLanguageSelector, compression, customLanguageFiles, headerImage, sidebarImage, installerHooks. |
signCommand |
Override the default signtool command. |
4.8 bundle.linux deep-dive
{
"bundle": {
"linux": {
"deb": {
"depends": ["libwebkit2gtk-4.1-0", "libgtk-3-0"],
"section": "utils",
"priority": "optional",
"files": { "/etc/myapp/config": "../etc/config" },
"desktopTemplate": "path/to/template.desktop"
},
"rpm": {
"depends": ["webkit2gtk4.1"],
"release": "1",
"epoch": 0,
"files": { "/etc/myapp/config": "../etc/config" },
"desktopTemplate": "path/to/template.desktop"
},
"appimage": {
"bundleMediaFramework": false,
"files": {}
}
}
}
}
4.9 plugins block
Each plugin defines its own schema under plugins.<name>:
{
"plugins": {
"fs": {
"scope": ["$APPCONFIG/**", "$APPDATA/**"],
"requireLiteralLeadingDot": false
},
"shell": {
"open": true,
"scope": [
{ "name": "git", "cmd": "git", "args": [{ "validator": "^[\\w-]+$" }] }
]
},
"updater": {
"active": true,
"endpoints": ["https://releases.example.com/{{target}}/{{current_version}}/manifest.json"],
"dialog": false,
"pubkey": "<your-base64-pubkey>",
"windows": { "installMode": "passive" }
},
"cli": {
"description": "My App",
"args": [{ "name": "verbose", "short": "v" }],
"subcommands": {
"update": { "description": "Update", "args": [] }
}
}
}
}
The schema for each plugin lives in that plugin's own crate (tauri-plugin-fs/permissions/schemas/, etc.). Use $schema in your config to get editor completions.
4.10 Config merging
Configs merge in this order (later wins):
- Base
tauri.conf.json(orTauri.tomlwithconfig-tomlfeature, ortauri.conf.json5withconfig-json5) - Platform-specific file (
tauri.linux.conf.json,tauri.macos.conf.json,tauri.windows.conf.json,tauri.android.conf.json,tauri.ios.conf.json) --config <FILE>arguments (one or more, merged in order)--config '<JSON>'inline argumentsTAURI_CONFIGenvironment variable (inline JSON)
The merge is JSON merge patch semantics — objects merge recursively, arrays are replaced wholesale.
Important
Array fields are replaced, not concatenated. If you want to add to a window list in a platform override, you must include the full base array plus your additions.
Important
The
identifierfield is baked into filesystem paths, bundle IDs, and OS registrations at first release. Changing it after distribution creates a new app identity — users will see a second app instead of an update. Treat it as immutable once shipped.
5. The Permission and Capability System (ACL)
5.1 Why it exists
Tauri v1's allowlist was an opt-in flag per built-in API. It had no scope (file APIs were on or off), no per-window granularity, and didn't extend to plugins. v2 replaces it with a structured ACL designed to:
- Default-deny everything. A fresh app with no capabilities can call zero IPC commands.
- Scope per command. Each command, in core or plugin, gets its own permission name.
- Bind to windows. A capability lists which window labels it applies to.
- Carry a payload. Permissions can include allow/deny scope objects (file paths, URL globs, command patterns).
- Compose. Plugins ship default permission bundles; you mix them.
5.2 Permission
A permission is a TOML or JSON file (TOML is the convention) inside a plugin's permissions/ directory:
"$schema" = "schemas/schema.json"
[default]
description = "Default permissions for the fs plugin (read-only access to AppData)"
permissions = [
"allow-read-text-file",
"allow-read-binary-file",
"allow-exists",
"allow-stat"
]
[[permission]]
identifier = "allow-read-text-file"
description = "Allows reading text files"
commands.allow = ["read_text_file"]
[[permission]]
identifier = "allow-write-text-file"
description = "Allows writing text files"
commands.allow = ["write_text_file"]
| Field | Purpose |
|---|---|
[default] |
The bundle other capabilities reference as <plugin>:default. |
[[permission]] |
A single permission. |
identifier |
Local name (referenced as <plugin>:<identifier> in capabilities). |
commands.allow |
Commands this permission allows. |
commands.deny |
Commands this permission explicitly denies (deny wins over allow). |
scope |
Optional payload schema (per-plugin meaning). |
platforms |
Restrict to specific OS. |
The schema is published at crates/tauri-cli/permission.schema.json.
5.3 Capability
A capability is a JSON file in src-tauri/capabilities/:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Capabilities for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
"fs:allow-write-text-file",
{
"identifier": "fs:scope",
"allow": [{ "path": "$APPCONFIG/*.json" }],
"deny": [{ "path": "$APPCONFIG/secrets.json" }]
}
],
"platforms": ["linux", "macOS", "windows"],
"local": true
}
| Field | Purpose |
|---|---|
identifier |
Unique name in the project. |
windows / webviews |
Glob lists of labels. ["*"] matches all. |
permissions |
List of permission references (string) or scope objects. |
platforms |
Restrict by OS. |
local |
If false, applies to remote URLs too. Default true. |
remote |
Inverse of local for explicit remote scoping. |
context |
"local", "remote", or both — alternative to local/remote. |
5.4 Capability resolution
At build time, tauri-build walks src-tauri/capabilities/ and merges every JSON file. The merged set is fed to tauri-codegen, which:
- Validates every permission identifier exists
- Resolves
<plugin>:defaultto the plugin's[default]bundle - Computes a per-window allowlist
- Generates a Rust
RuntimeAuthoritypopulated atapp.run()time
At runtime, every IPC call passes through RuntimeAuthority::resolve_access(window_label, command, payload_scope). If the command isn't in the window's allowlist, the call is rejected with the "command not allowed by ACL" error before any handler runs.
5.5 Inline capabilities (config-only)
For small apps you can avoid capabilities/*.json files and inline them in tauri.conf.json:
{
"app": {
"security": {
"capabilities": [
{
"identifier": "main",
"windows": ["main"],
"permissions": ["core:default", "fs:default"]
}
]
}
}
}
5.6 Runtime capabilities (dynamic-acl feature)
With the default dynamic-acl feature, plugins or your code can register capabilities at runtime:
use tauri::Manager;
app.add_capability(serde_json::json!({
"identifier": "user-granted-fs",
"windows": ["main"],
"permissions": [
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": user_picked_dir }]
}
]
}))?;
This is how the dialog plugin "expands" the FS scope when a user picks a file — the picked path is added to a dynamically registered capability.
5.7 Schemas
Tauri's build pipeline regenerates ACL schemas every compile under src-tauri/gen/schemas/:
desktop-schema.json— for desktop capabilitiesmobile-schema.json— for mobile capabilitieslinux-schema.json,macOS-schema.json,windows-schema.json,android-schema.json,iOS-schema.json— per-platform
Reference these in your capability files' $schema field for editor completions.
5.8 tauri permission and tauri capability CLI
| Command | Action |
|---|---|
tauri permission new <id> |
Create a new permission TOML (in your plugin's permissions/ directory). |
tauri permission add <perm-id> <capability-id> |
Append a permission to a capability file. |
tauri permission rm <perm-id> <capability-id> |
Remove a permission from a capability file. |
tauri permission ls |
List all known permissions in the project. |
tauri capability new <id> |
Create a new capability JSON file. |
6. The Bundler
6.1 Bundler invocation
The bundler is invoked as part of tauri build or standalone via tauri bundle. Its entry point is tauri_bundler::bundle_project() in crates/tauri-bundler/src/bundle.rs.
let settings = SettingsBuilder::new()
.project_out_directory("target/release")
.package_settings(...)
.bundle_settings(...)
.build()?;
let outputs = tauri_bundler::bundle_project(&settings)?;
The CLI does this on your behalf.
6.2 Per-format pipeline
Each format's logic lives in crates/tauri-bundler/src/bundle/<platform>/<format>/:
macOS DMG — Builds the .app first (under bundle/macos/app.rs), then creates a DMG via hdiutil with a styled background, icon positions, and Applications shortcut.
macOS notarization — bundle/macos/sign.rs invokes codesign with the user's identity. bundle/macos/notarize.rs calls xcrun notarytool and (unless --skip-stapling) waits for the ticket and runs xcrun stapler staple.
Windows NSIS — Downloads NSIS to the user's cache dir, generates installer.nsi from a Handlebars template (bundle/windows/nsis/installer.nsi), runs makensis.exe, then signs the resulting .exe if a signing config is present.
Windows MSI — Downloads WiX Toolset, generates a WXS file from bundle/windows/msi/main.wxs template, runs candle.exe then light.exe, then signs.
Linux Deb — Builds the directory layout under target/release/bundle/deb/<app>_<ver>_amd64/, writes DEBIAN/control, then assembles the .deb archive in pure Rust (no external dpkg-deb).
Linux RPM — Uses the rpm crate to build the package in pure Rust if rpmbuild isn't available.
Linux AppImage — Downloads appimagetool, builds the AppDir, runs appimagetool to produce the squashfs-based AppImage.
Updater — Bundles the platform's primary artifact format into a .tar.gz (Linux/macOS) or .zip (Windows), then writes a .sig file using the configured signing key.
6.3 Bundle output paths
src-tauri/target/release/bundle/
├── deb/<app>_<version>_<arch>.deb
├── rpm/<app>-<version>.<arch>.rpm
├── appimage/<app>_<version>_<arch>.AppImage
├── macos/<app>.app
├── dmg/<app>_<version>_<arch>.dmg
├── nsis/<app>_<version>_<arch>-setup.exe
├── msi/<app>_<version>_<arch>.msi
├── updater/<app>.app.tar.gz (and .sig)
└── kmp/<app>.xcframework (mobile plugin packaging)
6.4 Signing matrix
| Bundle | Signs with | Env var(s) |
|---|---|---|
macOS .app |
codesign + Developer ID cert |
APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID |
macOS .dmg |
codesign + Developer ID cert |
same |
Windows .exe/.msi |
signtool.exe + cert from cert store |
certificateThumbprint in config |
Linux .deb/.rpm/.AppImage |
(none by default — repo signing happens elsewhere) | - |
Updater .tar.gz/.zip |
Tauri's own Ed25519 keypair | TAURI_SIGNING_PRIVATE_KEY, TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
7. The Updater
7.1 Architecture
[ Your app starts ]
│
v
[ JS calls check() ]
│
v
[ tauri-plugin-updater fetches manifest URL ]
│
v
[ Manifest has { version, signature, url } per platform/arch ]
│ (version > current)
v
[ Plugin downloads update artifact ]
│
v
[ Plugin verifies signature against embedded pubkey ]
│ (verified)
v
[ Plugin extracts and replaces app contents ]
│
v
[ Plugin restarts app ]
7.2 Manifest URL templating
The endpoint URL supports placeholders:
| Placeholder | Replaced with |
|---|---|
{{target}} |
darwin-aarch64, darwin-x86_64, windows-x86_64, linux-x86_64, etc. |
{{current_version}} |
The app's current version. |
{{arch}} |
aarch64, x86_64, etc. |
Example:
https://releases.example.com/{{target}}/{{current_version}}/manifest.json
7.3 Manifest schema
{
"version": "1.2.0",
"notes": "Release notes...",
"pub_date": "2024-01-15T12:00:00Z",
"platforms": {
"darwin-aarch64": {
"signature": "<base64 .sig contents>",
"url": "https://example.com/downloads/MyApp-1.2.0-aarch64.app.tar.gz"
},
"darwin-x86_64": { ... },
"windows-x86_64": {
"signature": "...",
"url": "https://example.com/downloads/MyApp-1.2.0-x86_64.msi.zip"
},
"linux-x86_64": {
"signature": "...",
"url": "https://example.com/downloads/MyApp-1.2.0-amd64.AppImage.tar.gz"
}
}
}
7.4 Signing keys
tauri signer generate produces an Ed25519 keypair using the minisign format. The private key file is encrypted with a password (or unencrypted if -p "").
The public key is embedded in your binary via tauri.conf.json > plugins.updater.pubkey. The plugin uses it to verify every update artifact.
Warning
The signature verification is the only integrity check for updates. Network-level HTTPS is not enough — anyone who breaches your update server can serve malicious binaries unless they also have your private key. Treat the private key like a code-signing certificate.
7.5 Operational best practices
- Store
TAURI_SIGNING_PRIVATE_KEYin CI secrets, never in the repo. - Use a static-hosting CDN for update artifacts (S3 + CloudFront, R2, etc.).
- Use immutable filenames per release (don't overwrite).
- Use a versioned manifest filename (
manifest-1.2.0.json) and bumpendpointscarefully — or use a version-aware proxy. - Test updates in a staging environment before pushing the manifest.
- Have a rollback plan: keep the previous manifest pinned to an earlier version so you can revert by removing the new manifest.
8. Mobile Architecture
8.1 Android
Tauri Android apps are an Android Library (in the Tauri sense) wrapped in an Android Application generated by the CLI. The Rust code compiles to a .so (cdylib crate type) that the Android app loads via JNI.
Your App (Kotlin) ← gen/android/app/src/main/java/<id>/MainActivity.kt
│
v
TauriActivity (Java) ← crates/tauri/mobile/android/src/main/java/app/tauri/
│
v
JNI ↔ tauri_android (Rust) ← crates/tauri-runtime-wry compiled with android target
│
v
WryActivity → WebView
The CLI uses cargo-mobile2 (a fork of cargo-mobile) under the hood to manage the Gradle project lifecycle.
8.2 iOS
iOS apps are a Swift app embedding Rust code as a static library (staticlib crate type).
Your App (Swift) ← gen/apple/<id>/<id>App.swift
│
v
Tauri (Swift) ← crates/tauri/mobile/ios-api/Sources/Tauri/
│
v
Static lib link → tauri (Rust)
│
v
WKWebView in UIWindow
8.3 Mobile-aware plugins
Plugins that need native code on mobile ship Kotlin (Android) and Swift (iOS) modules in android/ and ios/ subdirectories. The Plugin Kotlin class extends app.tauri.plugin.Plugin; the Swift class implements the iOS plugin protocol. The Rust side uses swift-rs and JNI to call into them.
8.4 Mobile build artifacts
| Target | Output |
|---|---|
tauri android build (default) |
.apk + .aab in gen/android/app/build/outputs/. |
tauri android build --apk |
.apk only. |
tauri android build --aab |
.aab only (for Play Store). |
tauri ios build |
.ipa in gen/apple/build/. |
tauri ios build --export-method app-store |
App Store-ready .ipa. |
tauri ios build --export-method ad-hoc |
Ad-hoc distribution .ipa. |
tauri ios build --export-method development |
Development .ipa. |
9. Security Analysis
9.1 Threat model
Tauri's threat model assumes:
- The OS is trusted (kernel, webview engine).
- The Rust binary is trusted (you wrote it; it was code-signed by you).
- The frontend code is untrusted in the sense that it could be modified by a compromised dependency or runtime exploit.
- The network is hostile (MITM, DNS spoofing).
- The filesystem outside the app's data dir is hostile.
The defenses:
- Default-deny IPC via the ACL — a compromised frontend can only call commands explicitly granted to it.
- Custom protocol, not localhost HTTP — no port for other apps to scan, no CORS confusion.
- CSP restricting external resource loads.
- Isolation pattern for sensitive apps — every IPC payload passes through an app-controlled iframe before reaching Rust.
- Updater signature verification — only releases signed by the developer are accepted.
- Code-signed bundles — Gatekeeper / SmartScreen / packagekit verify provenance.
9.2 Known unsafes and gotchas
dangerousDisableAssetCspModification: truedisables Tauri's automatic nonce injection. Only set if you serve a pre-built CSP and know what you're doing.security.dangerousUseHttpSchemeuses plain HTTP for the IPC protocol; only allowed for legacy migration.shell:allow-executewith a wildcardargsregex is a remote code execution waiting to happen if any window can reach it.httpplugin without scope = anything the network can reach is reachable from JS. Always scope.fsplugin'sBaseDirectory.Home($HOME/**) is broader than most apps need. PreferBaseDirectory.AppDataorBaseDirectory.AppConfig.- The isolation pattern is opt-in and has a small runtime cost (iframe round-trip per IPC). Worth it for security-critical apps.
9.3 Reporting vulnerabilities
Per SECURITY.md: use GitHub Private Vulnerability Disclosure. Do not open a public issue, PR, Discord message, or forum post. The Tauri team aims for 90-day coordinated disclosure. Versions > 1.0 are supported with security patches; < 1.0 is not.
9.4 Active RUSTSEC advisories
As of commit df3fb97, the issue tracker has multiple open RUSTSEC advisories tracking transitive dependencies (issues #15243, #15244, #15245, #15246, #15247, #15248, #15249 — name-constraint validation in TLS certificate handling, and a license-file-naming compliance issue). Operators should:
- Subscribe to the Tauri release feed.
- Run
cargo auditin CI on every build. - Pin to known-good Tauri minor versions; bump on patch releases promptly.
- Use
cargo-vet(Tauri's own choice — seesupply-chain/andaudits/) for supply-chain audit metadata.
9.5 The supply-chain audit ledger
The repository ships its own supply-chain/ directory with cargo-vet audits. Every direct dependency has been reviewed by a Tauri maintainer and signed off in audits/. If you fork Tauri and add dependencies, follow the same process — cargo vet check will fail in CI otherwise.
10. Operations and Maintenance
10.1 Running the dev pipeline
The tauri dev lifecycle is:
1. Resolve dirs (find tauri.conf.json by walking up from cwd)
2. Load config (base + platform override + --config args)
3. Run beforeDevCommand
4. Wait for devUrl to respond (unless --no-dev-server-wait)
5. Build the Rust binary (cargo build, debug profile)
6. Spawn the binary with TAURI_DEV=true
7. Watch Rust files
8. On change: SIGTERM the binary, rebuild, respawn
Logs are colored via env_logger with custom action labels (Compiling, Bundling, etc.). Verbosity controlled by -v.
10.2 Stopping cleanly in CI
tauri dev traps SIGTERM and SIGINT. For containerized CI, use a process supervisor that propagates signals (e.g. dumb-init):
FROM rust:1.77-bullseye
RUN apt-get install -y libwebkit2gtk-4.1-dev
ENTRYPOINT ["dumb-init", "--"]
CMD ["pnpm", "tauri", "build"]
10.3 Build caching
Cargo cache is the biggest win. In CI:
# GitHub Actions example
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
src-tauri/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
Hot incremental builds drop from 2–5 min to 10–30 sec.
Tip
Cache
~/.cargo/registry/andsrc-tauri/target/separately in CI. The registry cache is shared across all Rust projects; the target cache is project-specific. A warm target cache withincremental = true(dev profile only) cuts rebuild time to seconds.
10.4 Logs and diagnostics
| Source | How to capture |
|---|---|
| Rust app logs | Use tauri-plugin-log with file + stdout sinks. |
| CLI logs | RUST_LOG=tauri_cli=debug plus -vv. |
| Webview console | DevTools or tauri-plugin-log's webview sink. |
| Bundler errors | RUST_LOG=tauri_bundler=debug. |
| WRY/TAO errors | RUST_LOG=wry=debug,tao=debug. |
For production telemetry, integrate a crash reporter via the tauri-plugin-sentry community plugin or roll your own using std::panic::set_hook.
Note
The release profile sets
panic = "abort", which means panics terminate the process immediately —catch_unwindwill not work. If you need panic recovery for specific subsystems, run them in a separate thread withpanic = "unwind"configured via a custom profile.
10.5 Backup and config drift
tauri.conf.json is committed to your repo. The Cargo.lock should be too — pinning Tauri's dependency tree avoids surprise upgrades. The pnpm-lock.yaml (or package-lock.json, yarn.lock) similarly pins JS deps.
Capability files in src-tauri/capabilities/ are the security configuration — review them in PR like any other code.
10.6 Performance tuning
- Set
bundle.removeUnusedCommands = trueto strip unused IPC commands and shrink binaries. - Disable Cargo features you don't need:
tauri = { version = "2", default-features = false, features = ["wry"] }if you don't need x11 or dbus. - Use
release-size-optimizedprofile for size-critical builds; switch to defaultreleasefor speed-critical. - For frontend, use Vite/Rollup tree-shaking and code-splitting. Webview asset size matters because everything is embedded.
- Enable
compressionfeature (default) for smaller bundle sizes.
10.7 Upgrade procedure
# 1. Read the Tauri release notes.
# 2. Upgrade the CLI:
cargo install tauri-cli --version "^2.X.Y" --locked --force
# or
pnpm add -D @tauri-apps/cli@^2.X.Y
# 3. Upgrade the Rust crates:
cd src-tauri && cargo upgrade --workspace tauri tauri-build tauri-plugin
# 4. Upgrade the JS API:
pnpm add @tauri-apps/api@^2.X.Y
# 5. Upgrade plugins:
pnpm add @tauri-apps/plugin-fs@^2.X.Y @tauri-apps/plugin-http@^2.X.Y ...
# 6. Run `tauri info` and confirm versions match across Rust and JS.
# 7. Rebuild and smoke-test.
11. Internal Modules — Deep Dive
This section indexes every important module in crates/tauri/src/ for contributors and code-spelunkers.
11.1 app/
| File | Purpose |
|---|---|
app.rs |
App, AppHandle, Builder, lifecycle. The fan-out hub. |
plugin.rs |
The plugin event registration in the builder. |
The App::run() blocking call drives the event loop on the main thread. AppHandle is the cheap Send + Sync clone you pass into commands. Builder is the public API surface for everything: .plugin(), .invoke_handler(), .manage(), .menu(), .system_tray(), .setup(), .on_window_event(), etc.
11.2 manager/
The internal manager owns the runtime state: window list, webview list, plugin list, IPC authority, event listeners, resource table. It's the dispatch hub for everything that isn't a direct plugin call.
11.3 ipc/
Already covered in §2.4. Key point: RuntimeAuthority::resolve_access is the choke point for ACL enforcement.
11.4 webview/ and window/
Window is the OS window (TAO Window). Webview is the embedded webview (WRY WebView). WebviewWindow is the convenience type combining both for the common 1:1 case. The split exists for multi-webview windows (split-pane apps).
11.5 event/
Pub/sub. The Listener trait is implemented by App, AppHandle, Window, Webview. Events are routed through the manager based on the EventTarget (Any, App, specific window/webview/label).
11.6 path/
Cross-platform path resolution. BaseDirectory::AppData resolves to:
| Platform | Path |
|---|---|
| Linux | $XDG_DATA_HOME/<identifier> or $HOME/.local/share/<identifier> |
| macOS | $HOME/Library/Application Support/<identifier> |
| Windows | {FOLDERID_RoamingAppData}\<identifier> |
| Android | /data/data/<identifier>/files |
| iOS | <App Sandbox>/Library/Application Support/<identifier> |
11.7 protocol/
Custom URI handlers. Tauri ships:
tauri://localhost— main asset protocolasset://localhost/<path>— filesystem assets (gated byprotocol-assetfeature +assetProtocolconfig)ipc://localhost— IPC fallback protocol on some platforms
You can register your own via Builder::register_uri_scheme_protocol.
11.8 pattern/
Implements the brownfield (default) and isolation patterns. The brownfield pattern just injects the IPC bootstrap script. The isolation pattern wraps every IPC payload in a postMessage round-trip through an app-controlled iframe.
11.9 plugin/
The plugin runtime — distinct from tauri-plugin (the SDK). This is the loader, lifecycle dispatcher, and registry that the manager consults during IPC routing.
11.10 state/ and resources/
State<T> is a command parameter extractor for shared state managed via Builder::manage(value). ResourceTable is the typed handle table — plugins use it to give JS opaque IDs that map to Rust resources (file handles, network sockets, etc.) without exposing internals.
11.11 scope/
Defines FsScope, ShellScope, etc. These types implement the scope evaluation logic that ACL permissions use. Each plugin that supports scoping ships its scope type here.
11.12 process/
current_binary(), restart(), exit(). The restart logic spawns a new copy of the binary with the same args and exits the current one. On macOS it uses NSWorkspace; on Linux execv; on Windows CreateProcess.
11.13 tray/ (feature tray-icon)
System tray icon and menu. Cross-platform via TAO. macOS uses NSStatusItem; Windows uses Shell_NotifyIcon; Linux uses ayatana-appindicator3 (with X11) or the new XDG StatusNotifier protocol.
11.14 vibrancy/
macOS-only blur effect for transparent windows. Wraps NSVisualEffectView.
11.15 image/
Image type used by tray icons, menu icon items, and notifications. Decoders gated by image-ico, image-png features for size.
11.16 async_runtime/
Tokio runtime accessor. Plugins that need async dispatch get a handle here. The runtime is shared across the whole app.
11.17 error.rs
The crate's Error enum and Result alias. Used by every other module's public API.
12. Code Patterns and Conventions
12.1 Error handling
Every module returns crate::Result<T> (Result<T, Error>). Errors use thiserror::Error for derivation and propagate via ?. The CLI wraps errors with context using a custom Context trait in crates/tauri-cli/src/error.rs (similar to anyhow::Context).
12.2 Cross-platform cfg(...) discipline
Tauri uses the standard Rust #[cfg(target_os = "...")] and #[cfg(any(...))] attributes plus custom cfgs emitted by tauri-build:
| cfg | Set when |
|---|---|
desktop |
target_os = "macos" or "linux" or "windows" |
mobile |
target_os = "ios" or "android" |
These are emitted from tauri-build and used throughout core to gate menu/tray APIs (desktop only) and JNI bridges (mobile only).
12.3 Builder pattern everywhere
Builder types appear all over: tauri::Builder, WindowBuilder, WebviewBuilder, WebviewWindowBuilder, MenuBuilder, SubmenuBuilder, MenuItemBuilder, TrayIconBuilder, tauri_plugin::Builder. The convention is .field(value) chaining ending in .build() or .run().
12.4 Macro-driven codegen
Three macros define the user-facing magic:
#[tauri::command]— wraps a regular Rust fn into anInvokehandlertauri::generate_handler![fn1, fn2, ...]— produces the dispatch tabletauri::generate_context!()— embeds the config + assets + icons at compile time
All three live in tauri-macros and call into tauri-codegen.
12.5 Re-exports
tauri re-exports types from its dependencies for ergonomic access:
pub use http; // entire http crate
pub use url::Url;
pub use tauri_macros::{command, generate_handler, include_image};
pub use tauri_utils as utils;
User code often reaches for tauri::http::Method or tauri::Url rather than depending on those crates directly.
12.6 Feature flag layering
tauri has 25+ Cargo features. Conventions:
- Defaults are conservative for desktop (you opt out, not in).
- "Allowlist" features (now mostly removed — replaced by ACL) used to gate built-in commands.
- Features like
tray-icon,macos-private-api,isolationare auto-enabled bytauri-buildif the corresponding config option is set.
12.7 Workspace crate naming
All Tauri-published crates start with tauri- (tauri-build, tauri-runtime, tauri-bundler, etc.). The exception is the bare tauri crate. Plugins live in a separate workspace (tauri-apps/plugins-workspace) and are named tauri-plugin-<name>.
13. Testing
13.1 Test categories
| Category | Location | Run with |
|---|---|---|
| Unit | inline #[cfg(test)] modules |
cargo test --workspace |
| Integration | crates/tests/ |
cargo test -p tauri-tests-restart etc. |
| ACL fixtures | crates/tests/acl/fixtures/ |
cargo test -p tauri-tests-acl |
| Bundler snapshots | crates/tauri-cli/tests/fixtures/ |
cargo test -p tauri-cli |
| App-updater E2E | crates/tests/app-updater/ |
cargo test -p tauri-tests-app-updater |
| JS / TS unit | packages/api/__tests__/ |
pnpm test |
| CLI Node tests | packages/cli/__tests__/ |
pnpm --filter @tauri-apps/cli test |
| WebDriver E2E | (use tauri-driver) |
external Selenium/WebDriver client |
13.2 The test feature
Set tauri = { version = "2", features = ["test"] } as a dev-dependency to access:
tauri::test::mock_app()— build a fakeAppfor unit teststauri::test::MockRuntime— runs without a real webviewtauri::test::mock_invoke_handler— assert command dispatch
#[cfg(test)]
mod tests {
use tauri::test::{mock_app, mock_builder};
#[test]
fn greet_works() {
let app = mock_builder()
.invoke_handler(tauri::generate_handler![crate::greet])
.build(mock_context(noop_assets()))
.unwrap();
// ...
}
}
13.3 Bench
bench/ contains performance tests for cold-start time, memory, IPC throughput, and bundle size. Run with cargo bench --workspace.
14. Migrating from v1 to v2 — Operator Checklist
14.1 Pre-migration
- [ ] Tag the repo at the v1 head (
git tag v1-final). - [ ] Read the official v1 → v2 migration guide.
- [ ] Confirm Rust toolchain is 1.77.2 or newer.
- [ ] Audit your plugin list: are any third-party plugins not yet ported to v2?
- [ ] Decide which mobile targets (if any) to add — they're a v2-exclusive feature.
14.2 Run the migration
- [ ]
cargo install tauri-cli --version "^2.0.0" --locked - [ ]
cargo tauri migratefrom the project root. - [ ] Review the diff. Pay special attention to
Cargo.toml,package.json,tauri.conf.json, and every source file that imports from@tauri-apps/api/....
14.3 Manual fixups
- [ ] Replace every old-style allowlist with an explicit capability file. Run
tauri permission lsand confirm every command your app calls has a matching permission granted. - [ ] Replace
appWindowwithgetCurrentWindow()in JS. - [ ] Update plugin imports:
@tauri-apps/api/dialog→@tauri-apps/plugin-dialog, etc. - [ ] Add
tauri-plugin-*Rust dependencies for every plugin viatauri add <name>. - [ ] If using the updater, generate a new keypair (the v1 keypair format is incompatible).
- [ ] Update CI configuration:
tauri buildis unchanged but the env var requirements may have changed. - [ ] Run
cargo updateandpnpm install --frozen-lockfile=falseto refresh lockfiles.
14.4 Test matrix
- [ ]
tauri devworks on every platform you target. - [ ]
tauri buildproduces signed bundles on every platform. - [ ] Updater can fetch and apply an update from a staging server.
- [ ] Every IPC command in the app fires successfully (no ACL rejections in the console).
- [ ] No deprecated API calls in the JS console.
14.5 Operational rollout
- [ ] Ship a "migration release" to internal users first.
- [ ] Watch crash reports for the first week.
- [ ] Ship to public after one week of clean telemetry.
Warning
The v1 → v2 migration is the largest breaking change in Tauri's history. Even with
tauri migrate, expect 1–3 days of cleanup work for a non-trivial app.
15. Contributing
15.1 Where to send what
| What you want to do | Where it goes |
|---|---|
Bug in tauri core |
tauri-apps/tauri issues |
Bug in a plugin (fs, http, etc.) |
tauri-apps/plugins-workspace issues |
| Bug in the docs site | tauri-apps/tauri-docs issues |
Bug in create-tauri-app |
tauri-apps/create-tauri-app issues |
| Bug in WRY / TAO | tauri-apps/wry / tauri-apps/tao |
| Security vulnerability | GitHub Private Vulnerability Disclosure |
| Feature discussion | Discord #feature-requests |
15.2 Contribution flow
- Open an issue first (or comment on an existing one) so the team can confirm the approach.
- Fork, branch from
dev. - Run
cargo fmt && cargo clippy --workspace --all-features -- -D warnings. - Add changeset via
pnpm changeset(the repo uses Changesets for versioning). - Open a PR against
dev. - CI runs
cargo test --workspace,pnpm test,pnpm run build, and the supply-chain audit. - A maintainer reviews, requests changes if needed, and merges.
15.3 Maintainer notes
- The
devbranch is the integration target. - Releases are cut from
release/v2.Xbranches via the Changesets bot. - Versioning follows SemVer; breaking changes wait for a major bump.
- Patch releases ship within a week of identification for security issues.
- The project follows the Programme governance of The Commons Conservancy (see
governance/if present in your fork).
16. Improvements and Tech Debt
Read in conjunction with the open issue tracker. Specifics observed during this review:
-
Dependency RUSTSEC drift — multiple open advisories (#15243–#15249) need either dependency bumps or
[patch.crates-io]entries. The supply-chain story is excellent (cargo-vet is in use), but the cadence of advisory triage could be tightened. -
The
#2486Windows Defender false-positive — a five-year-old open issue that is largely outside Tauri's control, but the workaround docs (sign with EV cert) deserve more visibility in the install flow rather than only being mentioned in the FAQ. -
tauri-bundlerexternal tool downloads — NSIS, WiX, appimagetool are downloaded on first use. This is a supply-chain risk if the upstream URLs are compromised. Consider distributing checksummed binaries via the Tauri release channel itself. -
Schema regeneration friction —
src-tauri/gen/schemas/is regenerated at compile time but is .gitignored. This means devs without a working build can't get IDE completions on capability files. Consider committing a snapshot or adding atauri capability schemaCLI command. -
macOS notarization parallelism —
tauri buildnotarizes serially per format. Building both DMG and APP triggers two notarization runs. Could be parallelized. -
Mobile DX —
tauri android initwill install SDK + NDK on prompt, but the prompt fires in the middle of a long-running build. Consider an explicittauri doctorcommand that prepares the environment up-front. -
The
TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE— useful for users behind GitHub-blocking firewalls, but the format is not well-documented in either the CLI help ortauri.conf.json. A small docs improvement. -
Unified plugin permission discovery — currently each plugin's permissions are loaded by
tauri-buildfrom convention. Atauri permission discovercommand that lists all available permissions across installed plugins would help users without opening every plugin's source. -
Updater rollback — there's no built-in "downgrade" path. If a release goes wrong, users have to reinstall the previous version manually. A
--target-versionflag forupdate.downloadAndInstall()would help. -
No explicit
tauri doctor—tauri infois close but doesn't run any active checks. A doctor command that validates all toolchains, signing keys, and plugin compatibility could catch bad-state environments early.
17. Troubleshooting
17.1 Build failures
error[E0433]: failed to resolve: use of undeclared crate or module after upgrading Tauri
Run cargo update to refresh the lockfile. Tauri minor releases often bump internal crate versions, and a stale Cargo.lock will attempt to resolve old versions.
Library not found for -lWkWebView on macOS
The macOS SDK is missing or Xcode CLI tools are not installed. Run xcode-select --install and ensure Xcode.app is fully downloaded (not just the CLI tools stub).
cannot find -lwebkit2gtk-4.1 on Linux
Install libwebkit2gtk-4.1-dev (Debian/Ubuntu) or webkit2gtk4.1-devel (Fedora). Tauri v2 requires the 4.1 API; the old webkit2gtk-4.0 package from v1 will not work.
Warning
On Ubuntu 22.04,
libwebkit2gtk-4.1-devis available but may not match the runtime version on the target system. If your build host runs a newer WebKitGTK than your target, the app may crash at launch with symbol errors. Pin the build-host WebKitGTK version or build inside a container matching the target distro.
error: failed to run custom build command for tauri with TAURI_CONFIG set
The TAURI_CONFIG environment variable must contain valid JSON. A common mistake is setting it to a file path instead of inline JSON. Use --config path/to/override.json for file-based overrides.
17.2 Runtime errors
"runtime already initialized"
This panic fires if tauri::Builder::build() or .run() is called more than once. The Tokio runtime is global — you cannot build two Tauri apps in the same process. In tests, use tauri::test::mock_app() which shares the runtime.
"state not found for type MyState"
You called app.state::<MyState>() or used State<MyState> in a command, but never registered it with .manage(MyState::new()) on the Builder. Every State<T> extractor requires a corresponding .manage() call.
"command not allowed by ACL"
The IPC call was rejected by RuntimeAuthority. The window making the call does not have a capability granting the required permission. Check src-tauri/capabilities/*.json and ensure the command's permission (e.g., fs:allow-read-text-file) is listed under a capability that targets the calling window's label.
Caution
Do not "fix" ACL rejections by granting
"*"to all windows. This disables the security model entirely. Instead, create narrowly-scoped capabilities per window label, granting only the specific permissions each window needs.
17.3 Bundler failures
Windows NSIS: "makensis: command not found"
The NSIS tools download failed or the cache was corrupted. Delete ~/.cache/tauri/ (or %LOCALAPPDATA%\tauri\ on Windows) and rebuild. The CLI will re-download NSIS.
macOS notarization: "The signature of the binary is invalid"
The signing identity in bundle.macOS.signingIdentity does not match an installed certificate. Run security find-identity -v -p codesigning to list available identities. For CI, ensure the certificate is imported into the keychain before the build step.
AppImage: "FUSE is not installed"
AppImage requires FUSE to run. Install fuse or libfuse2 on the target system. Alternatively, extract with --appimage-extract for FUSE-less environments.
17.4 Mobile build failures
Android: "SDK location not found"
Set ANDROID_HOME to your Android SDK directory (e.g., ~/Android/Sdk or %LOCALAPPDATA%\Android\Sdk). The CLI also accepts ANDROID_SDK_ROOT.
iOS: "No signing certificate found"
Run tauri ios build with --export-method development for local testing. Production builds require a valid Apple Developer certificate configured in Xcode.
17.5 Performance issues
Large binary size (>20 MB)
Check that the release profile uses opt-level = "s" or "z", lto = true, strip = true, and codegen-units = 1. Disable unused Cargo features (e.g., image-ico, image-png if you don't use programmatic images). Enable the compression feature (default on) for embedded asset compression.
Slow cold start on Windows
WebView2 initialization on first launch can take 1–3 seconds while the runtime initializes. Subsequent launches are faster. Use visible: false in the window config and show the window in setup() after initialization to avoid a blank window flash.
18. Quick Reference
18.1 Filesystem layout of a built app
| File | Where |
|---|---|
src-tauri/Cargo.toml |
Rust dependencies. |
src-tauri/Cargo.lock |
Locked Rust versions. Commit it. |
src-tauri/tauri.conf.json |
App configuration. |
src-tauri/build.rs |
Build script. |
src-tauri/src/main.rs |
Binary entry point. |
src-tauri/src/lib.rs |
App library. |
src-tauri/icons/ |
Generated icons. |
src-tauri/capabilities/*.json |
ACL capabilities. |
src-tauri/gen/ |
Generated code (mobile, schemas). gitignored. |
src-tauri/target/ |
Cargo build output. gitignored. |
src-tauri/target/release/bundle/<format>/ |
Built bundles. |
18.2 Important env vars
(See User Guide §21.3 for the full list.)
18.3 Important config keys
identifier— never change after releaseversion— drives bundling and updaterbuild.frontendDist/build.devUrl— wire the frontendapp.security.csp— CSPapp.security.pattern.use—brownfieldorisolationbundle.targets— restrict formatsbundle.createUpdaterArtifacts— produce updater filesplugins.updater.endpoints— updater manifest URLplugins.updater.pubkey— updater public key
18.4 Important commands cheat sheet
| Command | When to use |
|---|---|
tauri dev |
Daily development. |
tauri build |
Every release. |
tauri info |
First step of every bug report. |
tauri add <plugin> |
Adding new functionality. |
tauri permission ls |
Auditing what your app can do. |
tauri signer generate |
Once, when setting up the updater. |
tauri migrate |
One time, when upgrading from v1. |
tauri icon |
When you change branding. |
tauri inspect wix-upgrade-code |
When debugging Windows MSI upgrades. |
18.5 External resources
- Source: https://github.com/tauri-apps/tauri
- WRY (webview): https://github.com/tauri-apps/wry
- TAO (windowing): https://github.com/tauri-apps/tao
- Plugins workspace: https://github.com/tauri-apps/plugins-workspace
- Docs site: https://v2.tauri.app/
- Awesome list: https://github.com/tauri-apps/awesome-tauri
- Discord: https://discord.com/invite/tauri
- Funding: https://opencollective.com/tauri
- Schema reference: https://schema.tauri.app/config/v2