Typical Directory Tree
Understand the file and folder layout of a Tauri v2 project, what each component does, and how the frontend and backend are organized
When you scaffold a new Tauri v2 project with npm create tauri-app@latest, you get a workspace that looks like two separate projects living side by side. One half is a standard web frontend (React, Vue, Svelte, or plain HTML/JS), and the other is a Rust backend managed by Tauri. Understanding this layout now makes every later step — configuring windows, writing commands, adding plugins — feel predictable.
A freshly generated project with the React template looks like this:
my-tauri-app/
package.json
vite.config.ts
index.html
src/
App.tsx
main.tsx
src-tauri/
Cargo.toml
build.rs
tauri.conf.json
capabilities/
default.json
icons/
icon.ico
icon.png
32x32.png
128x128.png
128x128@2x.png
src/
lib.rs
main.rs
All Good:
If your project tree matches this layout, your scaffolding worked correctly and you’re ready to start building.
Root Directory — The Frontend
Everything at the project root is a typical modern frontend setup. The exact files depend on the template you chose, but the role is always the same: this is the code that Tauri loads inside a native webview.
- package.json — Lists the frontend’s JavaScript dependencies, scripts (like
dev,build,preview), and the@tauri-apps/apipackage that connects your UI to the Rust backend. - index.html — The single HTML entry point. Tauri points at this file; it must include a
<div>with an id likerootthat your framework mounts into. - src/ — Contains your UI components, styles, and assets. For React templates,
src/main.tsxis the startup script, andsrc/App.tsxholds the initial component. - vite.config.ts (or equivalent) — The bundler configuration. Tauri uses this to build the frontend into static files that the Rust side serves.
You can treat this part exactly like any standalone web app. Run
npm run devduring development, write components, and Tauri will reload the window when files change. The only thing that makes it “Tauri-aware” is the import from@tauri-apps/api, which we’ll cover when calling Rust from React.
Inside src-tauri — The Rust Backend
The src-tauri/ folder is a full Rust crate. Tauri’s CLI compiles this crate into a binary that opens native windows, loads your frontend, and exposes system APIs.
Cargo.toml
This is the manifest for the Rust side. It declares the crate name, version, and all Rust dependencies — most importantly the tauri v2 crate itself. Tauri plugins like tauri-plugin-shell or tauri-plugin-fs are listed here as Cargo dependencies and then registered in lib.rs.
Two Dependency Graphs:
Your project has two independent dependency graphs: package.json for JavaScript and Cargo.toml for Rust. They don’t share packages, and you update them separately.
tauri.conf.json
The central configuration file. It specifies the app’s identifier, window titles, the frontend dev server URL, and references to the capabilities and icon directories. Nearly every behavior you can tune — window size, security settings, bundle targets — lives in this one file.
A minimal tauri.conf.json looks like this:
{
"$schema": "https://raw.githubusercontent.com/nicepage/tauri/dev/crates/tauri-config-schema/schema.json",
"productName": "my-app",
"version": "0.1.0",
"identifier": "com.myapp.dev",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:1420",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
{
"title": "My App",
"width": 800,
"height": 600
}
]
}
}
build.rs
A small Rust build script that Tauri requires. It typically contains a single line that calls tauri_build::build(). This script collects metadata about the build (like the Tauri version) and ensures the binary sets environment variables correctly for the frontend discovery at runtime.
fn main() {
tauri_build::build()
}
capabilities/ Directory
Capabilities replaced the old allowlist system from Tauri v1. Each JSON file in capabilities/ declares a set of permissions — such as reading the file system, opening a shell, or showing notifications — and specifies on which platforms and windows those permissions apply.
A default capability might look like:
{
"identifier": "default",
"description": "Default capabilities",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open"
]
}
Pay Attention:
Capabilities are opt-in. If your frontend calls a Tauri API for which you haven’t granted a capability, the call will fail at runtime — not at compile time. Always add the corresponding permission before using a new plugin or API.
src/lib.rs and src/main.rs
Tauri v2 splits the Rust entry point into two files, a pattern that enables mobile support.
lib.rsdefines a publicrun()function. Inside it, you create the Tauri application builder, register plugins, invoke your setup logic, and call.run(). All your command functions can also live here, or they can be in separate modules.main.rsis a thin wrapper. On desktop, it callsyour_crate::run(). On mobile (through conditional compilation), it calls a different entry point that the mobile harness provides.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}
pub fn run() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Critical Issue:
Never delete lib.rs or merge its content back into main.rs. The separation is required for Tauri v2’s mobile compilation targets — if you skip it, your app will fail to build for Android and iOS.
icons/ Directory
Contains your app’s icons in multiple sizes and formats. Tauri’s CLI uses these files during tauri build to generate platform-specific icon assets (.ico for Windows, .icns for macOS, .png for Linux). The tauri icon command can populate this folder from a single source image.
Cargo.lock
Locks the exact versions of all Rust dependencies. Committed to version control for the src-tauri crate, because this is an application binary — reproducible builds depend on it.
Why the lib.rs and main.rs Split Exists
In Tauri v1, main.rs contained the entire application builder directly. That worked for desktop builds, but mobile platforms (Android and iOS) need to call into the application from a language-specific harness — Kotlin on Android, Swift on iOS. A crate that exposes a public run() function makes that possible: the mobile harness calls that function, while the desktop binary calls it from a trivial main.rs.
This architecture also means you can write integration tests that spin up a Tauri application by calling run() without needing a full main() binary.
Verifying Your Tree
You can confirm the layout with a quick command (Node.js must be installed, since npx comes with it):
cd my-tauri-app
npx tree-cli --ignore 'node_modules, target' --base .
This prints the directory structure, skipping build outputs and dependencies, so you can compare it against the expected layout shown earlier.