Skip to content

iOS setup

Step 1: Initialize the iOS project

bash
pnpm tauri ios init

Step 2: Add a Widget Extension target

Open the generated Xcode project:

bash
open src-tauri/gen/apple/*.xcodeproj

In Xcode: File → New → Target → Widget Extension. Name it (e.g. WidgetExtension), language: Swift.

In the Choose options for your new target dialog:

  • Set Product Name to WidgetExtension (or your widget name)
  • Select your Apple Team (e.g. Personal Team)
  • Keep Project as your current iOS project (e.g. myapp)
  • Set Embed in Application to your iOS app target (e.g. myapp_iOS)
  • For the basic plugin setup, disable:
    • Include Live Activity
    • Include Control
    • Include Configuration App Intent

Step 3: Add the TauriWidgets Swift Package

In Xcode:

  1. File → Add Package Dependencies...
  2. Click Add Local...
  3. Select the package folder:
    • for typical app projects: node_modules/tauri-plugin-widgets-api/swift/
    • for this repository example: swift/ (repository root)
  4. In Add to Target, choose your widget target (WidgetExtension / WidgetExtensionExtension).

Important: TauriWidgets must be linked to the widget target itself. If it is linked only to the main iOS app target, import TauriWidgets fails with no such module.

After creating the Widget Extension target, run:

bash
npx tauri-plugin-widgets-api init-ios

This updates the generated src-tauri/gen/apple/*/*.swift widget entry file from the plugin template using the auto-generated App Group. It also adapts the widget struct name to match Xcode-generated *Bundle.swift references, preventing cannot find 'WidgetExtension' in scope.

Manual fallback (if you prefer to edit/copy by hand):

swift
import SwiftUI
import WidgetKit
import TauriWidgets

struct MyWidgetEntryView: View {
    var entry: TauriWidgetEntry
    var body: some View {
        TauriWidgetView(entry: entry)
    }
}

@main
struct MyWidget: Widget {
    let kind = "ExampleWidget"
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: kind,
            // Use the same App Group that `init-ios` generated for your project.
            provider: TauriWidgetProvider(
                appGroup: "group.<your-tauri-identifier>",
                widgetId: "default" // must match JS setWidgetConfig widgetId
            )
        ) { entry in
            MyWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("Powered by TauriWidgets")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

Or copy from node_modules/tauri-plugin-widgets-api/templates/ios-widget/MyWidget.swift (plugin repo path: templates/ios-widget/MyWidget.swift).

Step 5: App Groups (portal + entitlements)

Widgets on a physical iOS device require a paid Apple Developer account — free provisioning cannot grant App Groups. The Simulator works without a paid seat.

init-ios writes both .entitlements under src-tauri/gen/apple/ and sets CODE_SIGN_ENTITLEMENTS on the app + widget targets. You still must register the group on the portal (Apple does not allow automating that):

  1. Open Identifiers.
  2. Register App Group = the id printed by init-ios (e.g. group.com.example.myapp).
  3. Enable App Groups on both App IDs (main + *.WidgetExtension) and tick that group.

Full checklist and “id not available” notes: Apple data transport → App Groups & signing.

In Xcode you only need a valid Team on both targets (Automatic signing). Capability UI is optional once entitlements + portal match.

Step 6: Plugin config (tauri.conf.json)

init-ios writes plugins.widgets with transport: "appGroup" (required on iOS):

json
{
  "plugins": {
    "widgets": {
      "appGroup": "group.<your-tauri-identifier>",
      "transport": "appGroup"
    }
  }
}

JS setWidgetConfig(..., group, widgetId) must use that same group string.

Also keep Swift TauriWidgetProvider(..., widgetId:) aligned with the JS widgetId (CLI default is "default"). If your app writes widgetId: "weather", pass widgetId: "weather" into the provider or the extension reads config:default while the host wrote config:weather.

Step 7: Run

bash
pnpm tauri ios dev

When it fails

Widget shows "No configuration"

  • Ensure the App Group identifier is identical in the main app, widget extension, plugins.widgets.appGroup, and the JS group argument.
  • Ensure Swift widgetId matches JS widgetId (default "default").
  • Call setWidgetConfig(...) from your app before adding the widget.
  • Confirm plugin init succeeded (missing plugins.widgets.appGroup aborts startup).

Widget doesn't update

  • Apple limits widget refreshes to ~40–70 per day.
  • Check plugin throttle: TAURI_WIDGET_MIN_RELOAD_SECS (Debug default 0, Release default 900). Inspect ApplyOutcome.reload from setWidgetConfig — throttled reloads no longer look like success-only true.
  • For testing: in Xcode, use Debug → Simulate Timeline → After Refresh.
  • For live counters, use { type: "timer" } instead of frequent reloads.

More symptoms: Troubleshooting index. Transport: Apple data transport.

Released under the MIT License. · Contributing