Safari Web Extension popup never opens on iOS 26 — silent failure with all resources signed and bundled

I'm distributing a Safari Web Extension iOS app via TestFlight (built from a Chrome MV3 extension via xcrun safari-web-extension-converter on Xcode 26.0.1). The extension installs and registers correctly, but its popup never opens when the toolbar item is tapped. The behavior is silent — no error, no flash of UI, no console output. The Safari "ᴀA" menu just closes and the user is back at the article.

WHAT WORKS:

  • Extension appears in Settings → Safari → Extensions, can be enabled
  • Permissions can be granted ("Always Allow on Every Website")
  • Extension appears as expected in Safari's "ᴀA" address-bar menu

WHAT DOESN'T WORK:

  • Tapping the extension item in the AA menu produces no popup, no error, no visible response of any kind. Same on iPhone and iPad, both on iOS 26.

WHAT I'VE VERIFIED VIA IPA INSPECTION:

  • Extension .appex contains: manifest.json, popup.html, popup.js, background.js, content.js, and images/ with all icons
  • _CodeSignature/CodeResources files2 lists 11 entries — every web extension resource is signed
  • manifest declares: "action": { "default_popup": "popup.html", "default_icon": {...} }
  • Extension Info.plist has standard NSExtension dict: NSExtensionPointIdentifier = com.apple.Safari.web-extension NSExtensionPrincipalClass = (My)_Extension.SafariWebExtensionHandler

ISOLATION TEST: To rule out my popup code, I replaced popup.html with a 506-byte file containing only a static green box and "Hello World" text — no scripts, no images, no external references. This minimal popup ALSO fails to open with the same silent behavior. So this is not a script error or content issue.

MANIFEST DETAILS (relevant excerpts): { "manifest_version": 3, "action": { "default_popup": "popup.html", "default_icon": {...} }, "background": { "scripts": ["background.js"], "persistent": false }, "content_scripts": [{ "matches": ["<all_urls>"], "js": ["content.js"] }], "permissions": ["activeTab", "storage", "scripting"], "host_permissions": ["<all_urls>"] }

I previously had "background": { "service_worker": "background.js", "type": "module" } and switched to scripts/non-persistent based on prior forum advice about iOS Safari incompatibility with module-type service workers. No change in behavior either way.

ENVIRONMENT:

  • iOS 26 (iPhone and iPad — both affected)
  • Built on macos-15 GitHub Actions runner with Xcode 26.0.1
  • Distribution via TestFlight
  • Manifest version 3
  • Extension target produced by xcrun safari-web-extension-converter

I cannot easily provide a focused Xcode test project as my entire build pipeline runs on GitHub Actions (no local Mac access at this time). I can share the IPA, build pipeline configuration, and source repository.

Has anyone else seen silent popup failures on iOS 26 from converter-built Safari Web Extensions? Is there an Info.plist key, build setting, or NSExtension attribute the converter is missing that's needed for popups to render on iOS 26?

Thanks for any insight.

RESOLVED — for anyone who hits this

The popup opens correctly now. Posting the actual cause for posterity since this took a long time to track down and may help others using GitHub Actions to build Safari Web Extensions.

Root cause: The .appex bundle inside the IPA was missing all web extension files (popup.html, popup.js, background.js, content.js, images/) — only manifest.json was being included. The popup couldn't render because popup.html literally wasn't in the bundle. iOS Safari was correctly attempting to load the popup but had nothing to load, hence the silent failure with no error.

Why files were missing: Running "xcrun safari-web-extension-converter ." against the repository root caused the converter to recursively copy its own build output back into the source folder during processing. This led to inconsistent state where Xcode's archive step didn't pick up the original web extension files.

Fix: Stage web extension files into a clean subfolder before running the converter, then run the converter against that subfolder:

- name: Stage extension source
  run: |
    rm -rf extension-src
    mkdir -p extension-src
    for f in manifest.json popup.html popup.js background.js content.js; do
      if [ -f "$f" ]; then cp "$f" extension-src/; fi
    done
    if [ -d "Resources/images" ]; then
      mkdir -p extension-src/images
      cp Resources/images/*.png extension-src/images/ 2>/dev/null || true
    fi

- name: Convert web extension to Safari extension
  run: |
    xcrun safari-web-extension-converter ./extension-src \
      --project-location ./build \
      ...

We also added a post-archive step to explicitly copy web extension files into the .appex as a belt-and-suspenders measure:

- name: Inject web extension resources into archive
  run: |
    APPEX=$(find build/<App>.xcarchive -name "*.appex" | head -1)
    for f in manifest.json popup.html popup.js background.js content.js; do
      if [ -f "$f" ]; then cp "$f" "$APPEX/$f"; fi
    done

After both steps were in place, popup.html and the rest of the web extension files were properly bundled into the .appex (visible in _CodeSignature/CodeResources files2 list), and the popup rendered correctly on iOS 26.

Note on the manifest discussion in my original post: I had switched between "service_worker" and "scripts/persistent:false" forms during debugging based on forum advice. Both forms work on iOS 26 once the bundle is correct. The manifest format wasn't the cause of the silent failure — the missing files were.

Hope this helps anyone else hitting the same wall. The lesson for me: when a Safari Web Extension popup fails silently on iOS, inspect the .appex inside the IPA directly — don't trust that all your web extension files made it into the bundle just because the build was green.

Safari Web Extension popup never opens on iOS 26 — silent failure with all resources signed and bundled
 
 
Q