tauri.conf.json - The Core Configuration File
Complete guide to the Tauri v2 configuration file structure covering product metadata build settings and application runtime configuration
Every Tauri project has exactly one file that sits at the intersection of your Rust backend and your React frontend. It tells Tauri what your app is called, how to find your web assets during development and production, and how the native window should behave when your application launches. That file is tauri.conf.json.
It lives in the src-tauri/ directory — see where it sits in the project — and is generated automatically when you run tauri init. You will modify it throughout the life of your project — adding new windows, adjusting build commands, changing the app name, or wiring up plugins. Understanding its structure early prevents configuration errors that are frustrating to debug later.
The file is written in JSON by default, but Tauri v2 also supports JSON5 (which allows comments and trailing commas) and TOML (which uses a more readable, kebab-case key style). The choice is yours — the structure and meaning of every field remain identical across all three formats.
{
"productName": "my-app",
"version": "0.1.0",
"identifier": "com.example.myapp",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "My App",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
},
"plugins": {}
}
TOML and JSON5 require Cargo features:
To use TOML or JSON5, add the corresponding feature flag to your Cargo.toml dependencies. For TOML, add features = ["config-toml"] to both the tauri and tauri-build crates. For JSON5, use features = ["config-json5"]. Without this, Tauri will only read the default tauri.conf.json file.
The configuration object has four main sections — app, build, bundle, and plugins — plus a handful of root-level fields that identify your application. The sections covered in detail here are the root-level product fields, the build object, and the app object. The bundle object is covered in Bundle Configuration and plugins is covered alongside individual plugin documentation.
v1 and v2 configs are not interchangeable:
If you see errors like Additional properties are not allowed ('package', 'tauri' were unexpected) or 'devPath', 'distDir' were unexpected, you are using a Tauri v1 configuration format in a Tauri v2 project. Tauri v1 wrapped config inside a "tauri" key and used "package", "devPath", and "distDir". Tauri v2 flattens the structure — "productName" and "version" sit at the root, and the build fields are "devUrl" and "frontendDist". Check your @tauri-apps/cli version with npm run tauri -- --version. If it is a beta version or version 1.x, update with npm i -D @tauri-apps/cli@latest.
Product Configuration
The dedicated Product Configuration page covers each identity field in isolation. Before Tauri can build your app, it needs to know what to call it and how to identify it on the user's system. Four root-level fields answer these questions: productName, version, identifier, and mainBinaryName. Together they determine the name shown in window titles, the version reported to operating systems, the unique bundle ID used by package managers, and the filename of the compiled binary.
{
"productName": "My Tauri App",
"version": "1.2.0",
"identifier": "com.mycompany.mytauriapp",
"mainBinaryName": "my-tauri-app"
}
productName is the human-readable name of your application. It appears in the window title by default, in installer dialogs, and in system-level application lists. The field accepts any string except characters that filesystems typically disallow: ^, /, \, :, *, ?, ", <, >, and |. If you omit it, Tauri falls back to the package name from Cargo.toml.
version follows semantic versioning — three numbers separated by dots, like "1.2.0". You can write the version directly as a string, or point Tauri to a package.json file in your frontend project by providing a relative path like "../package.json". When you use a path, Tauri reads the version field from that file. On macOS, this value populates CFBundleShortVersionString. On Android, the version code is auto-derived from the semantic version unless you override it in the bundle configuration. If you remove this field entirely, Tauri uses the version from Cargo.toml.
Keep versioning in one place:
It is tempting to manage the version in both Cargo.toml and tauri.conf.json. Pick one source of truth. The Tauri team recommends managing it in tauri.conf.json because that file controls the version reported to all target platforms — macOS, Windows, Linux, Android, and iOS. If your frontend package.json already has a version, point Tauri at it with "version": "../package.json" to avoid duplication.
identifier is the only root-level field that is required. It must be a reverse-domain-name string like "com.mycompany.myapp" — unique across all applications on a user's system. The operating system uses this value for the bundle ID, the path to the webview data directory, and other system-level configurations. It can contain only alphanumeric characters (A-Z, a-z, 0-9), hyphens (-), and periods (.). Once you publish an application with a given identifier, changing it means the OS treats the new version as a completely different app — separate data storage, separate permissions, separate installation.
mainBinaryName overrides the filename of the compiled binary that cargo produces. By default, Tauri uses the binary name from your Rust project. If you set this field, Tauri renames the binary during tauri build. Do not include a file extension — Tauri appends .exe on Windows automatically. This field is rarely needed. If you want a different binary name, it is usually cleaner to change the name field in Cargo.toml instead.
Quick validation:
If your config has at least a valid identifier, a build.devUrl, and a build.frontendDist, Tauri can start your app in development mode. Those three fields are the practical minimum for a working project. Run npm run tauri dev — if the window opens and loads your frontend, your core configuration is correct.
Build Configuration
The build object is where you tell Tauri how to work with your frontend tooling. For a React + Vite project, this means pointing Tauri at Vite's dev server during development and at Vite's output folder when building for production. The build configuration bridges the two halves of your application — the Rust process that Tauri manages and the Node.js process that Vite manages.
{
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist",
"additionalWatchFolders": ["../public"],
"removeUnusedCommands": false
}
}
The four essential fields form a clear workflow. When you run tauri dev, Tauri first executes beforeDevCommand — in this case npm run dev, which starts the Vite development server. Once that server is ready, Tauri opens a native window and loads the URL specified in devUrl, typically http://localhost:1420 for a default Vite setup. Your React app runs inside that window, and Vite's hot module replacement works exactly as it does in a browser.
When you run tauri build, the sequence reverses. Tauri executes beforeBuildCommand — npm run build — which runs vite build and outputs static files to the dist directory. Tauri then reads those files from frontendDist, which is set to "../dist" because the path is relative to the src-tauri directory where the config file lives. Those static files get embedded into the final binary.
additionalWatchFolders tells Tauri's file watcher to monitor extra directories during development. By default, Tauri watches src-tauri/ and your frontend source. If you have assets in a public/ directory that should trigger a rebuild when changed, list it here. The paths are relative to src-tauri/, so "../public" points to the public/ folder in your project root.
removeUnusedCommands is a Rust compilation optimization. When set to true, Tauri removes any commands from the final binary that are defined in your Rust code but never invoked from the frontend. This reduces binary size but can cause issues if you register commands dynamically. Leave it at false unless you are specifically optimizing for binary size and have tested that all commands still work.
Vite's default port:
Tauri's project generator sets Vite to port 1420 by default, not the more common port 5173. You can find this in vite.config.ts under server.port. If you change the Vite port, update devUrl to match. A mismatch causes tauri dev to open a blank window because Tauri is trying to load a URL where nothing is running.
The build object also supports a platform-specific windows sub-object with one notable field: staticVCRuntime. When set to true (the default), the Microsoft Visual C++ runtime is statically linked into your Windows binary. This means users do not need to install the VC++ redistributable separately. Setting it to false produces a smaller binary but requires the runtime to be present on the target machine.
Application Configuration
The app object controls what happens when your application is actually running — the native window that opens, the security rules that constrain what your web code can do, and how Tauri's API is exposed to your frontend. This is the runtime configuration, distinct from the build-time configuration in the build object.
{
"app": {
"windows": [
{
"title": "My Tauri App",
"width": 1024,
"height": 768,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null,
"assetProtocol": {
"enable": false,
"scope": []
},
"capabilities": [],
"dangerousDisableAssetCspModification": false,
"freezePrototype": false,
"pattern": {
"use": "brownfield"
}
},
"trayIcon": null,
"withGlobalTauri": false,
"enableGTKAppId": false,
"macOSPrivateApi": false
}
}
The windows array defines every native window your application creates at startup. Each entry is a window configuration object with fields for size, position, title, decorations, transparency, and more. Most applications start with a single window. You can add more by including additional objects in the array, each with its own title and label for identification. The full set of window options — including minimum and maximum size, always-on-top behavior, skipping the taskbar, and custom window decorations — is covered in Window Configuration.
Every window needs a unique label:
If you define multiple windows, give each one a unique "label" field. The label is how your Rust and JavaScript code identifies which window to target when calling window-specific APIs. Two windows with the same label will cause Tauri to panic at runtime. Labels must be unique across the entire application, including windows created programmatically after startup.
The security object is the gatekeeper between your web frontend and the native operating system. Its fields determine what your frontend code is allowed to access. The most important fields are:
csp— the Content Security Policy. When set tonull, Tauri removes the default restrictive CSP, which is common during development. For production, you should define a policy that restricts which sources can load scripts, styles, and connect to remote servers. Full CSP configuration is covered in Security & Capabilities.assetProtocol— controls whether Tauri's customasset://protocol is enabled and which local paths it can serve files from. When enabled, your frontend can load local resources through a secure protocol rather thanfile://, which browsers restrict heavily.capabilities— a list of capability identifiers that define which Tauri APIs and plugin features are available to your frontend. Capabilities are defined in separate JSON files withinsrc-tauri/capabilities/and referenced here. This is the primary permissions system in Tauri v2.dangerousDisableAssetCspModification— Tauri normally modifies your CSP to allow loading assets from its custom protocol. Setting this totruedisables that modification. The word "dangerous" in the name is accurate — only use this if you fully understand the CSP implications.freezePrototype— whentrue, callsObject.freeze()on JavaScript built-in prototypes (Object.prototype,Array.prototype, etc.) before your application code runs. This prevents malicious dependencies from modifying native JavaScript behavior.pattern.use— specifies the isolation pattern."brownfield"is the default and gives your frontend direct access to Tauri APIs through@tauri-apps/api. The alternative is"isolation", which routes all API calls through a separate iframe for stronger security at the cost of some performance.
trayIcon configures a system tray entry for your application — the small icon that sits in the notification area on Windows, the menu bar extra on macOS, or the status area on Linux. Setting it to null (or omitting it) means no tray icon is created. A configured tray icon can show menus, respond to clicks, and keep your application running when all windows are closed.
withGlobalTauri controls whether the Tauri API is injected as a global window.__TAURI__ object in your frontend. The default is false, meaning you import Tauri APIs explicitly through @tauri-apps/api packages. Setting it to true makes the API available globally, which can be convenient for quick scripts but pollutes the global namespace. Most projects should leave this at false and use explicit imports.
enableGTKAppId is Linux-specific. When true, Tauri sets the GTK application ID to match your identifier value. This is important on Linux desktops that use GTK for window management — without it, your application may appear as a generic icon in the taskbar or fail to group multiple windows together.
macOSPrivateApi enables two macOS-specific behaviors: transparent window backgrounds (which require private APIs for proper rendering) and the fullScreenEnabled preference. Most applications do not need this. Enable it only if you are building a desktop widget or a custom-shaped window on macOS and have tested that your app still passes App Store review if you plan to distribute there.
Your config is valid if the app launches:
The simplest way to verify your app configuration is to run npm run tauri dev. If a window opens, loads your React frontend, and responds to Tauri API calls from @tauri-apps/api, your application configuration is structurally correct. From there, you can iteratively add windows, adjust security settings, or enable platform-specific features — testing each change as you go.
Platform-Specific Overrides
Tauri supports platform-specific configuration files that merge with your main tauri.conf.json following the JSON Merge Patch specification (RFC 7396). This means you can define different window sizes, bundle settings, or plugin configurations per platform without duplicating the entire config.
The platform-specific files live alongside tauri.conf.json in src-tauri/:
tauri.linux.conf.json— Linux-specific overridestauri.macos.conf.json— macOS-specific overridestauri.windows.conf.json— Windows-specific overridestauri.android.conf.json— Android-specific overridestauri.ios.conf.json— iOS-specific overrides
Here is a concrete example. Suppose your main config sets a single window with a width of 800 pixels, but you want the macOS version to be wider to account for the menu bar. You would write:
{
"app": {
"windows": [
{
"width": 1024,
"height": 768
}
]
}
}
When building for macOS, Tauri merges this into the main config. The resolved configuration keeps everything from tauri.conf.json except the window width and height, which are replaced by the values in the platform file. The merge is a shallow replacement at each key — arrays are replaced entirely, not appended. If you define a windows array in the platform file, it replaces the entire windows array from the main config for that platform.
You can also extend the configuration at build time using the --config CLI flag. This accepts either a raw JSON string or a path to a JSON file. A common use case is building a beta version of your app with a different name and identifier:
{
"productName": "My App Beta",
"identifier": "com.mycompany.myapp.beta"
}
Then build with:
npm run tauri build -- --config src-tauri/tauri.beta.conf.json
This produces a completely separate application that can be installed alongside the production version on the same machine — different name, different identifier, different data storage.
How Beginners Should Think About This File
If you are new to Tauri and desktop development, the configuration file can feel like a long list of arbitrary fields. A more useful mental model is to think of it as three layers that correspond to three phases of your application:
Identity — the root-level fields (productName, version, identifier). These answer the question "what is this application?" They rarely change after the initial setup. The identifier is the most consequential choice you will make because it becomes the permanent identity of your app on every user's system.
Construction — the build object. These fields answer "how do I assemble the frontend and backend together?" They change when your tooling changes — switching from Vite to Webpack, changing the dev server port, or adding watch folders. During development, these are the fields you will revisit most often.
Execution — the app object. These fields answer "how should the application behave when it runs?" Window size and position, security constraints, tray icon behavior — all runtime concerns. They change as your application's feature set grows.
When something is not working, knowing which layer it belongs to narrows your search. If the app name is wrong in the title bar, check the app.windows[].title field (execution layer). If tauri dev opens a blank screen, check build.devUrl (construction layer). If the installer puts your app in the wrong location, check identifier (identity layer).
Summary
The three configuration sections covered in this document — product, build, and application — form the essential skeleton of every Tauri project. Product configuration establishes your application's identity and persists for the life of the project. Build configuration bridges your React + Vite frontend with Tauri's Rust tooling, and it is the section you will adjust most frequently during active development. Application configuration defines the runtime experience — the window your users see, the security boundaries your code operates within, and the platform-specific behaviors that make a desktop app feel native.
The fields not covered in depth here — bundle and plugins — are equally important but large enough to warrant their own dedicated sections.
The single most important field in tauri.conf.json is identifier. It is the only required field, it cannot change after publication without effectively creating a new application, and every major operating system uses it to isolate your app's data, permissions, and installation. Choose it carefully, follow the reverse-domain convention, and make sure it is unique.
Introduction to tauri.conf.json
The purpose, structure, and core concepts behind the main Tauri configuration file, including format options, platform-specific merging, and validation.
Product Configuration
Define the name version and unique identifier of your Tauri application in tauri.conf.json
Build Configuration
A complete reference for the build section of tauri.conf.json in Tauri v2 with React and Vite covering frontend paths, dev server URL, before hooks, watch folders and platform-specific options
Application Configuration
How to set up the app object in tauri.conf.json to control windows, security, global APIs, and platform behavior in Tauri v2