← Catalog

tauri-setup-scaffolding

Use when standing up a new Tauri v2 project from scratch or bolting Tauri onto an existing JS app — running `bun create tauri-app` (or `npm create tauri-app@latest`), picking the framework/package-manager flags, understanding the generated `src-tauri/` layout (`Cargo.toml`, `tauri.conf.json`, `capabilities/`, `icons/`, `src/lib.rs` vs `main.rs`), or invoking `tauri init` to inject Tauri into a repo that already has a Vite/Next/SvelteKit/etc. frontend.

Tauri v2 — Scaffolding

For prereq install see [[tauri-setup-prerequisites]]. For Vite specifics see [[tauri-setup-vite]]. For migrating an existing v1 app see [[tauri-setup-v1v2]].

Fresh project (one command)

bun create tauri-app        # bun
npm create tauri-app@latest # npm
pnpm create tauri-app       # pnpm
yarn create tauri-app       # yarn
cargo create-tauri-app      # cargo only (no JS frontend)

You’re prompted for:

  • App name → folder name; reused as productName.
  • Identifier → reverse-DNS, e.g. com.acme.notes. Cannot start with a digit. Used by the OS bundle ID. Pick deliberately — changing it later breaks signed builds, installed-app discovery, deep links, and updater identity.
  • Frontend language → TypeScript / Rust / .NET. (TS is the common path.)
  • Package manager → bun / npm / pnpm / yarn / deno.
  • UI template → vanilla / Vue / Svelte / React / Solid / Angular / Preact / Yew / Leptos / Sycamore.
  • UI flavor → JS or TS for JS frameworks; bundler choice (Vite is default and recommended).

Non-interactive (CI / scripts):

bun create tauri-app -- \
  --name my-app \
  --identifier com.acme.my-app \
  --template vanilla-ts \
  --manager bun

Flags map 1:1 to the prompts. See bun create tauri-app -- --help for the full list.

Adding Tauri to an existing frontend

Run from the repo root. Tauri lives in src-tauri/; your existing src/ (or wherever) stays put.

bunx @tauri-apps/cli@latest init
# you'll be asked for: app name, window title, frontend dist dir,
# dev-server URL, beforeDev / beforeBuild commands.

Answers you almost always want for a Vite app:

  • Frontend dist../dist
  • Dev URLhttp://localhost:1420
  • beforeDevCommandbun run dev
  • beforeBuildCommandbun run build

These land verbatim in tauri.conf.json under build.*.

Add the JS API + plugins you need:

bun add @tauri-apps/api
bun add @tauri-apps/plugin-opener   # etc.

Generated layout (annotated)

my-app/
├─ src/                     # your frontend (Vite/Next/SvelteKit/...)
├─ src-tauri/
│  ├─ Cargo.toml            # Rust deps. tauri = "2", tauri-build = "2".
│  ├─ tauri.conf.json       # ALL Tauri config. See `bun run tauri info`.
│  ├─ build.rs              # `tauri_build::build()` — runs at compile time
│  │                        #   to generate command/permission glue.
│  ├─ src/
│  │  ├─ main.rs            # Thin entry. Calls `my_app_lib::run()`.
│  │  └─ lib.rs             # ALL of your Rust app code lives here.
│  │                        #   Splitting like this enables mobile builds
│  │                        #   (iOS/Android consume the lib, not main).
│  ├─ capabilities/
│  │  └─ default.json       # ACL bundle granted to `main` window.
│  │                        #   Review and tighten before release.
│  ├─ icons/                # PNG + ICO + ICNS, generated by `tauri icon`.
│  ├─ gen/                  # Auto-generated. DO NOT commit (it's gitignored).
│  └─ target/               # Cargo build dir. DO NOT commit.
├─ package.json             # Frontend deps + scripts.
├─ index.html               # Vite entry (or framework equivalent).
└─ vite.config.ts           # See [[tauri-setup-vite]].

Why main.rs and lib.rs are split

Mobile (iOS + Android) compile Tauri as a static library linked into a platform-native binary. To make the same Rust code build on desktop and mobile, the v2 scaffolder puts everything in lib.rs and reduces main.rs to:

fn main() {
    my_app_lib::run();
}

If you add a new module, put it under src-tauri/src/ and mod it from lib.rs, not main.rs. Don’t fold logic back into main.rs — you’ll lose mobile builds.

Cargo.toml essentials

[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

[lib]
name = "my_app_lib"     # <-- referenced by main.rs
crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Add plugins as tauri-plugin-<name> = "2" and register them in lib.rs::run() with .plugin(tauri_plugin_<name>::init()). See [[tauri-plugins]].

Regenerate icons

bunx @tauri-apps/cli icon path/to/source.png rebuilds every icon size + format into src-tauri/icons/. Source should be ≥1024×1024 with transparent background. Do this before first release — the default Tauri logo will get a PR rejected.

Initial sanity check

bun install
bun run tauri info     # prints versions of every Tauri-related piece
bun run tauri dev      # builds Rust, runs frontend dev server, opens window

If tauri dev hangs on “Building…”, it’s compiling Rust (3–8 min first time). Subsequent runs are seconds.

Common scaffolding pitfalls

  • Identifier with a digit prefixtauri build refuses. Rename before any signed build ships.
  • Bundle identifier collision with another app on the same machine — installs overwrite. Use a unique reverse-DNS you control.
  • tauri init answered with absolute paths — config wants paths relative to src-tauri/. Use ../dist, not /Users/.../dist.
  • Commit of src-tauri/gen/ or src-tauri/target/ — bloats the repo. Verify .gitignore excludes both (the scaffolder does this for you; if you copied a partial layout, double-check).
  • Mixing bun create tauri-app template into an existing repo — runs git init and trashes your history if you point it at a non-empty dir. Use tauri init instead.

Templates

  • templates/tauri.conf.minimal.json — minimal valid v2 config with placeholders.
  • templates/Cargo.toml — minimal src-tauri/Cargo.toml with mobile-ready [lib].
  • templates/lib.rs — minimal run() with one plugin + one command registered.