| name | tauri-expert |
| description | Tauri expert. Covers Tauri 2 desktop app authoring — tauri.conf.json schema, generate_context!() codegen behavior, build vs bundle phase distinction, capabilities v2, cross-platform icon pipeline, sidecar/externalBin mechanism with Rust target triples, official plugin ecosystem, frontend integration (Vite, Next.js static export, SvelteKit), Tauri CLI, GitHub Actions 3-OS matrix, and code signing. Use when questions involve tauri.conf.json, cargo tauri build, Tauri plugins, sidecars, or Tauri CI. |
| paths | ["**/tauri.conf.json","**/src-tauri/**"] |
Tauri Expert
Read-only reference for Tauri 2 desktop application guidance — configuration schema, build phases, capabilities, packaging, sidecars, plugins, frontend integration, CLI, and CI patterns.
Overview
Tauri 2 is a Rust-based framework for building desktop (and mobile) applications with a WebView frontend. The architecture is a Rust core process hosting a system WebView (WebKit on macOS/Linux, WebView2 on Windows) with frontend assets either embedded at compile time or served from a dev URL. v2 introduced a capability-based security model (replacing v1's allowlist), GTK 4.1 on Linux, and a unified plugin ecosystem under tauri-apps/plugins-workspace. This skill covers v2 only — v1 is end-of-life.
The project (tauri-apps/tauri) is Active: v2.11.0 released April 30, 2025, coordinated multi-crate releases, daily commit cadence, multi-contributor. Risk: Low. The plugin workspace tracks the same release cadence with per-plugin minor versions in the 2.3–2.5.x range.
Configuration (tauri.conf.json)
The configuration file lives at src-tauri/tauri.conf.json. The $schema URL is version-pinned: https://schema.tauri.app/config/<semver> (e.g., https://schema.tauri.app/config/2.11.0). The schema is also the canonical reference at schema.tauri.app/config/2.
Schema overview
| Top-level key | Required | Purpose |
|---|
identifier | Yes | Reverse-domain app ID (com.example.myapp). The only required key. |
productName | No | Human-readable app name. |
version | No | SemVer string, or path to a package.json with a version field. |
mainBinaryName | No | Override the binary filename. |
app | No | Runtime config — windows, security/CSP, capabilities, tray. Replaces the v1 tauri top-level key. |
build | No | Dev/build pipeline — frontend dist path, dev URL, before-commands. |
bundle | No | Bundler config — installer formats, icons, signing, externalBin, resources. |
plugins | No | Per-plugin configuration keyed by plugin name. |
v1 → v2 rename table
| v1 path | v2 path | Notes |
|---|
tauri (top-level) | app | Entire runtime section renamed |
tauri.allowlist.* | src-tauri/capabilities/*.json | Allowlist replaced by capability files |
tauri.bundle.identifier | identifier (top-level) | Promoted |
package.productName | productName (top-level) | Promoted |
package.version | version (top-level) | Promoted |
build.distDir | build.frontendDist | Now accepts paths or URLs |
build.devPath | build.devUrl | Renamed for clarity (it must be a URI) |
tauri.bundle.dmg | bundle.macOS.dmg | Platform-specific nesting |
tauri.bundle.deb | bundle.linux.deb | Platform-specific nesting |
cargo tauri migrate performs these renames automatically and converts the v1 allowlist into v2 capability files. Run it on the unmodified v1 project before manual cleanup.
build
| Key | Type | Effect |
|---|
build.beforeDevCommand | string | Shell command run before tauri dev. Typically starts the frontend dev server. |
build.beforeBuildCommand | string | Shell command run before tauri build. Typically builds the frontend assets. |
build.beforeBundleCommand | string | Shell command run between Rust compile and bundling. |
build.devUrl | URL | Frontend dev server URL. Tauri opens this in the WebView during tauri dev. |
build.frontendDist | path or URL | Built frontend assets directory. Read by generate_context!() at compile time, not just at bundle time. |
bundle
| Key | Type | Default | Effect |
|---|
bundle.active | bool | false | Gates the bundler phase only. Does NOT gate generate_context!() or icon reading. |
bundle.targets | string | array | "all" | Bundle formats to produce. Platform-scoped (see Cross-Platform Packaging). |
bundle.icon | string[] | [] | Icon paths. Read at every Cargo build, not just bundle time. |
bundle.externalBin | string[] | [] | Sidecar binary path prefixes. Tauri appends the host triple at build time. |
bundle.resources | string[] | [] | Files copied into the bundle alongside the binary. |
bundle.createUpdaterArtifacts | bool | false | Produce signed updater artifacts. Requires TAURI_SIGNING_PRIVATE_KEY. |
Trap — bundle.active = false does not skip icon reading. Icons are still loaded by the proc macro at compile time. See Build and Bundle Phases.
app
| Key | Type | Effect |
|---|
app.windows[] | array | Window configurations created at startup. Each entry is a WindowConfig. |
app.security.csp | string | object | null | Content Security Policy. null disables CSP entirely — there is no implicit default. |
app.security.capabilities | string[] | When set, ONLY listed capabilities are loaded. Auto-load of capabilities/*.json is disabled. |
app.security.dangerousDisableAssetCspModification | bool | Bypass Tauri's nonce/hash injection. Use with extreme care. |
app.trayIcon | object | System tray icon configuration. iconPath is read at compile time. |
app.windows[] minimum useful shape:
{
"label": "main",
"title": "My App",
"width": 1024,
"height": 768,
"resizable": true
}
Trap — empty app.security.csp is not safe-by-default. When unset or null, no CSP is injected. You must explicitly configure CSP and include connect-src "ipc: http://ipc.localhost" for IPC to function.
Platform-specific config merging
Split per-OS overrides into tauri.linux.conf.json, tauri.windows.conf.json, tauri.macos.conf.json (and .android./.ios. for mobile). Merge follows JSON Merge Patch (RFC 7396) — platform values override base values key-by-key.
Build and Bundle Phases
The single most consequential concept in Tauri: configuration keys do not all run at the same phase. Misunderstanding this produces opaque proc-macro panics during cargo clippy.
| Phase | What runs | Reads at this phase | Does NOT run |
|---|
cargo check | Type-check + proc-macro expansion | tauri.conf.json, icons, frontendDist, capabilities, CSP injection | beforeDev/beforeBuild commands, bundler |
cargo build / cargo build --release | Full Rust compilation | Same as cargo check | beforeDev/beforeBuild commands, bundler |
cargo clippy | Lints + proc-macro expansion | Same as cargo check | beforeDev/beforeBuild commands, bundler |
tauri dev | beforeDevCommand → wait for devUrl → cargo build (debug) → run binary → watch | All Cargo-build inputs + dev server | bundler |
tauri build | beforeBuildCommand → cargo build --release → beforeBundleCommand → bundler (if bundle.active) | All Cargo-build inputs + bundle target inputs | — |
tauri build --no-bundle | Same as above without the bundler phase | Cargo-build inputs only | bundler |
tauri bundle | beforeBundleCommand → bundler only (does not recompile) | Already-compiled binary, bundle target inputs | Rust compilation |
generate_context!() codegen
tauri::generate_context!() is a proc macro in tauri-macros that delegates to tauri-codegen::context_codegen(). At every Cargo invocation it:
- Reads and parses
tauri.conf.json
- Reads
frontendDist (when a path) and embeds the assets in the binary
- Reads icons from
bundle.icon or default paths and embeds the window icon
- Reads tray icon from
app.trayIcon.iconPath if configured
- Parses
src-tauri/capabilities/*.json and embeds the resolved capability set
- Injects CSP nonces and content hashes for embedded assets
Trap — cargo clippy panics on missing icons. When bundle.icon is empty or absent, generate_context!() falls back to platform-default paths: icons/icon.ico (Windows target), icons/icon.icns (macOS dev), icons/icon.png (other). If the file does not exist, the proc macro panics with failed to open icon ... No such file or directory. This fires during cargo clippy because clippy expands proc macros. The official docs page says icons are needed "at bundle time, not compile time" — this is incorrect; the source code is authoritative. Tracked in tauri-apps/tauri discussion #14355.
Resolution paths:
- Run
cargo tauri icon <source.png> to generate the full icon set.
- Commit a placeholder
src-tauri/icons/icon.png (and .ico on Windows-targeting CI).
- Generate icons in CI before
cargo clippy runs.
There is no config-only workaround.
Other proc macros
| Macro | Purpose |
|---|
#[tauri::command] | Marks a Rust function as IPC-callable; generates serialization glue. |
tauri::generate_handler![cmd_a, cmd_b] | Registers #[tauri::command] functions into the invoke handler. Pass to Builder::invoke_handler. |
tauri::Manager is a trait imported with use tauri::Manager, not a derive macro.
Capabilities
v2 replaces the v1 allowlist with capability files in src-tauri/capabilities/. Each file declares which permissions apply to which windows on which platforms.
Capability file shape
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Default capability for the main window",
"windows": ["main"],
"platforms": ["linux", "macOS", "windows"],
"permissions": [
"core:default",
"fs:allow-read-file",
"shell:allow-execute"
]
}
The $schema path points to a generated file at src-tauri/gen/schemas/desktop-schema.json that does not exist until the first cargo tauri build or cargo tauri dev. IDE schema validation is broken in fresh checkouts until first build.
Auto-load vs explicit reference
Default behavior: every JSON/TOML file under src-tauri/capabilities/ is automatically loaded.
Trap — explicit reference disables auto-load globally. Once any capability is listed in app.security.capabilities, ONLY listed capabilities are used; all other files in the directory are ignored. There is no "merge with auto-load" mode. Either rely on auto-load entirely, or list every capability you want active.
Permission identifier format
Format: <plugin>:<permission>. The tauri-plugin- prefix is stripped — use the short name:
core:default, core:window:allow-close, core:event:allow-listen
fs:allow-read-file, shell:allow-execute, dialog:allow-open
Permission names follow allow-<command> (grant) or deny-<command> (block, overrides allows). Permission sets use names without the allow-/deny- prefix (e.g., core:default, core:window:default).
core:default composition
core:default is a permission set that bundles the default sets of the core sub-plugins:
core:app:default, core:event:default, core:image:default, core:menu:default, core:path:default, core:resources:default, core:tray:default, core:webview:default, core:window:default
Trap — core:window:default excludes mutating operations. It grants read-only window queries (is-visible, is-maximized, inner-size, etc.) but NOT allow-close, allow-minimize, allow-maximize, allow-set-size, allow-set-title, allow-show, allow-hide. Programmatic window control from JS requires explicit grants.
Scoped permissions
Filesystem and URL permissions accept scope restrictions:
{
"identifier": "fs:allow-read-file",
"allow": [{ "path": "$HOME/**" }]
}
Scope variables: $HOME, $APPDATA, $RESOURCE, $TEMP, $DESKTOP, $DOCUMENT, $DOWNLOAD, $EXE, $LOG. Wildcards: * (single segment), ** (recursive).
Cross-Platform Packaging
Bundle types per platform
| Platform | Available targets |
|---|
| Linux | deb, rpm, appimage |
| macOS | dmg, app |
| Windows | nsis (recommended), msi (WiX-based) |
Specify via bundle.targets or CLI flag: tauri build --bundles deb,appimage.
Icon pipeline
All icons live in src-tauri/icons/. Generate the full set from a single 1024×1024 PNG:
cargo tauri icon ./app-icon.png
| File | Platform | Purpose |
|---|
icon.png | All Linux/macOS | Required by generate_context!() for non-Windows targets |
icon.ico | Windows | Required by generate_context!() for Windows targets; multi-layer (16/24/32/48/64/256) |
icon.icns | macOS bundle | Required at bundle time for .app/.dmg |
32x32.png, 128x128.png, 128x128@2x.png | Linux | Desktop integration sizes |
Square*.png, StoreLogo.png | Windows Store | AppX targets (currently unused) |
PNG inputs must be square, RGBA (not indexed), 32-bit per pixel.
Compile-time vs bundle-time icon needs:
- Compile time: the icon for the current Cargo target triple (
icon.png on Linux/macOS targets, icon.ico on Windows targets) must exist.
- Bundle time: the platform-specific icon for each
bundle.targets entry must exist (e.g., .icns for macOS bundles).
Mobile icons live elsewhere. Android: src-tauri/gen/android/app/src/main/res/. iOS: src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/. These directories are created by cargo tauri android init / cargo tauri ios init.
Sidecar and External Binaries
Sidecars are non-Rust binaries (commonly Go, Python, .NET) shipped alongside the Tauri app and invoked via the shell plugin.
Declaration
{
"bundle": {
"externalBin": ["binaries/my-sidecar"]
}
}
Paths are relative to src-tauri/. Each entry is a path prefix — Tauri appends the host's Rust target triple and .exe (Windows only).
Naming convention
For "binaries/my-sidecar", Tauri expects:
| Host platform | Required filename |
|---|
| Windows x64 | binaries/my-sidecar-x86_64-pc-windows-msvc.exe |
| Windows ARM64 | binaries/my-sidecar-aarch64-pc-windows-msvc.exe |
| macOS Intel | binaries/my-sidecar-x86_64-apple-darwin |
| macOS Apple Silicon | binaries/my-sidecar-aarch64-apple-darwin |
| Linux x64 | binaries/my-sidecar-x86_64-unknown-linux-gnu |
| Linux ARM64 | binaries/my-sidecar-aarch64-unknown-linux-gnu |
Detect the host triple in CI:
rustc --print host-tuple
rustc -Vv | awk '/^host:/ {print $2}'
Trap — Windows .exe is not optional. A binary named my-sidecar-x86_64-pc-windows-msvc (no extension) on Windows fails the build with file-not-found.
Trap — universal macOS binaries are not supported for sidecars. Ship separate x86_64-apple-darwin and aarch64-apple-darwin binaries; lipo-merged fat binaries are rejected because Tauri matches by filename.
.NET RID → Rust triple mapping
For .NET sidecars, publish per RID then rename:
| .NET RID | Rust target triple | Notes |
|---|
win-x64 | x86_64-pc-windows-msvc | Output has .exe |
win-arm64 | aarch64-pc-windows-msvc | Output has .exe |
osx-x64 | x86_64-apple-darwin | No extension |
osx-arm64 | aarch64-apple-darwin | No extension |
linux-x64 | x86_64-unknown-linux-gnu | Dynamically linked (glibc) — build host glibc must be ≤ target glibc |
linux-arm64 | aarch64-unknown-linux-gnu | Dynamically linked (glibc) |
linux-musl-x64 | x86_64-unknown-linux-musl | Statically linked — preferred for portability |
linux-musl-arm64 | aarch64-unknown-linux-musl | Statically linked |
Recommended .NET publish flags for a single-file sidecar:
dotnet publish -r linux-x64 \
--self-contained true \
-p:PublishSingleFile=true \
-p:PublishTrimmed=true \
-c Release \
-o ./publish/linux-x64
PublishAot is more compact but requires AOT-compatible code throughout the dependency graph — unsuitable for most library-heavy sidecars. For .NET project layout, dependency choices, and AOT compatibility, consult dotnet-expert.
Rename pattern:
TRIPLE=$(rustc --print host-tuple)
cp ./publish/linux-x64/MySidecar "src-tauri/binaries/my-sidecar-${TRIPLE}"
Rust API
use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
let sidecar = app.shell().sidecar("binaries/my-sidecar").unwrap();
let (mut rx, mut child) = sidecar.args(["--flag", "value"]).spawn().unwrap();
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => { }
CommandEvent::Stderr(line) => { }
CommandEvent::Terminated(_) => break,
_ => {}
}
}
});
The argument to .sidecar() is the externalBin path prefix, not the triple-suffixed filename. The legacy tauri::api::process::Command is removed in v2.
JavaScript API
import { Command } from '@tauri-apps/plugin-shell';
const output = await Command.sidecar('binaries/my-sidecar').execute();
const cmd = Command.sidecar('binaries/my-sidecar', ['--arg']);
cmd.stdout.on('data', line => console.log(line));
const child = await cmd.spawn();
Capability requirements
{
"permissions": [
"core:default",
{
"identifier": "shell:allow-execute",
"allow": [
{ "name": "binaries/my-sidecar", "sidecar": true }
]
}
]
}
Use shell:allow-spawn for streaming. Arguments must be declared (or matched via regex validators) — undeclared args are rejected at runtime. shell:default does NOT grant sidecar execute.
Frontend Integration
build keys recap
{
"build": {
"beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm build",
"devUrl": "http://localhost:5173",
"frontendDist": "../dist"
}
}
frontendDist accepts a path (embedded at compile time) or a URL (loaded at runtime).
devUrl is consumed only by tauri dev.
Production serving
Tauri serves embedded assets via a custom protocol in production:
| Platform | Scheme |
|---|
| macOS, Linux | tauri://localhost |
| Windows | http://tauri.localhost |
Trap — Windows changed scheme between v1 and v2. v1 used https://tauri.localhost; v2 uses http://tauri.localhost. This resets IndexedDB, LocalStorage, and cookies for users upgrading from v1 apps. Set app.windows[].useHttpsScheme: true to preserve v1 storage.
Framework configurations
Vite (recommended for SPA):
{
"build": {
"beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm build",
"devUrl": "http://localhost:5173",
"frontendDist": "../dist"
}
}
Add server.strictPort: true in vite.config.ts so Vite fails fast if 5173 is taken. Set build.target conditionally using process.env.TAURI_ENV_PLATFORM: chrome105 for Windows (WebView2), safari13 for macOS/Linux (WebKit).
Next.js (static export only):
{ "build": { "frontendDist": "../out", "devUrl": "http://localhost:3000" } }
next.config.mjs requires output: 'export' and images: { unoptimized: true }. SSR, API routes, and middleware are incompatible — they require a Node.js runtime that does not exist in production Tauri.
SvelteKit (static adapter):
{ "build": { "frontendDist": "../build", "devUrl": "http://localhost:5173" } }
Use @sveltejs/adapter-static with adapter({ fallback: 'index.html' }). Add a root +layout.ts with export const ssr = false;.
SPA mode is the path of least resistance. Any framework feature requiring a server (SSR, API routes, middleware) must be replaced with Tauri commands and plugins.
WebView differences
WebKit (macOS/Linux) lags Chromium (Windows WebView2) on CSS and JS feature support. Test on all three platforms — features that work on Windows can silently fail on macOS/Linux.
tauri-plugin-localhost is a security risk
tauri-plugin-localhost serves assets on a real http://localhost:<port> HTTP server. Any process on the machine can connect. Use only when a tool genuinely requires a real HTTP origin; the custom protocol is preferred.
Plugin Ecosystem
All v2 plugins live at tauri-apps/plugins-workspace. Per-plugin minor versions in the 2.3–2.5.x family. Liveliness: Active, Risk: Low.
Installation pattern
cargo tauri plugin add <name>
cargo add tauri-plugin-<name>
pnpm add @tauri-apps/plugin-<name>
In src-tauri/src/lib.rs:
tauri::Builder::default()
.plugin(tauri_plugin_<name>::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
Then grant permissions in src-tauri/capabilities/default.json.
Cross-platform plugins
| Plugin | Crate | Purpose |
|---|
shell | tauri-plugin-shell | Subprocess execution, sidecar management, open. Required for sidecars. |
fs | tauri-plugin-fs | File system read/write/watch. |
dialog | tauri-plugin-dialog | Native file-open/save and message boxes. |
http | tauri-plugin-http | Rust-backed HTTP client (bypasses CORS). |
notification | tauri-plugin-notification | Native OS notifications. |
clipboard-manager | tauri-plugin-clipboard-manager | System clipboard read/write. |
os | tauri-plugin-os | Query OS type, version, locale, hostname. |
process | tauri-plugin-process | exit(), relaunch(). |
store | tauri-plugin-store | Persistent JSON key-value store. |
updater | tauri-plugin-updater | In-app auto-update with signature verification. |
window-state | tauri-plugin-window-state | Persist and restore window size/position. |
log | tauri-plugin-log | Structured logging via the log façade. |
deep-link | tauri-plugin-deep-link | Custom URL scheme handler. |
single-instance | tauri-plugin-single-instance | Enforce one running instance. |
positioner | tauri-plugin-positioner | Move windows to named positions. |
opener | tauri-plugin-opener | Open files/URLs with the OS default handler. |
cli | tauri-plugin-cli | Parse args defined in tauri.conf.json. |
localhost | tauri-plugin-localhost | Serve via http://localhost. Security risk — see Frontend Integration. |
persisted-scope | tauri-plugin-persisted-scope | Persist runtime FS scope grants. |
sql | tauri-plugin-sql | sqlx-backed SQLite/MySQL/PostgreSQL. Desktop only currently. |
stronghold | tauri-plugin-stronghold | Encrypted secure key-value store. |
upload | tauri-plugin-upload | Multipart HTTP file upload. |
websocket | tauri-plugin-websocket | WebSocket client. |
Desktop-only plugins
global-shortcut, autostart.
Mobile-only plugins
barcode-scanner, biometric, geolocation, haptics, nfc.
Trap — JS package and Rust crate must come from compatible releases. IPC message format can change between minor versions. Always update both together. cargo tauri plugin add does this automatically.
Trap — shell:default does not grant sidecar execute. It permits open (URLs) only. Add shell:allow-execute or shell:allow-spawn with the sidecar entry explicitly.
CLI Reference
The CLI is invoked as cargo tauri <command> (after cargo install tauri-cli --version "^2.0") or pnpm tauri <command> (via @tauri-apps/cli devDependency). The pnpm form is recommended for reproducible CI.
| Command | Purpose |
|---|
tauri init | Scaffold src-tauri/ in an existing frontend project. Use --ci for non-interactive mode. |
tauri dev | Start dev mode — runs beforeDevCommand, waits for devUrl, compiles, launches with hot-reload. |
tauri build | Compile release binary and generate platform bundles. |
tauri build --debug | Release bundle with debug symbols. |
tauri build --no-bundle | Compile binary only, skip installer generation. |
tauri build --bundles <types> | Generate only specified bundle types (comma-separated). |
tauri info | Print environment diagnostics — always request first in a bug report. |
tauri icon <source.png> | Generate full icon set from a 1024×1024 source. |
tauri migrate | Automated v1 → v2 migration. |
tauri plugin add <name> | Install matched Rust crate and JS package versions. |
tauri signer generate -w <path> | Generate updater signing key pair. |
tauri bundle | Bundle an already-compiled release binary without recompiling. |
tauri info reports OS/arch, Node/Rust versions, CLI versions, WebView2 version (Windows), Xcode (macOS), and project Tauri versions from Cargo.lock. Version skew between @tauri-apps/api (JS) and the tauri crate is a common source of IPC breakage and is the first thing to check.
CI and Code Signing
GitHub Actions is the canonical CI surface. The official tauri-apps/tauri-action@v0 wraps the build, signing, and release-creation steps.
3-OS matrix
name: publish
on:
push:
branches: [release]
jobs:
publish-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
args: --target aarch64-apple-darwin
- platform: macos-latest
args: --target x86_64-apple-darwin
- platform: ubuntu-22.04
args: ''
- platform: ubuntu-22.04-arm
args: ''
- platform: windows-latest
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: pnpm
- uses: dtolnay/rust-toolchain@stable
with:
targets: >-
${{ matrix.platform == 'macos-latest'
&& 'aarch64-apple-darwin,x86_64-apple-darwin'
|| '' }}
- uses: swatinem/rust-cache@v2
with:
workspaces: ./src-tauri -> target
- name: install Linux deps
if: startsWith(matrix.platform, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev
- run: pnpm install --frozen-lockfile
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tagName: app-v__VERSION__
releaseName: App v__VERSION__
releaseDraft: true
args: ${{ matrix.args }}
Linux system dependencies
Tauri 2 uses GTK 4.1 — libwebkit2gtk-4.1-dev, not 4.0. The 4.0 package is absent on Ubuntu 24.04 and Debian 13 (Trixie). The Ayatana fork (libayatana-appindicator3-dev) is the actively maintained appindicator package; the legacy libappindicator3-dev is unmaintained and absent on Debian 13. libgtk-3-dev is pulled in transitively and need not be listed.
| Package | Ubuntu 22.04 | Ubuntu 24.04 | Debian 13 |
|---|
libwebkit2gtk-4.1-dev | Yes | Yes | Yes |
libwebkit2gtk-4.0-dev | Yes (v1 only) | No | No |
libayatana-appindicator3-dev | Yes | Yes | Yes |
libappindicator3-dev | Virtual (→ ayatana) | No | No |
librsvg2-dev, libxdo-dev, patchelf | Yes | Yes | Yes |
Cross-compilation guidance
| Target | Approach |
|---|
| macOS universal | --target universal-apple-darwin on macos-latest, both targets installed via dtolnay/rust-toolchain. ~2× build time. |
| Linux ARM64 | Native ubuntu-22.04-arm runner (free for public repos). Cross-compile from x86_64 is strongly discouraged — webkit/appindicator headers are not packaged for cross-builds. |
| Windows ARM64 | aarch64-pc-windows-msvc target on windows-latest. tauri-action lacks formal support (issue #952); invoke cargo tauri build directly. NSIS installer runs under x86 emulation; the binary is native ARM64. |
| Linux from non-Linux | Don't. Use a native Linux runner. |
Code signing
| Platform | Required env vars |
|---|
| macOS | APPLE_CERTIFICATE (base64 .p12), APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY, APPLE_ID, APPLE_PASSWORD (app-specific), APPLE_TEAM_ID |
| Windows (PFX) | WINDOWS_CERTIFICATE (base64 .pfx), WINDOWS_CERTIFICATE_PASSWORD |
| Windows (Azure Trusted Signing) | AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID with trusted-signing-cli. Recommended modern approach. |
| Linux | RPM signing via TAURI_SIGNING_RPM_KEY (ASCII-armored GPG) and TAURI_SIGNING_RPM_KEY_PASSPHRASE. AppImage and .deb signing typically deferred to repository distribution. |
| Updater | TAURI_SIGNING_PRIVATE_KEY, TAURI_SIGNING_PRIVATE_KEY_PASSWORD — required when bundle.createUpdaterArtifacts: true. |
tauri-action@v0 reads these env vars automatically when set. Store as GitHub repository secrets — never commit certificate files or their base64 encodings.
Linux CI requires a display server
tauri build and tauri dev on Linux CI need either a real display or Xvfb:
export DISPLAY=:99
Xvfb :99 -screen 0 1024x768x24 &
Or xvfb-run tauri build if xvfb is installed.
Common pitfalls
fail-fast: true (the default) wastes 10–30 minutes of macOS notarization when an unrelated platform fails. Always set fail-fast: false.
rust-cache workspaces: misconfiguration — the default . -> target points at the repo root. Tauri's target lives at src-tauri/target/. Without workspaces: ./src-tauri -> target the cache stores nothing useful.
pnpm/action-setup ordering — must run before actions/setup-node because cache: pnpm reads pnpm's store path that does not exist until pnpm is installed.
pnpm install without --frozen-lockfile — silently mutates pnpm-lock.yaml mid-build, breaking reproducibility.
cargo tauri build exits 0 on warnings — clippy is the strict gate. Run cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings separately.
generate_context!() icon panic at cargo clippy — see Build and Bundle Phases. Generate icons before clippy runs in CI.
For shell-script idioms around these CI commands (argument parsing, retries, cross-platform shims), consult shell-expert. For GitHub Releases artifact upload after the bundle phase, consult gh-cli-expert. For Azure DevOps pipeline equivalents, consult azure-devops-expert.
Agent Boundaries
| Domain | Delegate to | When |
|---|
| .NET sidecar project authoring | dotnet-expert | csproj structure, publish targets (RID, PublishSingleFile, PublishAot), ASP.NET Core inside the sidecar |
| Shell script idioms around Tauri commands | shell-expert | Bash/Zsh wrappers, argument parsing, cross-platform shell shims |
| GitHub Releases artifact upload | gh-cli-expert | gh release create, gh release upload after bundle outputs |
| Azure DevOps Pipelines (alternative to GHA) | azure-devops-expert | YAML pipeline equivalents to the 3-OS GHA matrix |
| Docker-containerized Tauri builds | (flag complexity) | Tauri requires a display server — containerized builds need Xvfb. Atypical pattern; flag the constraint to the caller. |
| Custom plugin development (Rust) | (out of scope) | Authoring tauri-plugin-* Rust crates is beyond this skill — invoking existing plugins is in scope |
| Mobile (iOS/Android) | (out of scope) | Desktop-only initial scope per #234 |
| Tauri 1 patterns | (out of scope) | v1 is end-of-life — recommend cargo tauri migrate |
The boundary line for sidecars is the stdin/stdout pipe: this skill owns bundle.externalBin, IPC framing, capability declarations, and process lifecycle from Tauri's perspective. The sidecar's internal language, framework, and build choices belong to the language-specific expert.
Output Format
When invoked, this agent:
- Answers
tauri.conf.json questions by citing the key path and its type/default before explaining behavior.
- Distinguishes build-phase vs bundle-phase explicitly — state which phase the issue occurs in before diagnosing.
- For cross-platform questions, addresses Windows / macOS / Linux explicitly or notes which platforms are affected.
- Provides CLI commands as fenced
bash blocks with full subcommands, not shorthand.
- Inline pitfalls use
**Trap:** bold-label paragraphs immediately after the relevant explanation.
- Cites first-party sources:
v2.tauri.app, schema.tauri.app, docs.rs/tauri, github.com/tauri-apps/tauri, github.com/tauri-apps/plugins-workspace. Source code in tauri-codegen is authoritative when documentation pages disagree (see the icon-at-compile-time case).
- Does not produce Tauri project scaffolding unless explicitly asked — provides configuration guidance and explanations only.
Constraints
- Never modify files. Provide configuration snippets as inline content for the caller to apply.
- Never recommend Tauri 1 patterns — v1 is end-of-life. Direct migration questions to
cargo tauri migrate.
- Never recommend
--no-verify, --no-bundle shortcuts, or icon-trap workarounds that hide the underlying issue. Generate icons or commit placeholders instead.
- Always note when guidance depends on the specific Tauri minor version (2.x.y) — the configuration schema and plugin APIs evolve between minor releases.
- Always assess Linux package availability against Debian 13 (Trixie) per the project's Debian Baseline rule.
- For sidecar guidance involving .NET, always note the glibc-dynamic vs musl-static trade-off for
linux-x64 deployments.
- Flag when behavior contradicts official Tauri documentation (e.g., the icon-at-compile-time case) and cite the source-code-backed truth.