</>CodeWithKarani

App Store Guideline 2.3.1 Rejections: It's Rarely Your Text

Karani GeoffreyKarani Geoffrey8 min read

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.

  1. No hidden or dormant functionality. A JavaScript bundle you can replace after approval is, from a reviewer's point of view, dormant functionality.
  2. Described with specificity. "Bug fixes and improvements" in Notes for Review is explicitly called out as insufficient.
  3. Accessible for review. If the reviewer cannot reach a screen, it is undocumented as far as they are concerned.
Guideline 2.3.1 rejection Over-the-air updates Expo Updates, CodePush, remote JS bundles Declare it, in detail Debug artifacts DEBUG banner, dev menu, simulator status bar Ship a real release build Gated features Feature flags, remote config, role-based screens Enable for the review account Unreachable screens No demo account, OTP to a number they cannot use Give credentials and a path Not on this diagram: your app description, subtitle, keywords or promotional text. Those are 2.3.7 and 2.3.10 territory, and they are rarely what 2.3.1 is complaining about. First move: open Resolution Center and look for the screenshot the reviewer attached. It usually names the exact screen, and the email notification does not include it.
Two of these four are created by the cross-platform toolchain itself, which is why the rejection clusters in React Native and Flutter apps.

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 forWhy it fails
Red DEBUG ribbon in the cornerDebug build, which suggests the submitted binary is not what users get
Android navigation bar or Material back arrow2.3.10, imagery of another mobile platform
Simulator status bar with a placeholder carrier or 9:41 on the wrong device sizeReads as not a real device, and wrong sizes get rejected outright
The words Android, Google Play, or a competing store2.3.10 explicitly prohibits naming other platforms or marketplaces
Screens showing features that are behind a flag2.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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
#App Store#React Native#Flutter#iOS#App Review
Keep reading

Related articles