Jesse A. Green / notebook

Tauri Admin Guide

Generated May 2026 from tauri-apps/tauri at df3fb97

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 from release/* branches. If you fork for a downstream distribution, base on a release tag, not on dev.


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:

  1. Parses tauri.conf.json at build time and emits cargo rustc-cfg flags
  2. Generates the codegen context (icon embedding, config struct, asset map)
  3. On mobile, generates the JNI bindings and Swift bridge
  4. 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-wry IPC handler intercepts and serves the embedded asset
  • No localhost HTTP server runs (that's the tauri-plugin-localhost opt-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):

  1. Base tauri.conf.json (or Tauri.toml with config-toml feature, or tauri.conf.json5 with config-json5)
  2. Platform-specific file (tauri.linux.conf.json, tauri.macos.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json)
  3. --config <FILE> arguments (one or more, merged in order)
  4. --config '<JSON>' inline arguments
  5. TAURI_CONFIG environment 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 identifier field 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:

  1. Default-deny everything. A fresh app with no capabilities can call zero IPC commands.
  2. Scope per command. Each command, in core or plugin, gets its own permission name.
  3. Bind to windows. A capability lists which window labels it applies to.
  4. Carry a payload. Permissions can include allow/deny scope objects (file paths, URL globs, command patterns).
  5. 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:

  1. Validates every permission identifier exists
  2. Resolves <plugin>:default to the plugin's [default] bundle
  3. Computes a per-window allowlist
  4. Generates a Rust RuntimeAuthority populated at app.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 capabilities
  • mobile-schema.json — for mobile capabilities
  • linux-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

  1. Store TAURI_SIGNING_PRIVATE_KEY in CI secrets, never in the repo.
  2. Use a static-hosting CDN for update artifacts (S3 + CloudFront, R2, etc.).
  3. Use immutable filenames per release (don't overwrite).
  4. Use a versioned manifest filename (manifest-1.2.0.json) and bump endpoints carefully — or use a version-aware proxy.
  5. Test updates in a staging environment before pushing the manifest.
  6. 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:

  1. Default-deny IPC via the ACL — a compromised frontend can only call commands explicitly granted to it.
  2. Custom protocol, not localhost HTTP — no port for other apps to scan, no CORS confusion.
  3. CSP restricting external resource loads.
  4. Isolation pattern for sensitive apps — every IPC payload passes through an app-controlled iframe before reaching Rust.
  5. Updater signature verification — only releases signed by the developer are accepted.
  6. Code-signed bundles — Gatekeeper / SmartScreen / packagekit verify provenance.

9.2 Known unsafes and gotchas

  • dangerousDisableAssetCspModification: true disables Tauri's automatic nonce injection. Only set if you serve a pre-built CSP and know what you're doing.
  • security.dangerousUseHttpScheme uses plain HTTP for the IPC protocol; only allowed for legacy migration.
  • shell:allow-execute with a wildcard args regex is a remote code execution waiting to happen if any window can reach it.
  • http plugin without scope = anything the network can reach is reachable from JS. Always scope.
  • fs plugin's BaseDirectory.Home ($HOME/**) is broader than most apps need. Prefer BaseDirectory.AppData or BaseDirectory.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:

  1. Subscribe to the Tauri release feed.
  2. Run cargo audit in CI on every build.
  3. Pin to known-good Tauri minor versions; bump on patch releases promptly.
  4. Use cargo-vet (Tauri's own choice — see supply-chain/ and audits/) 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/ and src-tauri/target/ separately in CI. The registry cache is shared across all Rust projects; the target cache is project-specific. A warm target cache with incremental = 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_unwind will not work. If you need panic recovery for specific subsystems, run them in a separate thread with panic = "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 = true to 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-optimized profile for size-critical builds; switch to default release for speed-critical.
  • For frontend, use Vite/Rollup tree-shaking and code-splitting. Webview asset size matters because everything is embedded.
  • Enable compression feature (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 protocol
  • asset://localhost/<path> — filesystem assets (gated by protocol-asset feature + assetProtocol config)
  • 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 an Invoke handler
  • tauri::generate_handler![fn1, fn2, ...] — produces the dispatch table
  • tauri::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, isolation are auto-enabled by tauri-build if 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 fake App for unit tests
  • tauri::test::MockRuntime — runs without a real webview
  • tauri::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 migrate from 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 ls and confirm every command your app calls has a matching permission granted.
  • [ ] Replace appWindow with getCurrentWindow() in JS.
  • [ ] Update plugin imports: @tauri-apps/api/dialog → @tauri-apps/plugin-dialog, etc.
  • [ ] Add tauri-plugin-* Rust dependencies for every plugin via tauri add <name>.
  • [ ] If using the updater, generate a new keypair (the v1 keypair format is incompatible).
  • [ ] Update CI configuration: tauri build is unchanged but the env var requirements may have changed.
  • [ ] Run cargo update and pnpm install --frozen-lockfile=false to refresh lockfiles.

14.4 Test matrix

  • [ ] tauri dev works on every platform you target.
  • [ ] tauri build produces 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

  1. Open an issue first (or comment on an existing one) so the team can confirm the approach.
  2. Fork, branch from dev.
  3. Run cargo fmt && cargo clippy --workspace --all-features -- -D warnings.
  4. Add changeset via pnpm changeset (the repo uses Changesets for versioning).
  5. Open a PR against dev.
  6. CI runs cargo test --workspace, pnpm test, pnpm run build, and the supply-chain audit.
  7. A maintainer reviews, requests changes if needed, and merges.

15.3 Maintainer notes

  • The dev branch is the integration target.
  • Releases are cut from release/v2.X branches 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 #2486 Windows 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-bundler external 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 a tauri capability schema CLI command.

  • macOS notarization parallelism — tauri build notarizes serially per format. Building both DMG and APP triggers two notarization runs. Could be parallelized.

  • Mobile DX — tauri android init will install SDK + NDK on prompt, but the prompt fires in the middle of a long-running build. Consider an explicit tauri doctor command 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 or tauri.conf.json. A small docs improvement.

  • Unified plugin permission discovery — currently each plugin's permissions are loaded by tauri-build from convention. A tauri permission discover command 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-version flag for update.downloadAndInstall() would help.

  • No explicit tauri doctor — tauri info is 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-dev is 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 release
  • version — drives bundling and updater
  • build.frontendDist / build.devUrl — wire the frontend
  • app.security.csp — CSP
  • app.security.pattern.use — brownfield or isolation
  • bundle.targets — restrict formats
  • bundle.createUpdaterArtifacts — produce updater files
  • plugins.updater.endpoints — updater manifest URL
  • plugins.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