iOS Project Structure
What cargo tauri ios init generates, the gen/apple directory, Info.ios.plist, and bundle.iOS config
This page covers what gets added to your Tauri project when you enable iOS, where the iOS-specific files live, and which configuration fields in tauri.conf.json actually drive Xcode’s behavior.
Running cargo tauri ios init
From the directory containing your tauri.conf.json (the src-tauri/ directory in a typical layout):
cargo tauri ios init
This runs cargo-mobile2 under the hood and scaffolds an Xcode project. First run takes a few minutes because it compiles dependencies, sets up CocoaPods, and generates the Xcode project.
What Gets Generated
After ios init, your project picks up a gen/ directory:
src-tauri/
Cargo.toml
tauri.conf.json
Info.ios.plist # (you create this yourself when needed)
src/
lib.rs # must export `run` for mobile entry point
gen/
apple/ # iOS-specific output
Podfile
project.yml # XcodeGen project definition
<AppName>.xcodeproj/ # generated Xcode project
<AppName>_iOS/
<AppName>_iOS.entitlements # capability entitlements
Assets.xcassets/
LaunchScreen.storyboard
Sources/
main.mm # Objective-C++ entry point bridging to Rust
The gen/apple/ directory is generated from project.yml. XcodeGen reads project.yml and produces the .xcodeproj whenever you run cargo tauri ios init or cargo tauri ios dev/build. Anything you edit directly inside .xcodeproj risks being overwritten.
💡 Tip
Check gen/apple/ into git only if you need reproducible builds in CI without re-running ios init. For typical development, gitignore gen/ and rely on the CLI to regenerate. See the .gitignore example below.
lib.rs Must Export run
The mobile entry point calls into tauri_lib::run(). Your src/lib.rs needs to look roughly like:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|_app| Ok(()))
.invoke_handler(tauri::generate_handler![])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
And main.rs just calls the library:
fn main() {
app_lib::run()
}
Replace app_lib with your actual crate’s lib name (it’s configured via the [lib] section in Cargo.toml). If you don’t have a lib.rs yet, add one and update Cargo.toml:
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
The staticlib crate type is what the iOS build actually links against. Without it, cargo tauri ios build fails.
tauri.conf.json > bundle.iOS
The iOS-specific bundle configuration lives under bundle.iOS:
{
"bundle": {
"active": true,
"targets": "all",
"iOS": {
"developmentTeam": "ABCD123456",
"minimumSystemVersion": "14.0",
"frameworks": ["CoreHaptics", "WebKit"],
"bundleVersion": "1"
}
}
}
| Field | Purpose |
|---|---|
developmentTeam | 10-character Apple Team ID. Found in Xcode > Settings > Accounts > your team > “Team ID” |
minimumSystemVersion | Maps to IPHONEOS_DEPLOYMENT_TARGET. Default 13.0. Bump to 14.0+ if you need modern Web APIs |
frameworks | Extra Apple frameworks to link. Changing this requires re-running cargo tauri ios init |
bundleVersion | Maps to CFBundleVersion. Defaults to the top-level version. Override when you need a build number |
📝 Note
developmentTeam can also be set via the APPLE_DEVELOPMENT_TEAM environment variable — useful for CI where you don’t want to commit the team ID. The env var takes precedence over the config field.
The identifier Rule
The top-level identifier field in tauri.conf.json is the bundle identifier on iOS:
{
"identifier": "com.takazudo.myapp"
}
It must match the App ID you register on the Apple Developer portal (or the one auto-generated by Xcode for a personal team). Reverse-DNS style is the convention: com.<org>.<app>.
⚠️ Warning
Stick to alphanumeric characters and dots. Older Tauri versions had bugs with hyphens and underscores in the bundle identifier. com.takazudo.my-app has caused breakage in the past — com.takazudo.myapp is the safe choice.
Info.ios.plist: Merging Extra Keys
Tauri auto-generates an in-memory Info.plist for you with CFBundleShortVersionString, CFBundleVersion, and the basics. When you need additional keys (camera permission prompt, ATS exceptions, URL scheme handlers), create an Info.ios.plist next to tauri.conf.json:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan QR codes.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photos to attach images.</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
Keys defined here are merged on top of the generated Info.plist. Shared keys between desktop and iOS can go in a plain Info.plist instead — Tauri reads both.
Entitlements File
Capabilities that require entitlements (push notifications, App Groups, associated domains) are controlled by gen/apple/<AppName>_iOS/<AppName>_iOS.entitlements. That file is regenerated, so edits live there with care:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
In practice, because a free Personal Team does not grant these capabilities, you won’t touch this file until you’re on a paid Apple Developer Program membership.
.gitignore Additions
For a Tauri project with iOS enabled, extend the existing .gitignore:
/target/
/gen/apple/Pods/
/gen/apple/Externals/
/gen/apple/build/
Whether to ignore the rest of gen/apple/ is a judgement call. Committing the Xcode project can make it easier to tweak build settings in the GUI without XcodeGen regenerating them, but then you lose the “edit project.yml, re-init” workflow. The safe default is to gitignore all of gen/ and regenerate on CI.
iOS Version Matrix
minimumSystemVersion | Device reach | Notes |
|---|---|---|
13.0 (Tauri default) | iPhone 6s and newer | Lowest bar; some modern Web APIs may be missing |
14.0 | iPhone 6s and newer | Gets you better WKWebView features, container queries |
15.0 | iPhone 6s and newer | Safer bet for modern web apps in 2025+ |
16.0 | iPhone 8 and newer | Drops older devices, gains Developer Mode unified flow |
Pick the lowest version that supports the Web APIs your frontend actually uses. Check caniuse.com filtered to Safari-iOS for specifics.