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/api package 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 like root that your framework mounts into.
  • src/ — Contains your UI components, styles, and assets. For React templates, src/main.tsx is the startup script, and src/App.tsx holds 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 dev during 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:

src-tauri/tauri.conf.json
{
  "$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.

src-tauri/build.rs
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:

src-tauri/capabilities/default.json
{
  "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.rs defines a public run() 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.rs is a thin wrapper. On desktop, it calls your_crate::run(). On mobile (through conditional compilation), it calls a different entry point that the mobile harness provides.
src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

fn main() {
    app_lib::run();
}
src-tauri/src/lib.rs
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.