Guideline 2.3.1
Guideline 2.3.1(a) - Performance: Review-Environment Detection via Remote Configuration
Our Take
Apple is rejecting your app under Guideline 2.3.1(a) because it found logic that detects whether the app has a configurable state meant for App Review, with that behavior switched on or off through remote configuration.
Guideline 2.3.1(a) says apps must not include hidden, dormant, or undocumented features, and that an app's functionality should be clear to both end users and App Review. Code that lets the app look one way during review and another way once it ships is the clearest case of that.
This is a more serious variant than the general 2.3.1 'hidden features' rejection. That one is usually about a feature nobody mentioned in the review notes, and better notes fix it. Here the problem is the detection mechanism itself. Review can only approve what it sees, so the guideline treats any review-specific code path as making the review unreliable, whatever the intent behind it.
The guideline also notes that egregious or repeated behavior can lead to removal from the Apple Developer Program, so treat this one as urgent.
Common triggers:
review_mode, isReview, audit, apple_review or hide_feature that the app checks at runtime.appStoreReceiptURL ending in sandboxReceipt) or a TestFlight/review build, then changing what the app shows.The reason behind the code doesn't change the outcome. Teams often add a 'review mode' for understandable reasons: skipping a login wall, hiding an unfinished screen, turning off ads or a paywall, or getting around a region lock. Apple has standard ways to handle each of these (demo accounts, review notes, StoreKit sandbox), and the review-specific branch has to go.
Resolution Guide
Find every review-detection path
Search the codebase and every bundled SDK for checks on remote-config keys (review, audit, isReview, review_mode, approved_version), comparisons between the app version and a server-supplied 'live' version, appStoreReceiptURL / sandboxReceipt checks, TestFlight detection, and IP/region/timezone conditions. Also check the remote-config console (Firebase Remote Config, your own config endpoint, a feature-flag vendor) for conditions targeting review builds, versions awaiting approval, or Apple IP ranges.
Delete the logic from the binary, not just the flag value
Setting review_mode to false on the server isn't enough. The branch is still in the binary and the key still shows up in the config payload, and either can be flagged again. Remove the code path that reads the flag and the behavior it gated.
Delete the remote-config keys and targeting conditions
Remove the keys, audiences and conditions that existed only to tell review apart from real users. Keep ordinary feature flags only if they apply to all users the same way and don't depend on the review environment, app-approval status or reviewer location.
Make every feature visible or remove it
Any feature that was hidden during review should ship enabled in the review build, or come out of the binary until it's ready. Shipping it now and enabling it later is exactly the pattern this rejection is about.
Replace 'review mode' with the standard mechanisms
Login wall: give a demo account in App Review Information. Paywall or IAP: StoreKit sandbox already handles review purchases. Region or carrier lock: explain it in the review notes and give a way to see the content. Unfinished screens: remove them from the build.
Audit third-party SDKs
If the detection came from an SDK, update to a version without it or remove the SDK. White-label app frameworks, some ad mediation layers and 'dynamic content' SDKs are frequent sources. Note the SDK and version you removed so you can say so in your reply.
Reply in App Review with specifics, then resubmit
Say what the detection was, that you removed it from the binary and the remote configuration, and that the app now behaves the same for every user. Be accurate. Apple can compare the new build and config against what it found.
Prevention
Example Rejection Email
Consider Appealing
Only appeal if the app truly has no review-specific behavior. For example, the flagged config key is an ordinary feature flag that applies to all users the same way, or a staged rollout that doesn't depend on review status, approval status or reviewer location. In that case, reply factually: name the key, explain exactly what it controls and who it applies to, and include a screen recording showing the same behavior in every environment. If the app or any bundled SDK changes behavior based on review, sandbox receipts, approval status or reviewer location, don't appeal. Remove the logic and resubmit. Disputing a finding that turns out to be accurate makes the situation worse.
Before & After
The app fetches review_mode from Firebase Remote Config. While it is true (set while a build is in review) the app hides the casino-style rewards screen and shows a plain catalog. The team flips it to false after approval.
The review_mode key and every code path that reads it are deleted. The rewards screen is either submitted openly with an explanation in the review notes or removed from the binary.
What changed: The fix is removing the mechanism that let the app behave differently in review. Turning the flag off leaves the detection logic in place.
On launch the app compares its own version with live_version from the server and unlocks a premium feature only when its version is at or below the already-approved one, so the build under review never shows it.
The version comparison is removed. The premium feature behaves the same in every build, and reviewers see it through a demo account listed in App Review Information.
What changed: Version gating against approval status is review-environment detection. A demo account is the standard way to show reviewers gated functionality.
A bundled ad-mediation SDK turns off certain ad formats when it sees a sandbox receipt or an IP in Apple's ranges.
The SDK is updated to a version without environment detection (or replaced), and the reply to App Review names the SDK and version removed.
What changed: Apple holds the app responsible for what its SDKs do. Naming the specific SDK you removed shows you understand the finding and resolved it.
Community Solutions · 0
Sign in to share your solution.
More Guideline 2 (Performance) rejections
- Guideline 2.1 - App Completeness: App Crashes During Review
- Guideline 2.1 - App Completeness: App Requires External Hardware Not Provided for Review
- Guideline 2.1 - App Completeness: Crashes on Launch
- Guideline 2.1 - App Completeness: Demo Account Not Working
- Guideline 2.1 - App Completeness: IAP Products Not Found in Binary
- Guideline 2.1 - App Completeness: Missing App Icon