iOS setup
Step 1: Initialize the iOS project
pnpm tauri ios initStep 2: Add a Widget Extension target
Open the generated Xcode project:
open src-tauri/gen/apple/*.xcodeprojIn Xcode: File → New → Target → Widget Extension. Name it (e.g. WidgetExtension), language: Swift.
In the Choose options for your new target dialog:
- Set
Product NametoWidgetExtension(or your widget name) - Select your Apple
Team(e.g. Personal Team) - Keep
Projectas your current iOS project (e.g.myapp) - Set
Embed in Applicationto your iOS app target (e.g.myapp_iOS) - For the basic plugin setup, disable:
Include Live ActivityInclude ControlInclude Configuration App Intent
Step 3: Add the TauriWidgets Swift Package
In Xcode:
- File → Add Package Dependencies...
- Click Add Local...
- Select the package folder:
- for typical app projects:
node_modules/tauri-plugin-widgets-api/swift/ - for this repository example:
swift/(repository root)
- for typical app projects:
- 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.
Step 4: Sync the widget code via CLI (recommended)
After creating the Widget Extension target, run:
npx tauri-plugin-widgets-api init-iosThis 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):
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):
- Open Identifiers.
- Register App Group = the id printed by
init-ios(e.g.group.com.example.myapp). - 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):
{
"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
pnpm tauri ios devWhen it fails
Widget shows "No configuration"
- Ensure the App Group identifier is identical in the main app, widget extension,
plugins.widgets.appGroup, and the JSgroupargument. - Ensure Swift
widgetIdmatches JSwidgetId(default"default"). - Call
setWidgetConfig(...)from your app before adding the widget. - Confirm plugin init succeeded (missing
plugins.widgets.appGroupaborts startup).
Widget doesn't update
- Apple limits widget refreshes to ~40–70 per day.
- Check plugin throttle:
TAURI_WIDGET_MIN_RELOAD_SECS(Debug default0, Release default900). InspectApplyOutcome.reloadfromsetWidgetConfig— throttled reloads no longer look like success-onlytrue. - 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.
