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 URL →
http://localhost:1420 - beforeDevCommand →
bun run dev - beforeBuildCommand →
bun 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 prefix —
tauri buildrefuses. 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 initanswered with absolute paths — config wants paths relative tosrc-tauri/. Use../dist, not/Users/.../dist.- Commit of
src-tauri/gen/orsrc-tauri/target/— bloats the repo. Verify.gitignoreexcludes both (the scaffolder does this for you; if you copied a partial layout, double-check). - Mixing
bun create tauri-apptemplate into an existing repo — runsgit initand trashes your history if you point it at a non-empty dir. Usetauri initinstead.
Templates
templates/tauri.conf.minimal.json— minimal valid v2 config with placeholders.templates/Cargo.toml— minimalsrc-tauri/Cargo.tomlwith mobile-ready[lib].templates/lib.rs— minimalrun()with one plugin + one command registered.