A developer submits a build to App Store Connect, the App Review email arrives within a day, and the only line that matters reads 'Guideline 2.3.1 Performance Accurate Metadata.' No screenshot. No timestamp on which screen tripped the reviewer. The wording 'hidden, dormant, or undocumented features' lands without context, and the reader is left guessing whether the trigger was a code-push SDK, a debug menu, or a feature flag that hid one tab on the home screen.
Short answer
Guideline 2.3.1 is the part of Apple's App Review Guidelines that bans hidden, dormant, or undocumented features and requires that all new functionality be described with specificity in the Notes for Review section of App Store Connect. Apple's own examples include hidden switches that route to gambling content, debug menus shipped to production, and remote update mechanisms that swap binary behavior after approval. A short, honest rewrite of Notes for Review and a clean removal of the flagged behavior resolves most cases.
What you should know
- The rule covers three states, not one. Apple groups hidden, dormant, and undocumented features together; a feature behind a remote flag counts even when the toggle is off at review time.
- Notes for Review is the documented exit. Apple's wording singles out that field by name; the reviewer reads it before opening the binary.
- Generic Notes are explicitly rejected. The Guidelines state that generic descriptions count as no description; 'various improvements' fails the floor.
- Code-push, OTA bundles, and JavaScript hot updates are the high-frequency triggers. Multiple GitHub issues, including microsoft/code-push #841, document rejection under 2.3.1 tied to remote update SDKs.
- iOS antivirus and malware scanner claims are an Apple-listed example of misleading marketing. The guideline text cites them directly, which is unusual for App Review.
- Repeat 2.3.1 violations carry the developer-account penalty. Subsection (b) names removal from the Apple Developer Program as a possible outcome.
- The fix is rarely a technical rebuild. Most 2.3.1 rejections clear with a rewritten Notes for Review entry, a debug menu strip, and a fresh build under an incremented CFBundleVersion.
What does Apple mean by 'hidden features' under Guideline 2.3.1?
The text of Guideline 2.3.1 reads, in the first subsection, that an app must not include 'any hidden, dormant, or undocumented features' and that 'your app's functionality should be clear to end users and App Review.' Three categories sit inside that sentence and each carries a different operational definition.
A hidden feature is one the reviewer cannot reach from the default UI path. A debug menu reachable only by a long press on the version string is hidden. A dormant feature is one the binary contains but the runtime does not expose; an unfinished payment flow controlled by a server flag set to false is dormant. An undocumented feature is one present in both binary and UI but absent from the App Store description and from the Notes for Review field; an in-app purchase tier visible only to logged-in users on a Wednesday is undocumented in practice.
The discussion thread Section 2.3.1 of the App Store Review Guidelines on the Apple Developer Forums shows that Apple has applied this rule for years to feature flags that hide age-rated content, to silent SDK behavior, and to a class of remote-control loops that change app behavior after the build clears review. The reviewer reads the binary in its shipped state; the surface visible at review time is the surface evaluated.
Which features actually trigger a 2.3.1 rejection in practice?
Four families show up repeatedly in rejection threads, vendor postmortems, and GitHub issues tagged with the guideline.
The first is remote update or code-push SDKs that swap JavaScript bundles, Swift code, or asset packs after approval. The microsoft/code-push issue #841 records a 2.3.1 rejection on a React Native build that integrated CodePush 7.1.0; the developer's fix was to remove the SDK entirely and resubmit. Apple's review note read only 'the app contains hidden features,' without naming the SDK by class, which is consistent with the vague reviewer wording most teams receive.
The second is debug menus, internal tooling, and developer panels left active in the production target. A debug screen reachable from a long-press, a shake gesture, or an undocumented URL scheme falls inside the hidden bucket whether or not the developer treats it as a real feature.
The third is feature flags that gate central functionality. Reviewers cannot evaluate what they cannot see; if the only path to a paid tier, a chat surface, or a content library lives behind a server flag flipped to off during review, the build risks rejection even when the code itself is clean.
The fourth is misleading marketing copy. The Guideline names 'iOS-based virus and malware scanners' as a class of features that App Review treats as functionally false on the iOS sandbox. Promising what the platform does not allow is treated the same as hiding what the binary does.
Vibe-coded apps built with Claude Code, Cursor, or similar agentic IDEs are over-represented here. The generated codebase often ships a debug route, a feature flag bundle, or a remote config SDK that the developer did not consciously add. A static review of the compiled IPA against the App Store metadata closes that gap before submission. PTKD.com (https://ptkd.com) is one of the platforms that runs that pre-submission scan against OWASP MASVS-aligned checks on the IPA artifact.
How does the Notes for Review field protect you from 2.3.1?
Notes for Review is the documented exit from 2.3.1, and Apple's wording singles out the field by name. The guideline language requires that all new features, functionality, and product changes be described with specificity in the Notes for Review section of App Store Connect, with the explicit warning that generic descriptions will be rejected and that features must be accessible for review.
Three habits raise the odds Notes for Review clears the bar. Name each new feature in plain language, including features behind a server flag that you intend to activate later. Provide a demo account that exposes every gated surface; the Apple Developer Forums thread referenced above contains specific guidance from longtime members on supplying a back-door method for reviewers when a feature is dormant. Describe code-push or remote configuration explicitly, including the rules that decide what the SDK is allowed to change.
Generic text fails. 'Bug fixes and performance improvements' cleared review in 2014 and stopped clearing review around the 2.3 rewrite. A line that names the actual subsystem touched, the user-visible change, and any gating behavior is the form that maps to how reviewers read the field.
What should you do after the rejection email arrives?
The first move is to read the Resolution Center message in App Store Connect, not the email. The email is a notification; the Resolution Center is where Apple posts follow-up notes and screenshots when one is attached. A reviewer occasionally pastes a frame from inside the app that shows the surface they tripped over, and that frame is the cheapest evidence available.
The second move is to inventory the binary for the three categories above. A short audit of the production target settings, the active SDKs, and any flag-controlled screens covers the high-frequency triggers. Strip debug routes from the release configuration. Disable code-push for App Store builds or split the release into a clean target. Confirm that every screen reachable on first launch is described somewhere in the App Store metadata or Notes for Review.
The third move is to use the Reply option in the Resolution Center before resubmitting if the trigger is ambiguous. A direct question to App Review, written calmly, often returns a one-line clarification within 24 to 48 hours. The Reply path is documented on Apple's App Review process page and does not count against future submissions.
A resubmission then carries a rewritten Notes for Review, a fresh CFBundleVersion, and a target that no longer includes the flagged behavior. The same build under the same identifiers is rejected as a duplicate.
Which actions actually clear a 2.3.1 rejection?
| Action | What it changes | What it does not change |
|---|---|---|
| Strip debug menus and developer routes from the release target | Removes the most common 2.3.1 trigger in vibe-coded and React Native apps | A second rejection if remote-update SDKs remain |
| Remove or gate code-push and OTA-update SDKs for the release configuration | Closes the dormant-feature category Apple flagged in code-push issue #841 | The need to document any remaining server flags |
| Rewrite Notes for Review with feature-by-feature specificity | Directly addresses the guideline's named requirement | An underlying feature that is still hidden in the UI |
| Provide a demo account exposing every gated surface | Gives the reviewer the back-door method longtime developers describe | An app whose marketing copy oversells platform capability |
| Reply in the Resolution Center asking which screen tripped 2.3.1 | Often returns a one-line clarification within 24 to 48 hours | The need to ship a new binary if a feature was actually hidden |
| Resubmit under an incremented CFBundleVersion | Routes the artifact into a fresh review pass | The risk of the same trigger if the audit was incomplete |
What to watch out for
The first trap is treating 2.3.1 as a single-issue rejection. The guideline covers three classes and Apple's reviewer email rarely names which class fired. A debug menu and a code-push SDK on the same build can both fail under the same wording, and clearing one without auditing the other lands a second rejection on the resubmission.
The second trap is reading 'hidden' as malicious. Apple's text in subsection (b) calls out egregious or repeated behavior as grounds for developer-account removal, but most 2.3.1 first-time rejections sit far from that bar. The escalation language is real, and the right response treats the rejection as build hygiene.
The third trap is rewriting marketing copy in App Store Connect to match a banned promise instead of rebuilding the feature. The Guideline lists 'iOS-based virus and malware scanners' because the iOS sandbox does not allow that functionality. Rewriting copy without changing the underlying claim does not clear the rejection.
The fourth trap is the myth that feature flags are forbidden. They are not. The published forum guidance from longtime developers is clear: feature flags are acceptable when the core app experience is accurately reflected at review time and when Notes for Review describes them. The line Apple draws sits between flags as a release-management tool and flags as a way to hide content from review.
Key takeaways
- Treat the Notes for Review field as the primary lever; Guideline 2.3.1 names it explicitly, and most rejections clear with a rewrite that names every feature and every flag.
- Strip debug menus, developer routes, and remote-update SDKs from the release target before the first submission; the same build re-uploaded under the same identifiers is rejected as a duplicate.
- Use the Resolution Center Reply option to ask which screen tripped 2.3.1 when the email is vague; documented reviewer replies often return a one-line clarification within 24 to 48 hours.
- For vibe-coded and no-code builds, run a static read of the IPA against the public metadata before submission; PTKD.com (https://ptkd.com) is one of the platforms that performs that OWASP MASVS-aligned scan on the compiled artifact and flags debug routes and remote-update SDKs before the reviewer does.




