How a missing entitlement, a corrupted project file, and a few wrong paths cost hours of debugging and exactly how i fixed every single one.

The Problem

You've set everything up. Firebase is configured. Your APNs key is uploaded. Push Notifications are enabled on the Apple Developer Portal. You run your Flutter app on a real device and immediately see this in your logs:

❌ APNs REGISTRATION FAILED: no valid "aps-environment" entitlement string found for application
❌ Full error: Error Domain=NSCocoaErrorDomain Code=3000 "no valid "aps-environment"
entitlement string found for application"
UserInfo={NSLocalizedDescription=no valid "aps-environment" entitlement string found
for application}

Frustrating. Everything looks right, yet nothing works. This article documents the exact journey of debugging this error from start to finish — every wrong turn, every fix, and every lesson learned.

The App Setup

  • Framework: Flutter
  • Bundle ID: com.exmple.myapp.you
  • Team: xxxxxxx
  • Push service: Firebase Cloud Messaging (FCM)
  • APNs key: Configured on both Apple Developer Portal and Firebase Console

The AppDelegate.swift was already properly written to handle APNs registration and forward tokens to Firebase:

override func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
  FirebaseApp.configure()
  Messaging.messaging().delegate = self
  UNUserNotificationCenter.current().delegate = self
  application.registerForRemoteNotifications()
  GeneratedPluginRegistrant.register(with: self)
  return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(
  _ application: UIApplication,
  didFailToRegisterForRemoteNotificationsWithError error: Error
) {
  print("❌ APNs REGISTRATION FAILED: \(error.localizedDescription)")
  print("❌ Full error: \(error)")
}

All of this was correct. So what was wrong?

Root Cause #1: Push Notifications Capability Was Missing

The first thing to check when you see this error is whether the Push Notifications capability is actually added to your Xcode target.

Opening Signing & Capabilities revealed that only Background Modes → Remote notifications were checked, but the Push Notifications capability itself was never added. These are two completely different things:

  • Background Modes → Remote notifications: Allows the app to wake up in the background when a push arrives.
  • Push Notifications capability: Grants the aps-environment entitlement that Apple requires before your app can even register with APNs.

Fix:

  1. Go to Signing & Capabilities in Xcode
  2. Click + Capability
  3. Search for and add Push Notifications

After adding it, Xcode showed Push Notifications (Profile) — note the (Profile) suffix. This means the capability was added in Xcode but the provisioning profile hadn't been updated yet to include it.

Root Cause #2: The Entitlements File Didn't Exist on Disk

Even after adding the capability, the error persisted. Xcode was referencing a file called RunnerProfile.entitlements in Build Settings, but the file had never actually been created on disk.

This is a surprisingly common Xcode quirk; it can add a reference to an entitlements file in the project without ever writing the actual file to the filesystem.

Verification:

ls -la /Users/michael/StudioProjects/Billp-App/ios/Runner/RunnerProfile.entitlements
# ls: No such file or directory

Confirmed the file didn't exist.

Fix: Create the file manually:

cat > /Users/michael/StudioProjects/Billp-App/ios/Runner/RunnerProfile.entitlements << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>aps-environment</key>
    <string>development</string>
</dict>
</plist>
EOF

Root Cause #3: Build Settings Only Pointed to Profile Scheme

Even with the file created, there was another problem. In Build Settings → CODE_SIGN_ENTITLEMENTS, the entitlements file was only linked for the Profile scheme, not for Debug or Release.

This meant:

  • Running on device (Debug) → No entitlements → APNs fails ❌
  • Archiving (Release) → No entitlements → Archive fails ❌

Fix:

Go to Build Settings → search CODE_SIGN_ENTITLEMENTS → set all three rows to the same value:

Scheme Value Debug Runner/RunnerProfile.entitlements Profile Runner/RunnerProfile.entitlements Release Runner/RunnerProfile.entitlements

Root Cause #4: Corrupted project.pbxproj (The Worst One)

This is where things went sideways. In an attempt to clean up a trailing character issue in the entitlements path, a The sed command was run to strip trailing characters:

# This command was too aggressive
sed -i '' 's|Runner/RunnerProfile.entitlements[^"]*|Runner/RunnerProfile.entitlements|g' \
  Runner.xcodeproj/project.pbxproj

