zudo-tauri-wisdom

Type to search...

to open search from anywhere

iOS Project Structure

CreatedApr 16, 2026Takeshi Takatsudo

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"
    }
  }
}
FieldPurpose
developmentTeam10-character Apple Team ID. Found in Xcode > Settings > Accounts > your team > “Team ID”
minimumSystemVersionMaps to IPHONEOS_DEPLOYMENT_TARGET. Default 13.0. Bump to 14.0+ if you need modern Web APIs
frameworksExtra Apple frameworks to link. Changing this requires re-running cargo tauri ios init
bundleVersionMaps 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

minimumSystemVersionDevice reachNotes
13.0 (Tauri default)iPhone 6s and newerLowest bar; some modern Web APIs may be missing
14.0iPhone 6s and newerGets you better WKWebView features, container queries
15.0iPhone 6s and newerSafer bet for modern web apps in 2025+
16.0iPhone 8 and newerDrops 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.

Official Docs