DEV Community

孫昊
孫昊

Posted on • Originally published at dev.to

When Apple's altool can't determine the Apple ID from your widget bundle

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'
Enter fullscreen mode Exit fullscreen mode

You probably:

  1. Created a YourAppWidget target in your Xcode project with bundle ID com.yourcompany.yourapp.widget.
  2. Registered the widget bundle in Apple Developer Portal via POST /v1/bundleIds (or manually clicked through the web UI).
  3. Added the right entitlements (com.apple.security.application-groups, com.apple.developer.icloud-services if needed).
  4. fastlane match generated profiles for both com.yourcompany.yourapp AND com.yourcompany.yourapp.widget.
  5. xcodebuild archive succeeded. .ipa is valid.
  6. altool --upload-app fails 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,
)
Enter fullscreen mode Exit fullscreen mode

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:

  1. project.yml main target dependencies: - target: YourAppWidget
  2. project.yml scheme targets: YourAppWidget: all
  3. Fastfile sync_code_signing app_identifier: [BUNDLE_ID, WIDGET_BUNDLE_ID]app_identifier: [BUNDLE_ID]
  4. Fastfile widget profile resolve + update_code_signing_settings block
  5. Fastfile build_app export_options.provisioningProfiles WIDGET_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 iCloud capability to the widget bundle (Apple Portal POST).
  • Stripping iCloud capability and trying again.
  • Multiple apple_id / app_identifier permutations 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")
  }
Enter fullscreen mode Exit fullscreen mode

The project.yml diff:

  DaysUntil:
    type: application
    ...
-   dependencies:
-     - target: DaysUntilWidget

  schemes:
    DaysUntil:
      build:
        targets:
          DaysUntil: all
-         DaysUntilWidget: all
          DaysUntilTests: [test]
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)