The [^"]* pattern matched and removed the semicolons at the end of each CODE_SIGN_ENTITLEMENTS line, corrupting the project.pbxproj file. The result:

xcodebuild: error: Unable to read project 'Runner.xcodeproj'.
Reason: The project 'Runner' is damaged and cannot be opened due to a parse error.
CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in
dictionary on line 505.

Xcode couldn't open the project at all.

Fix: Restore the missing semicolons:

sed -i '' \
  's|CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements$|CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;|g' \
  /Users/michael/StudioProjects/Billp-App/ios/Runner.xcodeproj/project.pbxproj

Verify all three lines end with a semicolon:

grep "CODE_SIGN_ENTITLEMENTS" Runner.xcodeproj/project.pbxproj
# CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
# CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
# CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;

Root Cause #5: development vs production for Archive Builds

After fixing the project file, the app ran successfully in debug mode — APNs TOKEN ARRIVED Finally appeared in the logs. But archiving for App Store submission still failed because the entitlements file had development instead of production.

Apple requires:

  • development for debug/testing builds
  • production for App Store / TestFlight builds

Fix for archiving:

sed -i '' \
  's/<string>development<\/string>/<string>production<\/string>/' \
  /Users/michael/StudioProjects/Billp-App/ios/Runner/RunnerProfile.entitlements

After archiving, switch back to development:

sed -i '' \
  's/<string>production<\/string>/<string>development<\/string>/' \
  /Users/michael/StudioProjects/Billp-App/ios/Runner/RunnerProfile.entitlements

The Proper Long-Term Setup

To avoid manually switching between development and production, the recommended approach is to have separate entitlements files per scheme:

Runner.entitlements (for Debug):

<key>aps-environment</key>
<string>development</string>

RunnerProfile.entitlements (for Release/Archive):

<key>aps-environment</key>
<string>production</string>

Then in Build Settings → CODE_SIGN_ENTITLEMENTS:

Scheme File Debug Runner/Runner.entitlements Profile Runner/RunnerProfile.entitlements Release Runner/RunnerProfile.entitlements

Complete Checklist

Use this checklist any time you hit APNs registration issues on iOS:

  • Apple Developer Portal: Push Notifications enabled for your App ID
  • Xcode Signing & Capabilities: Push Notifications capability added (not just Background Modes)
  • Entitlements file exists on disk: Not just referenced in Xcode, but physically present
  • Entitlements file content: Contains aps-environment key with correct value
  • Build Settings: CODE_SIGN_ENTITLEMENTS set for Debug, Profile, AND Release
  • Semicolons present: Every line in project.pbxproj ends with ;
  • Firebase Console: APNs Auth Key uploaded with correct Key ID and Team ID
  • Testing on real device: APNs does not work on Simulator
  • Bundle ID matches: Xcode bundle ID matches Apple Developer Portal App ID exactly
  • development vs production: Correct value for your build type

Key Takeaways

1. Background Modes ≠ Push Notifications capability. You need both. Remote notifications in Background Modes allow background wake-up. The Push Notifications capability grants the actual APNs entitlement.

2. Xcode can reference files that don't exist. Always verify the entitlements file physically exists on disk, not just in Xcode's project navigator.

3. Be careful with sed on project.pbxproj. The project file uses semicolons as delimiters. Any regex that strips trailing characters can accidentally remove them and corrupt the entire project.

4. Always open Flutter iOS projects as .xcworkspace, not .xcodeproj. After pod install, The workspace file is the only correct entry point. Opening .xcodeproj directly will fail or behave unexpectedly.

5. The The (Profile) label in Signing & Capabilities is a warning. If a capability shows (Profile) next to its name, it means Xcode added it but the provisioning profile hasn't synced yet. Force a refresh via Xcode → Settings → Accounts → Download Manual Profiles.

Final Result

After working through all five root causes, the app logs finally showed:

🚀 AppDelegate: didFinishLaunching called
🔥 Firebase configured successfully
📨 Messaging delegate set
🔔 Notification center delegate set
📲 registerForRemoteNotifications called
✅ APNs TOKEN ARRIVED: a1b2c3d4e5f6...
✅ APNs token forwarded to Firebase Messaging
✅ FCM Token received in AppDelegate: fXyZ...

Push notifications are working. Archive building. App Store submission ready.

If this article helped you, consider clapping and sharing it — APNs debugging is painful and more developers need a clear guide through it.