If you've added a WidgetKit extension to an iOS app that was previously widget-less, and your CI just started failing with this error during the altool validation stage, you've hit the widget catch-22:
ERROR ITMS-90000: Cannot determine the Apple ID from Bundle ID
'com.yourcompany.yourapp.widget'
You probably:
- Created a
YourAppWidgettarget in your Xcode project with bundle IDcom.yourcompany.yourapp.widget. - Registered the widget bundle in Apple Developer Portal via
POST /v1/bundleIds(or manually clicked through the web UI). - Added the right entitlements (
com.apple.security.application-groups,com.apple.developer.icloud-servicesif needed). - fastlane match generated profiles for both
com.yourcompany.yourappANDcom.yourcompany.yourapp.widget. -
xcodebuild archivesucceeded..ipais valid. -
altool --upload-appfails with the error above.
You search for the error. Apple's docs say "make sure the bundle ID is registered." You verify it's registered. The error persists.
The catch-22
altool doesn't check Apple Developer Portal for the bundle. It checks iTunes Connect (now App Store Connect's app-association table). iTunes Connect has no record of your widget bundle, because iTunes Connect only creates app-association records after a successful upload containing that bundle.
So:
- altool refuses to upload until iTunes Connect knows the widget bundle.
- iTunes Connect won't know the widget bundle until altool successfully uploads.
The first widget upload on a previously-widget-less app is impossible.
Why explicit apple_id doesn't help
If you've found older Stack Overflow / Apple forum threads suggesting you pass explicit apple_id and app_identifier to your upload step (in our case fastlane's upload_to_testflight):
upload_to_testflight(
api_key: api_key,
apple_id: "6765669356", # main app's iTunes Connect numeric ID
app_identifier: BUNDLE_ID, # main bundle, not widget
skip_waiting_for_build_processing: true,
)
This works for some older Xcode + altool versions. In our case (Xcode 26.3 era, altool v2026), it didn't help. altool's per-bundle validation runs before it reads fastlane's high-level config — it queries iTunes Connect for every embedded bundle in the .ipa, regardless of what you pass at the upload step.
The workaround that worked
Ship two versions:
Version 1.0.x — widget-less
Strip the widget from the build entirely. Keep the widget's Swift code in the repo, but comment out:
-
project.ymlmain targetdependencies: - target: YourAppWidget -
project.ymlschemetargets: YourAppWidget: all -
Fastfilesync_code_signingapp_identifier: [BUNDLE_ID, WIDGET_BUNDLE_ID]→app_identifier: [BUNDLE_ID] -
Fastfilewidget profile resolve +update_code_signing_settingsblock -
Fastfilebuild_appexport_options.provisioningProfilesWIDGET_BUNDLE_ID => widget_profile_name
The resulting binary contains no widget. It matches your previous LIVE version's scope (assuming that was widget-less too — there's no UX regression for existing users).
Submit this widget-less version for review. Once it's READY_FOR_SALE on the App Store, your main app bundle is now associated in iTunes Connect with the same distribution cert your widget will eventually use.
Version 1.0.x+1 — widget re-enabled
Uncomment all 5 places above. Re-tag. CI builds with widget bundle embedded. altool probably now accepts it — because iTunes Connect now has a parent-app association on file for the same dist cert, and the widget bundle inherits that association when uploaded as an embedded extension of the same .ipa.
I say "probably" because this is the workaround I'm running today. The previous version (1.0.2 widget-less) hit READY_FOR_SALE 30 minutes ago. The next CI build (v1.0.15 with widget re-enabled) is running. We'll know in 10 minutes.
If it succeeds: documented workaround.
If it fails: drop the widget extension entirely. Ship the same UI as a Live Activity (which lives in the main bundle).
Why I'm writing this before the verdict is in
Because if the workaround fails, the existence of the catch-22 itself is the documented lesson. And if the workaround succeeds, this post tells future you (and me) how to spend 10 minutes instead of 4 days.
The 4 days I lost were spent trying:
- Adding
iCloudcapability to the widget bundle (Apple Portal POST). - Stripping
iCloudcapability and trying again. - Multiple
apple_id/app_identifierpermutations in fastlane. - Different Xcode versions (downgraded back up).
- Manually creating the widget bundle in App Store Connect (you can't — there's no UI for it).
None of those worked. The only thing that worked was: ship the main app LIVE first, then add the widget.
Code reference
The exact Fastfile diff that strips the widget for v1.0.2 widget-less:
- sync_code_signing(app_identifier: [BUNDLE_ID, WIDGET_BUNDLE_ID])
+ sync_code_signing(app_identifier: [BUNDLE_ID])
- update_code_signing_settings(
- bundle_identifier: WIDGET_BUNDLE_ID,
- profile_name: widget_profile_name,
- ...
- )
export_options: {
provisioningProfiles: {
- BUNDLE_ID => real_profile_name,
- WIDGET_BUNDLE_ID => widget_profile_name
+ BUNDLE_ID => real_profile_name
},
signingStyle: "manual",
teamID: ENV.fetch("TEAM_ID")
}
The project.yml diff:
DaysUntil:
type: application
...
- dependencies:
- - target: DaysUntilWidget
schemes:
DaysUntil:
build:
targets:
DaysUntil: all
- DaysUntilWidget: all
DaysUntilTests: [test]
Reverting these (uncomment everything) is v1.0.15 / v1.0.3 / whatever your next tag is.
What about a brand-new app with a widget?
A new app submission (first version ever) shouldn't hit this catch-22 — there's no prior LIVE version, so iTunes Connect creates the app + widget association together on first review approval. The catch-22 only applies when you're adding a widget to an app that already shipped without one.
If you're starting a new app fresh and want a widget from v1.0, include it from the very first build. You won't hit this.
If you're upgrading an app that shipped widget-less, you'll hit this — and the workaround is to keep shipping widget-less until you have a LIVE version on the new cert, then add the widget in the next version.
TL;DR
First widget upload after widget-less LIVE version = impossible.
Workaround:
1. Strip widget from build (5 places: project.yml + Fastfile).
2. Ship + get LIVE on App Store.
3. Add widget back, ship next version.
The catch-22 exists. Don't waste 4 days like I did.
If you're an indie iOS dev hitting WidgetKit traps, ping me. I keep a list of these in CLAUDE.md for my own project.
Tags: ios, swift, fastlane, appstore
Hit similar Apple Review trap? I run a $249 iOS Audit Sprint — 60-min Zoom + 3-page written audit + 14-day refund. Book a 15-min call (free, no commitment) or grab the $29 TestFlight Debug Bible.
Top comments (0)