App Store Guideline 2.3.1 Rejections: It's Rarely Your Text
The rejection lands overnight, which for a team in Nairobi means you read it at 6am with a client demo at 10. It says Guideline 2.3.1, Performance, Accurate Metadata. So you open App Store Connect, rewrite the description, trim the keyword field, soften the subtitle, and resubmit. Two days later, the same rejection.
You spent two review cycles editing text that was never the problem. Guideline 2.3.1 is not about your description. Read what it actually says: "Don't include any hidden, dormant, or undocumented features in your app; your app's functionality should be clear to end users and App Review." That is a hidden functionality rule. It lives under a heading called Accurate Metadata, and that heading has probably cost the mobile industry more wasted review cycles than any other four words Apple has written.
For React Native and Flutter apps specifically, there are three things that trip it, and none of them are in your listing copy.
2.3.1 means App Review believes there is functionality they could not see or that could change after approval. Check these before touching your description.
- Over-the-air JavaScript update tooling (Expo Updates, CodePush and similar) that you did not declare in Notes for Review.
- A debug build artifact visible in your screenshots or the build itself: Flutter's DEBUG banner, React Native's dev overlays, a simulator status bar.
- Feature flags or remote config that hide functionality from the reviewer's account.
- Any screen the reviewer could not reach: no demo account, a paywall, an OTP to a Kenyan number they cannot receive.
- Non-Apple platform imagery in screenshots or metadata, which is the neighbouring 2.3.10 rejection.
The rejection, verbatim
Guideline 2.3.1 - Performance - Accurate Metadata
That header is what everybody searches for and it is the least useful part of the message. The paragraph underneath it is the part that matters, and it varies: sometimes it names a specific screen, sometimes it mentions code that can be changed after review, sometimes App Review attaches a screenshot. In App Store Connect, open Resolution Center and look for an attached image before you read anything else. Reviewers frequently attach the exact screen that failed, and teams routinely miss it because they read the email notification instead of the actual message thread.
What guideline 2.3.1 actually says
Straight from the review guidelines:
Don't include any hidden, dormant, or undocumented features in your app; your app's functionality should be clear to end users and App Review. All new features, functionality, and product changes must be described with specificity in the Notes for Review section of App Store Connect (generic descriptions will be rejected) and accessible for review.
Three obligations, and cross-platform tooling makes each one harder to meet.
- No hidden or dormant functionality. A JavaScript bundle you can replace after approval is, from a reviewer's point of view, dormant functionality.
- Described with specificity. "Bug fixes and improvements" in Notes for Review is explicitly called out as insufficient.
- Accessible for review. If the reviewer cannot reach a screen, it is undocumented as far as they are concerned.
The fix, step by step
Step 1: Read the message in Resolution Center, not the email
Reply and ask a direct question if the message is ambiguous: which screen, which feature, which build. A short, specific reply gets a specific answer more often than people expect, and it costs you a day instead of a whole review cycle spent guessing.
Step 2: Prove the submitted build is a release build
Flutter's debug banner and React Native's development overlays only appear in debug builds, so if a reviewer saw one, a debug artifact reached the archive. This happens most often when someone builds locally with a misconfigured scheme rather than through CI.
# Flutter: release is the default for this command, but be explicit
flutter build ipa --release
# React Native: archive with the Release configuration, not Debug
xcodebuild -workspace ios/App.xcworkspace -scheme App \
-configuration Release -archivePath build/App.xcarchive archive
Then verify on a device, not in a simulator. Install the exact TestFlight build a reviewer would receive and check that there is no banner, no shake-to-open developer menu, and no yellow warning boxes.
Step 3: Audit every screenshot as if you were the reviewer
| Look for | Why it fails |
|---|---|
| Red DEBUG ribbon in the corner | Debug build, which suggests the submitted binary is not what users get |
| Android navigation bar or Material back arrow | 2.3.10, imagery of another mobile platform |
| Simulator status bar with a placeholder carrier or 9:41 on the wrong device size | Reads as not a real device, and wrong sizes get rejected outright |
| The words Android, Google Play, or a competing store | 2.3.10 explicitly prohibits naming other platforms or marketplaces |
| Screens showing features that are behind a flag | 2.3.1, functionality shown in metadata that the reviewer cannot reach |
| Lorem ipsum, placeholder avatars, "Coming soon" | Reads as incomplete or dormant functionality |
The Android navigation bar one catches cross-platform teams constantly, because the fastest way to produce six screenshots is to reuse the Play Store set.
Step 4: Declare the over-the-air update mechanism
If your app can download and run a new JavaScript bundle after approval, say so, and say what it can and cannot change. Apple's own agreement permits interpreted code to be downloaded and run in specific circumstances, provided it does not materially change the app's primary purpose. Reviewers are not objecting to the technology, they are objecting to finding it undeclared.
Write it in Notes for Review with the specificity the guideline demands. Something like: this app uses an over-the-air update mechanism to deliver JavaScript bug fixes only; it cannot add features, change the app's purpose, or enable functionality not present in this submitted build; the update endpoint is under our control at a named domain. Name the library and the version.
If you are being rejected repeatedly and cannot get past it, the pragmatic move some teams take is shipping review builds with the update channel disabled, then enabling it after approval. Understand the trade: that is closer to the letter of the rule than the spirit of it, and if it is discovered, the escalation is worse than the original rejection. Disclosure is the better path.
Step 5: Turn on everything for the review account
Feature flags default to off for new accounts, which means the demo account you supplied sees a fraction of the app. Before submitting, sign in as the review account on a clean device and walk every flow mentioned in your screenshots and your release notes.
Fill in the demo account fields properly. If sign-in requires an SMS OTP, the reviewer cannot receive one on a Kenyan or Nigerian number, so provide a bypass code and explain it in the notes. This single issue delays more African-built apps than anything else in the process.
Step 6: Match the names
The App Store display name and the name under the icon on the home screen should be recognisably the same
thing. CFBundleDisplayName in Info.plist is what a user sees on device. If the store
says "Ishi Property Manager" and the icon says "ishi-app-prod", that mismatch is a legitimate accurate metadata
finding.
Verification before you resubmit
Do this on a physical device with the TestFlight build, signed in as the review account, with no development tooling attached:
[ ] No debug banner, dev menu, or warning overlay anywhere
[ ] Every screenshot corresponds to a screen this account can actually reach
[ ] Demo credentials work on a clean install, first try
[ ] Any OTP or 2FA step has a documented bypass for review
[ ] Notes for Review names each new feature specifically, not "improvements"
[ ] OTA update mechanism disclosed by name and scope
[ ] No Android or other-platform imagery in any screenshot or preview video
[ ] Home screen app name matches the App Store name
You know it worked when the next status is In Review and then Ready for Distribution. There is no partial signal in between, which is exactly why the pre-submission checklist matters more here than in almost any other release process.
What people get wrong
- Rewriting the description. The single most common response and almost always wasted. If the listing text were the problem, the rejection would usually cite 2.3.7 for keywords or 2.3.10 for irrelevant metadata.
- Resubmitting unchanged, hoping for a different reviewer. This works occasionally, which is the worst possible outcome, because it teaches the team that the process is random and stops them fixing the real issue. It will be rejected again on the next update.
- Appealing immediately. The App Review Board exists and is worth using, but appealing before you have identified the specific screen means arguing without evidence. Ask in Resolution Center first.
- Removing the OTA library in a panic. Sometimes necessary, usually not. Try disclosure with specificity first, because ripping out your update pipeline costs weeks and removes the ability to ship a hotfix.
- Treating it as an Apple problem. Two of the four common causes are debug artifacts and unreachable screens. Both are ours.
When it is still rejected
- Ask for the specific screen in writing. "Could you confirm which screen or feature this refers to" is a reasonable request and reviewers do answer it.
- Audit third-party SDKs. An analytics or A/B testing SDK that downloads and executes configuration can read as remotely changeable behaviour. List every SDK and what it fetches at runtime.
- Check for leftover test entry points. A hidden debug menu behind a five-tap gesture on the version label is, precisely and unarguably, a hidden feature.
- Escalate to the App Review Board once you have a specific disagreement rather than a general one, and include what you changed and why you believe it complies.
The mental shift that fixes this permanently: stop reading 2.3.1 as "your metadata is inaccurate" and start reading it as "we think there is something in this app we cannot see". Then the fix is always the same, make it visible and write it down, and it stops being a mystery rejection.
Frequently asked questions
- What does Guideline 2.3.1 - Performance - Accurate Metadata actually mean?
- Despite the Accurate Metadata heading, 2.3.1 is a hidden functionality rule. It says not to include hidden, dormant or undocumented features, that all new features must be described with specificity in Notes for Review, and that they must be accessible for review. A 2.3.1 rejection almost always means App Review believes there is functionality they could not see or that could change after approval, not that your app description is wrong.
- Does CodePush or Expo Updates cause App Store rejections?
- Over-the-air JavaScript updating is not banned, but reviewers flag it when it is undeclared because a replaceable bundle looks like dormant functionality. Declare it in Notes for Review by name, state that it delivers bug fixes only and cannot change the app's purpose or enable features absent from the submitted build, and name the endpoint you control. Disclosure clears this far more often than removing the library.
- Why do Flutter and React Native apps hit 2.3.1 more often?
- Their tooling injects development-only UI that can leak into a submission, such as Flutter's DEBUG banner or React Native's dev overlays, and their teams often reuse Play Store screenshots that contain Android navigation bars. Both frameworks also commonly ship over-the-air update mechanisms. None of these exist in a purely native iOS submission, which is why the rejection clusters here.
- Should I appeal a 2.3.1 rejection or resubmit?
- Reply in Resolution Center first and ask which specific screen or feature the rejection refers to, because reviewers frequently attach a screenshot that never reaches your email notification. Fix that specific thing, then resubmit. Escalate to the App Review Board only once you have a concrete disagreement, not as an opening move.