store-review-check
Analyzes iOS project files and store listing data to identify App Store review rejection risks, mapping findings to Apple guideline sections and providing fix instructions.
Analyzes Android project files and store listing data to identify Google Play review rejection risks, mapping findings to Google Play guideline sections and providing fix instructions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@store-review-checkScan my Expo app for App Store and Google Play rejection risks"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
store-review-check
Find App Store and Google Play rejection risks in an iOS, Android, Flutter, React Native or Expo project before you submit it. It runs as a CLI and as an MCP server, so you can use it from a terminal, from CI, or from an AI coding assistant.
npx store-review-check ./my-appIt reads Info.plist, entitlements, PrivacyInfo.xcprivacy, StoreKit configuration files, AndroidManifest.xml, Gradle files and version catalogs, pubspec.yaml, package.json and Expo app.json, plus your store listing text if you provide it. Each finding names the guideline section, links to the official Apple or Google page and says how to fix the problem.
The rule set leans toward problems dating and social apps run into, such as user-generated content without report and block tools, missing account deletion, social login without Sign in with Apple and subscription terms. Most rules apply to any app.
There is no AI in the checks. The same project gives the same report every time, and nothing leaves your machine.
Contents
Related MCP server: VibeGuard MCP Server
What it checks
iOS permissions: if the code or a library uses the camera, photos, location, contacts, microphone, tracking and so on, the matching
NS…UsageDescriptionkey has to exist and say why. Empty, placeholder and Expo default strings are flagged too.iOS privacy manifest: whether the app has
PrivacyInfo.xcprivacy, and whether your own Swift and Objective-C code uses required reason APIs (UserDefaults, file timestamps, disk space, boot time, active keyboards) without declaring them.Accounts: account creation without in-app deletion, the Google Play web deletion link, third-party login without Sign in with Apple, and a missing demo account for reviewers.
User-generated content: report, block, content filtering, published contact details and terms acceptance, plus an age check for dating apps.
Payments: subscriptions shorter than seven days, missing restore purchases, missing terms and price details, third-party payment SDKs for digital goods, and an outdated Play Billing Library.
Android platform:
targetSdkagainst the current Google Play requirement, restricted permissions (SMS, call log, all-files access and others), photo and video permissions, background location, and sensitive permissions with no disclosure screen.Store listing: length limits, price and ranking claims, other apps' names in keywords, mentions of other platforms, emoji and ALL CAPS in Play titles, and wording tied to hookup or anonymous chat apps.
Sample output
Output of a run against the risky Flutter dating app in test/fixtures/flutter-dating-risky, errors only:
$ npx store-review-check test/fixtures/flutter-dating-risky --min-severity error
store-review-check 0.1.0 · rules 2026.10.15
Project: test/fixtures/flutter-dating-risky
Platforms: ios, android (flutter)
Listing data: store-review.yaml
Dating app: yes (app.category is "dating")
User content: yes (dating apps show profiles, photos and messages)
Accounts: yes (dependency firebase_auth (pubspec.yaml), lib/main.dart)
ERRORS (9)
✖ android.billing-library Google Play Billing Library version is no longer accepted
com.android.billingclient:billing:6.2.1 in android/app/build.gradle. Play requires Billing Library 8 or later since 2026-08-31 (extension until 2026-11-01).
Files: android/app/build.gradle
Fix: Update com.android.billingclient:billing (or billing-ktx) to 8.x or newer. If billing comes from a plugin (in_app_purchase, RevenueCat, react-native-iap), update the plugin to a release built on PBL 8+.
See: Google Play · Google Play Billing Library version deprecation · https://developer.android.com/google/play/billing/deprecation-faq
✖ android.target-sdk targetSdk is below the current Google Play requirement
targetSdk is 34 (android/app/build.gradle). Google Play requires 36 or higher for new apps and updates since 2026-08-31 (extension requests until 2026-11-01). At 34 an already published app is also hidden from new users on newer Android versions. Wear OS and Android Automotive OS apps need API 35; Android TV and Android XR apps need API 34.
Files: android/app/build.gradle
Fix: Raise targetSdk (and compileSdk) to 36 in the app module's build.gradle, then test the Android 15 and 16 behavior changes.
See: Google Play · Meet Google Play's target API level requirement · https://developer.android.com/google/play/requirements/target-sdk
Google Play · Target API level requirements for Google Play apps · https://support.google.com/googleplay/android-developer/answer/11926878
✖ ios.privacy-manifest-required-reason Required reason API used but not declared in the privacy manifest
User defaults APIs are used in ios/Runner/AppDelegate.swift but NSPrivacyAccessedAPICategoryUserDefaults is not declared in the app (no PrivacyInfo.xcprivacy exists yet).
Files: ios/Runner/AppDelegate.swift
Fix: Add a dictionary to NSPrivacyAccessedAPITypes with NSPrivacyAccessedAPIType set to the category and NSPrivacyAccessedAPITypeReasons set to the approved reason code that matches your use (for example CA92.1 for UserDefaults read and written only by the app itself).
See: Apple · Describing use of required reason API · https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
Apple · NSPrivacyAccessedAPIType · https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype
✖ ios.purpose-string-missing Protected resource used without a purpose string
- Camera is used (dependency image_picker (pubspec.yaml)) but NSCameraUsageDescription is missing from ios/Runner/Info.plist.
Files: ios/Runner/Info.plist, pubspec.yaml
- Location (while in use) is used (dependency geolocator (pubspec.yaml)) but NSLocationWhenInUseUsageDescription is missing from ios/Runner/Info.plist.
Files: ios/Runner/Info.plist, pubspec.yaml
Fix: Add the key to Info.plist (or to INFOPLIST_KEY_* build settings, or expo.ios.infoPlist) with one specific sentence: what the app uses it for, e.g. "Your camera is used to take profile photos and verify your identity."
See: Apple 5.1.1(ii) · Data Collection and Storage: Permission · https://developer.apple.com/app-store/review/guidelines/#5.1.1
Apple · Requesting access to protected resources · https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources
✖ ios.subscription-min-period Auto-renewable subscription shorter than seven days
Subscription com.example.amoryx.boost3day renews every P3D (3 days) in ios/Runner/Products.storekit.
Files: ios/Runner/Products.storekit
Fix: Use a period of one week or longer. For short boosts (a common dating-app product) sell a consumable in-app purchase instead.
See: Apple 3.1.2(a) · Subscriptions: Permissible uses · https://developer.apple.com/app-store/review/guidelines/#3.1.2a
✖ listing.field-limits Store listing field is over the length limit
appStore.name is 38 characters; the limit is 30.
Fix: Shorten the field. App Store Connect and Play Console will not save values over the limit.
See: Apple 2.3.7 · Accurate Metadata: app names · https://developer.apple.com/app-store/review/guidelines/#2.3.7
Apple · App information (name, subtitle) · https://developer.apple.com/help/app-store-connect/reference/app-information/app-information
Apple · Platform version information (keywords, description, promotional text) · https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information
Google Play · Best practice for your store listing · https://support.google.com/googleplay/android-developer/answer/13393723
✖ listing.privacy-policy No privacy policy URL in the store listing data
links.privacyPolicyUrl is not set.
Fix: Publish a privacy policy that covers what you collect, who you share it with, retention and how to delete data. Set links.privacyPolicyUrl in store-review.yaml and link it in the app's settings.
See: Apple 5.1.1(i) · Data Collection and Storage: Privacy Policies · https://developer.apple.com/app-store/review/guidelines/#5.1.1
Google Play · Provide information for Google Play's Data safety section · https://support.google.com/googleplay/android-developer/answer/10787469
✖ listing.prohibited-dating-terms Listing uses terms tied to hookup or compensated dating
appStore.description contains "hookup".
Fix: Remove these terms from the name, subtitle, keywords and descriptions, and make sure the product itself does not offer what they describe.
See: Apple 1.1.4 · Objectionable Content: overtly sexual material · https://developer.apple.com/app-store/review/guidelines/#1.1.4
Google Play · Inappropriate Content: sexual content · https://support.google.com/googleplay/android-developer/answer/9878810
Summary: 9 errors, 22 warnings, 5 info · 40 rules checked, 0 not applicable · 9 files scanned
27 findings below --min-severity not shown.
Static checks against public App Store and Google Play guidelines. A clean report does not guarantee approval: review is done by people, and policies change. Treat findings as a checklist, not a verdict.$ npx store-review-check test/fixtures/flutter-dating-risky
store-review-check 0.1.0 · rules 2026.10.15
Project: test/fixtures/flutter-dating-risky
Platforms: ios, android (flutter)
Listing data: store-review.yaml
Dating app: yes (app.category is "dating")
User content: yes (dating apps show profiles, photos and messages)
Accounts: yes (dependency firebase_auth (pubspec.yaml), lib/main.dart)
ERRORS (9)
✖ android.billing-library Google Play Billing Library version is no longer accepted
com.android.billingclient:billing:6.2.1 in android/app/build.gradle. Play requires Billing Library 8 or later since 2026-08-31 (extension until 2026-11-01).
Files: android/app/build.gradle
Fix: Update com.android.billingclient:billing (or billing-ktx) to 8.x or newer. If billing comes from a plugin (in_app_purchase, RevenueCat, react-native-iap), update the plugin to a release built on PBL 8+.
See: Google Play · Google Play Billing Library version deprecation · https://developer.android.com/google/play/billing/deprecation-faq
✖ android.target-sdk targetSdk is below the current Google Play requirement
targetSdk is 34 (android/app/build.gradle). Google Play requires 36 or higher for new apps and updates since 2026-08-31 (extension requests until 2026-11-01). At 34 an already published app is also hidden from new users on newer Android versions. Wear OS and Android Automotive OS apps need API 35; Android TV and Android XR apps need API 34.
Files: android/app/build.gradle
Fix: Raise targetSdk (and compileSdk) to 36 in the app module's build.gradle, then test the Android 15 and 16 behavior changes.
See: Google Play · Meet Google Play's target API level requirement · https://developer.android.com/google/play/requirements/target-sdk
Google Play · Target API level requirements for Google Play apps · https://support.google.com/googleplay/android-developer/answer/11926878
✖ ios.privacy-manifest-required-reason Required reason API used but not declared in the privacy manifest
User defaults APIs are used in ios/Runner/AppDelegate.swift but NSPrivacyAccessedAPICategoryUserDefaults is not declared in the app (no PrivacyInfo.xcprivacy exists yet).
Files: ios/Runner/AppDelegate.swift
Fix: Add a dictionary to NSPrivacyAccessedAPITypes with NSPrivacyAccessedAPIType set to the category and NSPrivacyAccessedAPITypeReasons set to the approved reason code that matches your use (for example CA92.1 for UserDefaults read and written only by the app itself).
See: Apple · Describing use of required reason API · https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
Apple · NSPrivacyAccessedAPIType · https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype
✖ ios.purpose-string-missing Protected resource used without a purpose string
- Camera is used (dependency image_picker (pubspec.yaml)) but NSCameraUsageDescription is missing from ios/Runner/Info.plist.
Files: ios/Runner/Info.plist, pubspec.yaml
- Location (while in use) is used (dependency geolocator (pubspec.yaml)) but NSLocationWhenInUseUsageDescription is missing from ios/Runner/Info.plist.
Files: ios/Runner/Info.plist, pubspec.yaml
Fix: Add the key to Info.plist (or to INFOPLIST_KEY_* build settings, or expo.ios.infoPlist) with one specific sentence: what the app uses it for, e.g. "Your camera is used to take profile photos and verify your identity."
See: Apple 5.1.1(ii) · Data Collection and Storage: Permission · https://developer.apple.com/app-store/review/guidelines/#5.1.1
Apple · Requesting access to protected resources · https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources
✖ ios.subscription-min-period Auto-renewable subscription shorter than seven days
Subscription com.example.amoryx.boost3day renews every P3D (3 days) in ios/Runner/Products.storekit.
Files: ios/Runner/Products.storekit
Fix: Use a period of one week or longer. For short boosts (a common dating-app product) sell a consumable in-app purchase instead.
See: Apple 3.1.2(a) · Subscriptions: Permissible uses · https://developer.apple.com/app-store/review/guidelines/#3.1.2a
✖ listing.field-limits Store listing field is over the length limit
appStore.name is 38 characters; the limit is 30.
Fix: Shorten the field. App Store Connect and Play Console will not save values over the limit.
See: Apple 2.3.7 · Accurate Metadata: app names · https://developer.apple.com/app-store/review/guidelines/#2.3.7
Apple · App information (name, subtitle) · https://developer.apple.com/help/app-store-connect/reference/app-information/app-information
Apple · Platform version information (keywords, description, promotional text) · https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information
Google Play · Best practice for your store listing · https://support.google.com/googleplay/android-developer/answer/13393723
✖ listing.privacy-policy No privacy policy URL in the store listing data
links.privacyPolicyUrl is not set.
Fix: Publish a privacy policy that covers what you collect, who you share it with, retention and how to delete data. Set links.privacyPolicyUrl in store-review.yaml and link it in the app's settings.
See: Apple 5.1.1(i) · Data Collection and Storage: Privacy Policies · https://developer.apple.com/app-store/review/guidelines/#5.1.1
Google Play · Provide information for Google Play's Data safety section · https://support.google.com/googleplay/android-developer/answer/10787469
✖ listing.prohibited-dating-terms Listing uses terms tied to hookup or compensated dating
appStore.description contains "hookup".
Fix: Remove these terms from the name, subtitle, keywords and descriptions, and make sure the product itself does not offer what they describe.
See: Apple 1.1.4 · Objectionable Content: overtly sexual material · https://developer.apple.com/app-store/review/guidelines/#1.1.4
Google Play · Inappropriate Content: sexual content · https://support.google.com/googleplay/android-developer/answer/9878810
WARNINGS (22)
▲ account.deletion Account creation without in-app account deletion
Accounts are created (dependency firebase_auth (pubspec.yaml), lib/main.dart) but no delete-account flow was found in source or strings.
Fix: Add Settings > Delete account that deletes the account and its data server-side (not just deactivation). If deletion lives in a WebView or on the server, declare it with features.accountDeletionInApp: true in store-review.yaml.
See: Apple 5.1.1(v) · Data Collection and Storage: Account Sign-In · https://developer.apple.com/app-store/review/guidelines/#5.1.1v
Apple · Offering account deletion in your app · https://developer.apple.com/support/offering-account-deletion-in-your-app/
Google Play · Understanding Google Play's app account deletion requirements · https://support.google.com/googleplay/android-developer/answer/13327111
▲ account.deletion-web-link No web link for account deletion (Google Play)
No links.accountDeletionUrl in the store listing data. Google Play asks for this link in the Data safety form.
Fix: Publish a page (for example https://example.com/delete-account) that explains the steps and accepts requests, then set links.accountDeletionUrl in store-review.yaml and add it in Play Console.
See: Google Play · Understanding Google Play's app account deletion requirements · https://support.google.com/googleplay/android-developer/answer/13327111
▲ android.background-location ACCESS_BACKGROUND_LOCATION declared
ACCESS_BACKGROUND_LOCATION declared in android/app/src/main/AndroidManifest.xml.
Files: android/app/src/main/AndroidManifest.xml
Fix: Request location only while the app is in use. If background access is truly core, prepare the declaration form and a video of the disclosure flow.
See: Google Play · Location Permissions · https://support.google.com/googleplay/android-developer/answer/9799150
▲ android.permission-rationale Sensitive runtime permissions with no disclosure or rationale found
Declared: CAMERA, ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION, READ_CONTACTS, RECEIVE_SMS, READ_MEDIA_IMAGES. No rationale or disclosure code found (shouldShowRequestPermissionRationale, a rationale dialog, or permission_handler's shouldShowRequestRationale).
Files: android/app/src/main/AndroidManifest.xml
Fix: Before each runtime request, show a screen that says what data you access and why, and handle the shouldShowRequestPermissionRationale case. Request permissions in context (camera when the user taps Add photo), not all at launch.
See: Google Play · User Data: Prominent Disclosure & Consent Requirement · https://support.google.com/googleplay/android-developer/answer/10144311
Google Play · Request runtime permissions · https://developer.android.com/training/permissions/requesting
▲ android.photo-video-permissions READ_MEDIA_IMAGES / READ_MEDIA_VIDEO declared
READ_MEDIA_IMAGES declared in android/app/src/main/AndroidManifest.xml.
Files: android/app/src/main/AndroidManifest.xml
Fix: Use the Android Photo Picker (androidx.activity 1.7.0+ PickVisualMedia; Flutter image_picker and Expo ImagePicker use it) and remove the permissions. If a library adds them through manifest merging, remove them with tools:node="remove".
See: Google Play · Photo and Video Permissions (restricted permissions with minimum scope alternatives) · https://support.google.com/googleplay/android-developer/answer/14115180
▲ android.restricted-permissions Restricted permission declared
RECEIVE_SMS declared in android/app/src/main/AndroidManifest.xml. SMS and Call Log permissions are limited to default SMS/phone handlers and listed exceptions. For one-time codes use the SMS Retriever or SMS User Consent API.
Files: android/app/src/main/AndroidManifest.xml
Fix: Remove the permission (check merged manifests from libraries too), or prepare the declaration for the approved use case listed in the policy.
See: Google Play · Permissions and APIs that Access Sensitive Information · https://support.google.com/googleplay/android-developer/answer/9888170
▲ dating.age-gate Dating app with no age check found
Dating app (app.category is "dating") with no date-of-birth or age check found.
Fix: Ask for date of birth at sign-up and block under-18s (or use Apple's Declared Age Range API), state the minimum age in your terms, and declare it as age assurance in the rating questionnaires. Set features.ageGate: true when done.
See: Apple 2.3.6 · Accurate Metadata: age rating · https://developer.apple.com/app-store/review/guidelines/#2.3.6
Apple · Age ratings values and definitions · https://developer.apple.com/help/app-store-connect/reference/app-information/age-ratings-values-and-definitions
Google Play · Content Ratings · https://support.google.com/googleplay/android-developer/answer/9898843
Google Play · User Generated Content (incidental sexual content) · https://support.google.com/googleplay/android-developer/answer/9876937
▲ iap.subscription-disclosure Subscription terms, price or legal links missing
Subscriptions found: no links.termsOfUseUrl (Apple requires a Terms of Use link in the app and in App Store metadata); no plans listed under subscriptions, so price and period cannot be checked.
Fix: Set links.termsOfUseUrl and links.privacyPolicyUrl, list each plan under subscriptions (price, period, trial) in store-review.yaml, and put the same facts on the paywall next to the buy button.
See: Apple 3.1.2(c) · Subscriptions: Subscription Information · https://developer.apple.com/app-store/review/guidelines/#3.1.2c
Apple · Auto-renewable subscriptions · https://developer.apple.com/app-store/subscriptions/
Google Play · Subscriptions · https://support.google.com/googleplay/android-developer/answer/9900533
▲ iap.third-party-payments Third-party payment SDK in an app that sells digital features
Found dependency flutter_stripe (pubspec.yaml). Make sure it only charges for physical goods or real-world services. This is a dating app, so premium features count as digital goods.
Files: pubspec.yaml
Fix: Sell digital features through StoreKit / Play Billing. Keep Stripe or PayPal only for physical goods or real-world services, or follow the specific external-link programs for the storefronts you target.
See: Apple 3.1.1 · In-App Purchase · https://developer.apple.com/app-store/review/guidelines/#3.1.1
Google Play · Payments · https://support.google.com/googleplay/android-developer/answer/9858738
▲ ios.privacy-manifest-missing No app privacy manifest (PrivacyInfo.xcprivacy)
No PrivacyInfo.xcprivacy found for the app target. Expected something like ios/Runner/PrivacyInfo.xcprivacy.
Fix: In Xcode choose File > New File > App Privacy, add it to the app target, and declare collected data types and each required reason API category your own code uses. Flutter: ios/Runner/PrivacyInfo.xcprivacy. Expo: expo.ios.privacyManifests in app.json.
See: Apple · Privacy manifest files · https://developer.apple.com/documentation/bundleresources/privacy-manifest-files
Apple · Describing use of required reason API · https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
▲ ios.purpose-string-quality Purpose string is empty, a placeholder or too vague
NSPhotoLibraryUsageDescription is empty or a placeholder ("Photos") in ios/Runner/Info.plist.
Files: ios/Runner/Info.plist
Fix: Rewrite the string as a full sentence that names the feature, e.g. "Location is used to show people near you and the distance to them."
See: Apple 5.1.1(ii) · Data Collection and Storage: Permission · https://developer.apple.com/app-store/review/guidelines/#5.1.1
▲ ios.restore-purchases In-app purchases without a restore mechanism
In-app purchases found (dependency purchases_flutter (pubspec.yaml)) but no restore call (AppStore.sync, restorePurchases, restoreCompletedTransactions).
Files: pubspec.yaml
Fix: Add a visible "Restore purchases" button that calls AppStore.sync() (StoreKit 2), restoreCompletedTransactions() (StoreKit 1) or your SDK's restore method (RevenueCat restorePurchases, in_app_purchase restorePurchases).
See: Apple 3.1.1 · In-App Purchase · https://developer.apple.com/app-store/review/guidelines/#3.1.1
Apple · Auto-renewable subscriptions: clearly describing subscriptions · https://developer.apple.com/app-store/subscriptions/
▲ ios.sign-in-with-apple Third-party login without an equivalent privacy-friendly option
Third-party login found (dependency google_sign_in (pubspec.yaml), Info.plist key GIDClientID (ios/Runner/Info.plist)) and no Sign in with Apple or other 4.8-compliant option.
Files: pubspec.yaml, ios/Runner/Info.plist
Fix: Add Sign in with Apple next to the other social buttons (entitlement com.apple.developer.applesignin plus the ASAuthorization flow), or confirm that one of the exceptions in 4.8 applies to your app.
See: Apple 4.8 · Login Services · https://developer.apple.com/app-store/review/guidelines/#4.8
▲ ios.tracking-without-att Ad or attribution SDK present, no App Tracking Transparency prompt found
Found dependency google_mobile_ads (pubspec.yaml), but no App Tracking Transparency request and no NSUserTrackingUsageDescription.
Files: pubspec.yaml
Fix: If any SDK tracks users (IDFA, cross-app attribution), request permission with ATTrackingManager.requestTrackingAuthorization and add NSUserTrackingUsageDescription. If none does, configure the SDKs not to track and answer the App Privacy questions to match.
See: Apple 5.1.2(i) · Data Use and Sharing · https://developer.apple.com/app-store/review/guidelines/#5.1.2
Apple · App Tracking Transparency · https://developer.apple.com/documentation/apptrackingtransparency
▲ listing.competitor-names Other app names in the App Store keywords or name
appStore.keywords mentions "tinder", "bumble".
Fix: Remove other apps' names from the name, subtitle and keywords; describe what your app does instead.
See: Apple 2.3.7 · Accurate Metadata: names and keywords · https://developer.apple.com/app-store/review/guidelines/#2.3.7
▲ listing.other-platform-mentions App Store listing mentions Android or Google Play
appStore.description contains "Android", "Google Play".
Fix: Remove mentions such as "also on Android" or "Google Play" from the App Store name, subtitle, keywords, description and promotional text.
See: Apple 2.3.10 · Accurate Metadata: other platforms · https://developer.apple.com/app-store/review/guidelines/#2.3.10
▲ listing.promo-terms Price, promotion or ranking claims in the name, subtitle, title or keywords
- appStore.name contains "Free".
- appStore.keywords contains "free".
Fix: Move prices and offers into the paywall and promotional text; keep the title to the brand plus a plain description.
See: Apple 2.3.7 · Accurate Metadata · https://developer.apple.com/app-store/review/guidelines/#2.3.7
Google Play · Metadata · https://support.google.com/googleplay/android-developer/answer/9898842
▲ listing.title-format Google Play title uses emoji, repeated symbols or ALL CAPS
googlePlay.title "AMORYX 💘 DATING!!" uses emoji, repeated special characters, ALL CAPS (fine only if that is the brand name).
Fix: Write the title in normal case without emoji or decorative symbols.
See: Google Play · Metadata · https://support.google.com/googleplay/android-developer/answer/9898842
▲ review.demo-account Login required, but no demo account for reviewers
The app has a login (dependency firebase_auth (pubspec.yaml), lib/main.dart). Set review.demoAccountProvided: true once a reviewer account is entered in both consoles.
Fix: Create a reviewer account with a finished profile, a few matches and a chat, enter it in App Review Information / App access, and set review.demoAccountProvided: true in store-review.yaml.
See: Apple 2.1(a) · App Completeness · https://developer.apple.com/app-store/review/guidelines/#2.1
Google Play · Prepare your app for review (App access) · https://support.google.com/googleplay/android-developer/answer/9859455
▲ ugc.moderation User-generated content without report, block, filter or contact info
No sign of filtering of objectionable text and photos, a way to report users and content, a way to block users in source or strings. User content detected: dating apps show profiles, photos and messages.
Fix: Add Report and Block on every profile and in every chat, filter text and photos (word list plus a moderation API), and publish a support contact. If these live on the server or in a WebView, declare them under features in store-review.yaml.
See: Apple 1.2 · User-Generated Content · https://developer.apple.com/app-store/review/guidelines/#1.2
Google Play · User Generated Content · https://support.google.com/googleplay/android-developer/answer/9876937
▲ ugc.terms-acceptance No terms of use that users accept before posting content
No terms of use link (links.termsOfUseUrl) and no terms acceptance step found in source or strings.
Fix: Show the terms (with a zero-tolerance line on objectionable content and abusive users) at sign-up and require a tap to accept before the first profile photo or message. Set links.termsOfUseUrl in store-review.yaml.
See: Google Play · User Generated Content · https://support.google.com/googleplay/android-developer/answer/9876937
INFO (5)
● android.contacts-permission READ_CONTACTS declared (new Contacts Permission policy)
READ_CONTACTS declared in android/app/src/main/AndroidManifest.xml.
Files: android/app/src/main/AndroidManifest.xml
Fix: Use the Contact Picker for invite-a-friend flows. Keep READ_CONTACTS only if a core feature needs the full list, and prepare the declaration.
See: Google Play · Contacts Permission (restricted permissions with minimum scope alternatives) · https://support.google.com/googleplay/android-developer/answer/14115180
● android.location-minimum-scope Precise location declared; approximate may be enough
ACCESS_FINE_LOCATION declared in android/app/src/main/AndroidManifest.xml. Check whether approximate location is enough.
Files: android/app/src/main/AndroidManifest.xml
Fix: Declare only ACCESS_COARSE_LOCATION unless a feature needs precise location, and say which feature in your Data safety answers.
See: Google Play · Location Permissions · https://support.google.com/googleplay/android-developer/answer/9799150
● ios.saturated-category Dating is a category Apple calls well established
Detected as a dating app (app.category is "dating"). Expect App Review to ask what makes it different from existing dating apps.
Fix: In the App Review notes, describe in two or three sentences what your app does that existing dating apps don't (audience, matching model, safety features). Don't ship several near-identical dating apps from one account.
See: Apple 4.3(b) · Spam · https://developer.apple.com/app-store/review/guidelines/#4.3
● listing.age-rating-dating Age rating answers to review for a dating app
Dating app (app.category is "dating"): review the user-generated content, messaging and chat, age assurance and mature themes answers.
Fix: Answer yes to user-generated content and messaging, describe your age assurance, and pick the frequency of mature themes honestly. Keep screenshots free of suggestive photos.
See: Apple 2.3.6 · Accurate Metadata: age rating · https://developer.apple.com/app-store/review/guidelines/#2.3.6
Apple 2.3.8 · Accurate Metadata: appropriate for all audiences · https://developer.apple.com/app-store/review/guidelines/#2.3.8
Apple · Age ratings values and definitions · https://developer.apple.com/help/app-store-connect/reference/app-information/age-ratings-values-and-definitions
Google Play · Content Ratings · https://support.google.com/googleplay/android-developer/answer/9898843
● privacy.data-safety-hints Data types to declare in Data safety and App Privacy
Likely data types: Location: Precise location; Location: Approximate location; Photos and videos: Photos; Contacts: Contacts; Messages: Other in-app messages; Personal info: Name, Email address, User IDs; Personal info: Other info (profile details such as date of birth and gender), and Sexual orientation or Political or religious beliefs if the profile asks for them; Device or other IDs; Financial info: Purchase history; Financial info: User payment info.
Fix: Compare this list with your Data safety form and App Privacy answers. Set googlePlay.dataSafety.dataTypes in store-review.yaml to have this check compare them for you.
See: Google Play · Provide information for Google Play's Data safety section · https://support.google.com/googleplay/android-developer/answer/10787469
Apple · App privacy details on the App Store · https://developer.apple.com/app-store/app-privacy-details/
Summary: 9 errors, 22 warnings, 5 info · 40 rules checked, 0 not applicable · 9 files scanned
Static checks against public App Store and Google Play guidelines. A clean report does not guarantee approval: review is done by people, and policies change. Treat findings as a checklist, not a verdict.Install and run
You need Node.js 22 or newer. No install is needed with npx:
npx store-review-check # current folder
npx store-review-check ./apps/mobile # a subfolder of a monorepo
npx store-review-check . --json > store-review-report.json
npx store-review-check rules --tag datingOr add it to the project:
npm install --save-dev store-review-checkOption | What it does |
| Check one store only. Default: every platform found. |
| Store listing data. Default: |
| Run the dating-app checks even if nothing in the project says it is one. |
| Print the JSON report. |
| Also write the report to a file. A |
| Hide findings below this level. They still count in the summary. |
| Exit with 1 when there are findings at this level. Default: |
| List the rules that did not apply and why. |
| Plain output. |
Exit codes: 0 when nothing reaches the --fail-on level, 1 when something does, 2 for a usage error or a folder that doesn't exist.
In CI
# .github/workflows/store-review.yml
name: Store review check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npx store-review-check . --fail-on errorAs a library
import { checkProject, checkListing, formatText } from 'store-review-check';
const report = checkProject('./my-app', { platform: 'ios' });
console.log(report.summary, report.findings);
console.log(formatText(report));Store listing data
Some rules need facts that aren't in the code: your listing text, your privacy policy link, or the fact that reporting lives on your server. Put them in store-review.yaml (or .json) in the project root. Every field is optional.
app:
name: Shelfmate
category: dating # "dating", "matchmaking" or "matrimony" turns on the dating checks
hasAccounts: true
hasUserGeneratedContent: true
hasChat: true
links:
privacyPolicyUrl: https://example.com/privacy
termsOfUseUrl: https://example.com/terms
supportUrl: https://example.com/support
contactEmail: support@example.com
accountDeletionUrl: https://example.com/delete-account # Google Play asks for this
appStore:
name: Shelfmate
subtitle: Slow dating for book lovers
keywords: books,reading,slow dating,book club
description: Meet people through the books you love.
ageRating: 18+
googlePlay:
title: Shelfmate
shortDescription: Meet people through the books you love.
fullDescription: Every match starts with a shared favorite book.
dataSafety:
dataTypes: [Location, Photos and videos, Messages, Personal info]
# Things the code scan can't see, such as server-side moderation.
features:
accountDeletionInApp: true
reportContent: true
blockUsers: true
contentFiltering: true
ageGate: true
restorePurchases: true
termsAcceptance: true
permissionDisclosure: true
review:
demoAccountProvided: true
subscriptions:
- productId: com.example.shelfmate.premium.monthly
name: Premium monthly
price: $12.99
period: 1 month
trial: 7 days freeIf the project uses fastlane, the tool also reads fastlane/metadata (App Store name, subtitle, keywords, description, privacy and support URLs) and fastlane/metadata/android (Play title and descriptions). Values in store-review.yaml win. Demo account passwords are never read.
Misspelled keys are reported as errors, so a typo can't silently switch a check off.
Use it as an MCP server
npx store-review-check mcp starts an MCP server over stdio. It has three read-only tools:
Tool | Input | What it returns |
|
| The text report plus structured findings |
|
| Listing problems, without a project folder |
|
| Rules with severity, guideline section and source URL |
check_listing is useful while an assistant drafts store copy: it flags length limits, price claims, other apps' names and risky dating wording before anything reaches App Store Connect.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"store-review-check": {
"command": "npx",
"args": ["-y", "store-review-check", "mcp"]
}
}
}Cursor (.cursor/mcp.json in the project, or ~/.cursor/mcp.json):
{
"mcpServers": {
"store-review-check": {
"command": "npx",
"args": ["-y", "store-review-check", "mcp"]
}
}
}Claude Code:
claude mcp add store-review-check -- npx -y store-review-check mcpThen ask something like "Check this project for App Store rejection risks" or "Is this App Store description OK for a dating app?".
Rules
40 rules, checked against the official pages on 2026-10-15. Source of truth: rules/rules.json.
Rule | Severity | Platform | What it checks | Source |
| error | iOS | Protected resource used without a purpose string | Apple 5.1.1(ii)Apple: Requesting access to protected resources |
| warning | iOS | Purpose string is empty, a placeholder or too vague | |
| warning | iOS | No app privacy manifest (PrivacyInfo.xcprivacy) | Apple: Privacy manifest filesApple: Describing use of required reason API |
| error | iOS | Required reason API used but not declared in the privacy manifest | Apple: Describing use of required reason APIApple: NSPrivacyAccessedAPIType |
| warning | iOS | NSPrivacyTracking is true but no tracking domains are listed | |
| warning | iOS | Ad or attribution SDK present, no App Tracking Transparency prompt found | |
| warning | iOS | Third-party login without an equivalent privacy-friendly option | |
| warning | iOS | Sign in with Apple code without the entitlement | |
| error | iOS | Auto-renewable subscription shorter than seven days | |
| warning | iOS | In-app purchases without a restore mechanism | Apple 3.1.1Apple: Auto-renewable subscriptions: clearly describing subscriptions |
| info | iOS | Dating is a category Apple calls well established | |
| info | iOS | Full photo library access where the system picker may be enough | |
| error | Android | targetSdk is below the current Google Play requirement | Google Play: Meet Google Play's target API level requirementGoogle Play: Target API level requirements for Google Play apps |
| info | Android | targetSdk could not be read from the build files | Google Play: Meet Google Play's target API level requirement |
| error | Android | Google Play Billing Library version is no longer accepted | Google Play: Google Play Billing Library version deprecation |
| warning | Android | READ_MEDIA_IMAGES / READ_MEDIA_VIDEO declared | Google Play: Photo and Video Permissions (restricted permissions with minimum scope alternatives) |
| warning | Android | ACCESS_BACKGROUND_LOCATION declared | |
| info | Android | Precise location declared; approximate may be enough | |
| warning | Android | Restricted permission declared | Google Play: Permissions and APIs that Access Sensitive Information |
| info | Android | READ_CONTACTS declared (new Contacts Permission policy) | Google Play: Contacts Permission (restricted permissions with minimum scope alternatives) |
| warning | Android | Sensitive runtime permissions with no disclosure or rationale found | Google Play: User Data: Prominent Disclosure & Consent RequirementGoogle Play: Request runtime permissions |
| warning | Both | Account creation without in-app account deletion | Apple 5.1.1(v)Apple: Offering account deletion in your appGoogle Play: Understanding Google Play's app account deletion requirements |
| warning | Android | No web link for account deletion (Google Play) | Google Play: Understanding Google Play's app account deletion requirements |
| warning | Both | User-generated content without report, block, filter or contact info | |
| warning | Both | No terms of use that users accept before posting content | |
| warning | Both | Third-party payment SDK in an app that sells digital features | |
| warning | Both | Subscription terms, price or legal links missing | Apple 3.1.2(c)Apple: Auto-renewable subscriptionsGoogle Play: Subscriptions |
| warning | Both | Login required, but no demo account for reviewers | Apple 2.1(a)Google Play: Prepare your app for review (App access) |
| warning | Both | Dating app with no age check found | Apple 2.3.6Apple: Age ratings values and definitionsGoogle Play: Content RatingsGoogle Play: User Generated Content (incidental sexual content) |
| info | Both | Personal data may go to a third-party AI service | |
| info | Both | Data types to declare in Data safety and App Privacy | Google Play: Provide information for Google Play's Data safety sectionApple: App privacy details on the App Store |
| error | Both | No privacy policy URL in the store listing data | Apple 5.1.1(i)Google Play: Provide information for Google Play's Data safety section |
| error | Both | Store listing field is over the length limit | Apple 2.3.7Apple: App information (name, subtitle)Apple: Platform version information (keywords, description, promotional text)Google Play: Best practice for your store listing |
| error | Both | Listing uses terms tied to hookup or compensated dating | Apple 1.1.4Google Play: Inappropriate Content: sexual content |
| warning | iOS | Listing promotes random or anonymous chat or rating people | |
| warning | iOS | Other app names in the App Store keywords or name | |
| warning | Both | Price, promotion or ranking claims in the name, subtitle, title or keywords | |
| warning | iOS | App Store listing mentions Android or Google Play | |
| warning | Android | Google Play title uses emoji, repeated symbols or ALL CAPS | |
| info | Both | Age rating answers to review for a dating app | Apple 2.3.6Apple 2.3.8Apple: Age ratings values and definitionsGoogle Play: Content Ratings |
Severity means:
error: the stores block this at upload or reject for it, and the project shows it directly (a missing key, a version number, a field over its limit).
warning: a common rejection reason, or something the tool could not find evidence for. Check it.
info: a reminder or a likely question from review.
How it works, and what it can't see
The scan walks the project (skipping node_modules, Pods, build and similar folders), parses the build and config files, and searches your source and localization strings for specific API names and patterns. The patterns live in rules/rules.json next to the rules, so you can read exactly what triggers each finding.
What that means in practice:
Absence is a guess. "No report button found" means no matching code or string was found. If reporting lives in a WebView, on the server, or under an unusual name, say so in
featuresand the warning goes away.Library manifests aren't merged. Permissions that an Android library adds at build time don't show up unless you scan the merged manifest. Check
app/build/intermediates/merged_manifestsafter a build.Some values are only known at build time.
flutter.targetSdkVersion, the Expo SDK default andapp.config.jsaren't evaluated; the report says so instead of guessing.Binary property lists aren't parsed. Convert them with
plutil -convert xml1.String matching is English-first. Localized UI text in other languages may not count as evidence.
Passing isn't approval. App Review is done by people, and guidelines change. Treat the report as a checklist.
Contributing
Issues and pull requests are welcome, especially new rules, false positives and false negatives. Please don't include code or details from apps you don't own.
To add or change a rule:
Find the requirement on an official page: the App Review Guidelines, Apple developer documentation, Play Console Help or developer.android.com. Rules without an official source are not accepted.
Edit
rules/rules.json: add the rule with itsreferences, and any detection patterns undersignals. If it needs new logic, add a check function insrc/checks/and register it insrc/checks/index.ts.Add or extend a fixture under
test/fixtures/and assert the finding intest/fixtures.test.ts. Add a negative case too, so the rule doesn't fire where it shouldn't.Run:
npm install npm test # build + all tests npm run check:urls # every source URL must load npm run docs:rules # regenerate the rule table above npm run format:rules # keep rules.json tidy
When a store changes a deadline or a number (target SDK level, Billing Library version), update params in the rule and the verified date at the top of rules.json.
Credits
Built by Rocketech, a team that builds and ships dating and social apps for iOS and Android. If a review keeps bouncing your app, we can help.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Compliance & security scan for your app: secrets, exposed files, headers, privacy, AI-disclosure.
Zero-config MCP security scanner for AI-generated apps. 25K+ vulnerability patterns.
Screens public GitHub repos and PRs to generate risk maps, findings, and merge-readiness signals.
- metarunOAuthdev.metarun
Run App Store Connect from your IDE: pricing, listings, screenshots, releases, AI visibility.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceLocal-first security check for AI coding agents — finds hardcoded secrets, exposed .env files, git-history leaks and vulnerable dependencies (OSV), entirely on your machine. Ask your agent "is this safe to ship?" and get a Launch Readiness score with a fix for every finding.MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI coding tools to scan projects for security vulnerabilities, hardcoded secrets, injection flaws, and privacy violations with 699 rules and 76 MCP tools, all running locally with zero telemetry.6 npm7MIT
- AlicenseNot gradedqualityCmaintenanceProvides pre-deploy AI-assisted security and readiness checks for AI-built apps, including scanning for secrets, auth gaps, and payment safety.5MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to diagnose and fix iOS app issues that cause App Store rejections, such as missing privacy manifests, permission strings, and test credentials, before submission.165 npm2MIT