Preparing for Production
Configure your Tauri app's identity, icons, resources, and external binaries to build distributable production packages.
Before you run tauri build and hand your application to real users, you need to tell Tauri who your application is, what it looks like, and what extra files it needs to run. Skipping these steps produces builds that may fail to install, show a blank icon in the system tray, or silently miss data your app expects at runtime. Product identity lives in Product Configuration; icons and installers in Bundle Configuration.
The work falls into a handful of configuration blocks inside tauri.conf.json (or Tauri.toml). Each one maps to a clear question: What is this app called? Which version is this? Where are the icons? What files must travel with the binary?
Setting Product Information
Every operating system identifies an application by three pieces of metadata: a human‑readable name, a unique reverse‑domain identifier, and a version string. If any of these is missing or inconsistent, the OS may reject the installer, overwrite an existing installation incorrectly, or prevent auto‑update from matching builds.
The fields live at the root of the Tauri configuration file.
{
"productName": "My Notes",
"identifier": "com.mycompany.mynotes",
"version": "1.0.0"
}
productName is the name shown in the title bar, the installer, and the operating system’s application list. It can contain spaces and capital letters. identifier must be a globally unique string — by convention, a reverse domain like com.mycompany.appname. Two applications with the same identifier are treated as the same application by the OS, so changing this after a public release effectively creates a brand‑new app. version follows Semantic Versioning (MAJOR.MINOR.PATCH) and is used by the auto‑updater to decide whether a newer build exists.
Identifier Changes Break Updates:
The identifier is the operating system’s permanent key for your app. If you change it after shipping, users will end up with two separate installations and the auto‑updater will stop working for the original one. Pick a stable identifier before your first release.
Managing Version Numbers
Tauri reads the version from the configuration file, but your frontend and Rust crate each have their own version too. During development, those three numbers can drift apart without consequence. At release time, they need to match — or at least be consciously managed — so that log files, crash reports, and update checks all agree on what “1.2.0” means.
The version field in tauri.conf.json is the source of truth for the bundle. Tauri injects it into the installer metadata and into the app.version API available in the frontend. Separately, src-tauri/Cargo.toml carries a version for the Rust binary, and package.json carries one for the frontend tooling.
A common approach is to keep all three in sync with a script that reads the root config version and writes it to the other two files before the build. At minimum, ensure the Tauri config version is bumped for every public release.
Frontend Caching and Version Mismatch:
If you bump the Tauri version but the frontend still loads an old cached index.html, users may see stale UI after an update. Always rebuild the frontend from scratch (npm run build without caching) before packaging.
Icons for Your Application
The application icon is the single most visible piece of branding. Every platform expects a specific set of sizes and file formats. Providing the wrong format or missing a required size causes build warnings, a default placeholder icon in the system tray, or a blurred icon on high‑DPI screens.
Tauri accepts a list of icon file paths in the bundle.icon array. It then automatically converts and resizes those source icons into the formats each platform needs. The recommended minimum set is a single 1024×1024 PNG with transparency; Tauri will generate everything else from it. If you need pixel‑perfect control for specific platforms, you can supply platform‑specific sizes directly.
"bundle": {
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
The paths are relative to the src-tauri directory. If you only include one large PNG, the Tauri CLI handles the rest. On macOS, an .icns file is still preferred for the final bundle; on Windows, an .ico with multiple embedded sizes is best for the taskbar and file association icons.
Where to Place Icon Files:
Store icons inside src-tauri/icons/ to keep them versioned alongside the Rust backend. The bundle step copies them into the final package, so they do not need to be in the frontend’s public folder.
Bundling Resources
Not everything your app needs lives inside the compiled binary. Configuration files, SQLite database templates, machine‑learning models, or even entire folder trees of assets may need to ship alongside the executable. Tauri’s bundle.resources field lets you specify files and directories that the bundler should embed into the final package.
Resources can be individual files or whole directories (which are copied recursively). The paths are resolved relative to src-tauri. At runtime, Tauri exposes the path to the resource directory through the tauri::path::resource_dir() Rust API.
"bundle": {
"resources": [
"assets/logo.png",
"data/defaults.json",
"models/*"
]
}
A directory resource like "models/*" will include every file inside src-tauri/models/. On macOS, these end up inside the .app bundle’s Resources/ folder; on Windows, they sit next to the .exe; on Linux, they are placed according to the packaging format.
Missing Resources Cause Silent Crashes:
If your Rust code calls resource_dir() and expects a file that wasn’t listed in bundle.resources, the build will succeed but the app will fail at runtime with a file‑not‑found error that may not be caught. Always test the packaged app, not just tauri dev.
Verifying Resource Inclusion:
After running tauri build, open the output directory (src-tauri/target/release/bundle/) and inspect the package contents. On macOS, right‑click the .app and choose “Show Package Contents”; on Windows, look in the folder containing the .exe. Your resource files should be visible there.
Embedding External Binaries
Some applications need to ship with a separate binary — a sidecar process like ffmpeg, a database engine, or a helper tool written in a different language. Tauri refers to these as “external binaries.” They are declared in the bundle.externalBin field and are automatically included in the package, signed (if code signing is enabled), and accessible from Rust through the tauri::api::process::Command API or the sidecar plugin.
Each entry is a path relative to src-tauri pointing to the binary. Tauri resolves the correct executable for the target operating system by using a naming convention: binary-name-$TARGET_TRIPLE (for example, ffmpeg-x86_64-pc-windows-msvc.exe).
"bundle": {
"externalBin": [
"binaries/ffmpeg"
]
}
In src-tauri/binaries/, you would place:
ffmpeg-x86_64-pc-windows-msvc.exeffmpeg-x86_64-apple-darwinffmpeg-aarch64-apple-darwinffmpeg-x86_64-unknown-linux-gnu
At build time, Tauri picks the binary matching the target triple and embeds it. From the Rust side, you spawn the sidecar with:
use tauri::api::process::Command;
#[tauri::command]
async fn run_ffmpeg(args: Vec<String>) -> Result<String, String> {
let (mut rx, child) = Command::new_sidecar("ffmpeg")
.map_err(|e| e.to_string())?
.args(&args)
.spawn()
.map_err(|e| e.to_string())?;
// Read output...
}
The binary must be compiled for each platform you intend to support. Tauri does not cross‑compile external binaries; you provide the pre‑compiled artifacts.
Sidecar Permissions on macOS and Linux:
After embedding, the sidecar binary loses its executable permission unless Tauri restores it. The sidecar API handles this automatically, but if you attempt to execute the binary directly via the filesystem, you must call chmod +x on the extracted path first. Stick to the official sidecar command API.
Verifying the Frontend Build Process
Production builds rely on the build.frontendDist and build.beforeBuildCommand settings to integrate the frontend correctly. If these are misconfigured, the resulting application will show a blank window or a “file not found” error.
For a React + Vite project, the default configuration created by create-tauri-app works out of the box:
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:5173",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
}
beforeBuildCommand tells the Tauri CLI to run the frontend’s production build script before it starts compiling the Rust backend. frontendDist points to the folder that contains the built index.html and static assets, relative to the src-tauri directory. With Vite, the default output folder is ../dist; make sure this matches what vite.config.ts defines.
After the build, the Tauri CLI copies everything from frontendDist into the platform‑specific bundle. A mismatch here is the most common reason a production build launches to a white screen.
Test the Built App Locally:
Run npm run tauri build -- --debug to produce a debug build that still includes dev‑tooling. Install it on your machine and confirm the UI loads and all native features work before you ship.
Security Configuration for Production
Development mode relaxes several security restrictions so you can iterate quickly. Before distributing, tighten the Content Security Policy (CSP) and review the capability permissions your app requests.
The CSP is set in the app.security.csp field (or app.security.devCsp for development). A production CSP should restrict scripts and connections to only the origins your app actually needs. The Tauri CSP can be a string or null to disable the webview CSP entirely, but disabling it makes your app vulnerable to cross‑site scripting attacks that could escape the webview.
"app": {
"security": {
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
}
}
Capabilities, defined in src-tauri/capabilities/, control which native APIs the frontend can call. For production, audit every permission in your capability files and remove any that aren’t used. A file‑sharing app that never accesses the shell should not include "shell:allow-open".
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"fs:allow-read",
"fs:allow-write"
]
}
Capability Over‑Permission:
Every permission you grant is a potential escape hatch. If a malicious dependency in your frontend can call invoke("shell:open", { path: "/bin/rm" }), the OS will execute it. Remove any permission your app does not actively use.
Summary
Preparing for production is a configuration discipline, not a single command. The same tauri build invocation produces a broken installer when the identifier is wrong and a polished, signed package when every field is correct.
The critical checklist is small: a stable identifier, a bumped version, at least one source icon, every runtime file declared in resources or external binaries, and a verified frontend build. With those in place, the build pipeline will produce artifacts ready for testing, signing, and distribution.