Configuration Files
A detailed look at the core configuration files in a Tauri v2 project - tauri.conf.json, Cargo.toml, package.json, capability files, and environment variables - and how they work together.
A Tauri project is not a single-file monolith. Five distinct configuration files each own a slice of the application’s identity: the UI toolchain, the Rust backend, the security perimeter, the build system, and the environment-sensitive secrets. Knowing which file to reach for when you want to change the app name, add a Rust dependency, or grant access to the filesystem is the difference between guessing and engineering.
tauri.conf.json
This is the central configuration file. The Tauri CLI reads it to understand what to build, how to build it, and what the final application should look like and be allowed to do. It sits inside src-tauri/ — see Key Files and Directories — and is generated automatically when you run tauri init.
The file defines the application metadata (productName, version, identifier), the frontend asset origin (build.devUrl, build.frontendDist), window properties, bundle settings, and plugin configurations. Without it, the CLI has no idea how to assemble your app.
Supported Formats
By default the configuration file is written in JSON as tauri.conf.json. Tauri v2 also supports JSON5 and TOML. You enable these alternatives by adding a feature flag to both the tauri and tauri-build dependencies in Cargo.toml.
{
"productName": "MyApp",
"version": "0.1.0",
"identifier": "com.mycompany.myapp",
"build": {
"devUrl": "http://localhost:5173",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "My App",
"width": 800,
"height": 600
}
]
}
}
One format per project:
Pick a single format and stick with it. Tauri does not merge multiple formats — only one configuration file is read per build. If both tauri.conf.json and Tauri.toml exist and the TOML feature is enabled, TOML takes priority.
Field names are case-sensitive in all three formats. JSON5 and TOML both support comments, which is a practical advantage for explaining why certain values are set.
Platform-Specific Overrides
You can create separate configuration files for each target platform. These files merge with the base configuration following the JSON Merge Patch (RFC 7396) specification.
| Platform | JSON File Name | TOML File Name |
|---|---|---|
| Linux | tauri.linux.conf.json | Tauri.linux.toml |
| Windows | tauri.windows.conf.json | Tauri.windows.toml |
| macOS | tauri.macos.conf.json | Tauri.macos.toml |
| Android | tauri.android.conf.json | Tauri.android.toml |
| iOS | tauri.ios.conf.json | Tauri.ios.toml |
A platform override does not need to repeat the entire configuration. It only needs to specify the fields that differ for that platform. For example, if your Linux build requires a different set of bundled resources, you create tauri.linux.conf.json with only the bundle.resources array. Everything else is inherited from the base file.
Merge semantics can surprise you:
A platform file that sets a field to null will delete that field from the merged configuration, not leave it empty. If you want to clear an array, set it to an empty array — not null.
Extending the Configuration at Build Time
The CLI’s --config flag accepts a raw JSON string or a path to a JSON file. The provided JSON is merged on top of the resolved configuration (after platform overrides). This is useful for building separate flavours of the same application — a production build with one identifier and a beta build with another — without maintaining duplicate configuration files.
npm run tauri build -- --config src-tauri/tauri.beta.conf.json
The file tauri.beta.conf.json might contain only:
{
"productName": "My App Beta",
"identifier": "com.mycompany.myappbeta"
}
Everything else (windows, build commands, bundle settings) is inherited from the base configuration. No duplication, no drift.
Core Structure
The top-level properties of tauri.conf.json are:
productName– The human-readable app name displayed in window titles, bundle names, and OS menus.version– A semver version string or a path to apackage.jsonfile whoseversionfield should be used.identifier– A reverse-domain string (e.g.,com.mycompany.myapp). Must be unique across applications — the OS uses it for bundle IDs, data directories, and system-level identification.build– Commands and paths for the frontend:devUrl,frontendDist,beforeDevCommand,beforeBuildCommand.app– Runtime behaviour: window definitions, security configuration, tray icon settings, and global Tauri API access.bundle– Packaging settings: icon paths, installer targets, code signing identities, OS-specific configurations.plugins– Plugin-specific configuration objects, such as updater endpoints and deep-link settings.
Identifier cannot be changed lightly:
Once an app is distributed, changing the identifier effectively creates a new application from the OS perspective. Users will not receive updates from the old identifier, and their data may be stored under a different path. Pick your identifier once and stick with it.
Cargo.toml
Cargo.toml is the manifest file for Rust’s package manager, Cargo. Even if you write zero Rust beyond the boilerplate generated by tauri init, this file governs which versions of the Tauri core libraries your app links against.
The critical section is the dependencies:
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
The tauri-build crate runs during the build phase to generate the glue code that connects your configuration to the compiled binary. The tauri crate is the runtime. Both must be on compatible versions — ideally the same minor release line as the Tauri CLI you use.
Cargo follows Semantic Versioning. Specifying version = "2" allows any 2.x.x release. Running cargo update in src-tauri/ pulls the latest compatible patch. If a bug fix ships in the runtime, you get it without editing the file.
Feature flags are managed automatically:
You do not need to manually add feature flags for plugins in the tauri dependency. Running tauri dev or tauri build enables the required features based on your configuration and installed plugins.
When you build your app, Cargo generates a Cargo.lock file. Commit it to version control. It pins exact versions of every dependency so that every developer and CI machine builds the same binary — the same role as package-lock.json in the Node.js ecosystem.
package.json
For a React + Vite frontend, package.json is where the frontend toolchain lives. It declares the dev server command, the build command, and the Tauri CLI itself.
{
"name": "my-app",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"tauri": "tauri"
},
"dependencies": {
"react": "^18.3.0",
"react-dom": "^18.3.0",
"@tauri-apps/api": "^2"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@vitejs/plugin-react": "^4",
"typescript": "^5.5.0",
"vite": "^5.4.0"
}
}
Two Tauri packages matter here:
@tauri-apps/api– The JavaScript library that providesinvoke(), event listeners, and typed wrappers for all built-in Tauri APIs. It runs inside the webview at runtime.@tauri-apps/cli– The command-line tool that orchestrates the Rust compilation and bundling. It is a development dependency because end users never need it.
The "tauri" script in scripts is a convenience. Instead of typing npx tauri dev, you run npm run tauri dev. The scripts.dev and scripts.build entries are referenced by beforeDevCommand and beforeBuildCommand in tauri.conf.json — the Tauri CLI runs them automatically before launching the webview or packaging the frontend.
Capability Files
Tauri v2 replaced the v1 allowlist with a capability-based security model. Capability files are JSON documents stored in src-tauri/capabilities/ that declare exactly which permissions each window (or webview) receives. If a command is not explicitly permitted, calling it from the frontend fails at runtime.
A capability file looks like this:
{
"identifier": "main-capability",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"fs:allow-read-text-file",
"fs:allow-write-text-file"
]
}
"windows": ["main"] scopes these permissions to the window whose label is main. The permissions array uses a prefix scheme: core: for built-in APIs, plugin names for plugin commands (e.g., shell:, fs:), and default to grant a sensible pre-selected set of commands for that module.
Missing permissions cause silent denials:
If your frontend calls invoke('plugin:fs|read_text_file', ...) without fs:allow-read-text-file in a capability file, the call is blocked. Tauri v2 does not warn you at compile time for missing runtime permissions — the call simply rejects with a permission error. Always test API calls after writing capability files.
Custom plugins expose their own permissions. When a plugin is built, a permissions directory is generated. The permissions your plugin provides show up in the CLI error message if you misspell one — the build fails and lists every valid permission string, including your custom plugin’s.
Verifying capabilities:
If you deliberately misspell a permission during development, the build halts with a message like Permission shell:allow-open-misspelled not found, expected one of .... Seeing your custom plugin’s permissions in that list confirms the build script generated the permission manifest correctly.
Capability files are plain JSON and are not merged with platform-specific configurations. You can create multiple capability files — one for the main window, another for a secondary window that needs fewer permissions. This principle of least privilege means a vulnerability in an auxiliary window cannot access APIs that only the main window requires.
Environment Files
Environment variables in a Tauri + Vite project serve two separate layers.
Frontend Environment Variables (Vite)
Vite loads variables from .env files at build time. Variables prefixed with VITE_ are exposed to your React code via import.meta.env. Variables without the prefix are only available in vite.config.ts.
VITE_API_BASE_URL=http://localhost:8080
VITE_FEATURE_FLAG=true
const apiBase = import.meta.env.VITE_API_BASE_URL;
// Works: VITE_ variables are injected into the bundle at build time.
// const secret = import.meta.env.SECRET_KEY;
// Undefined: variables without VITE_ prefix are not exposed.
Create .env.production for production-specific overrides. Vite merges these based on the mode passed to the build command.
Tauri-Side Environment Variables
The Rust backend and the Tauri CLI read environment variables from the shell, not from .env files. These control sensitive operations like code signing and updater key management. Common ones include:
TAURI_SIGNING_PRIVATE_KEY– The private key for signing updater artifacts.TAURI_SIGNING_PRIVATE_KEY_PASSWORD– The password for that private key.APPLE_SIGNING_IDENTITY– The Developer ID certificate identity for macOS code signing.APPLE_CERTIFICATEandAPPLE_CERTIFICATE_PASSWORD– For CI-based notarization.
These are never committed to the repository. In local development you might export them in your terminal session; in CI, they are stored as secret environment variables.
Keep signing secrets out of config files:
Do not embed signing keys or passwords directly in tauri.conf.json. The configuration file is often committed. Use environment variables and reference them in your build scripts or let the Tauri CLI read them automatically from the environment.
Some Tauri plugins support the {{ env.VARIABLE_NAME }} placeholder syntax within their configuration blocks, allowing environment-specific values without hardcoding them. This is defined per-plugin — check the specific plugin’s documentation for whether it supports environment variable interpolation.