
App Store Review
- 1k installs
- 254 repo stars
- Updated July 21, 2026
- safaiyeh/app-store-review-skill
app-store-review is a Claude Code skill that scans iOS, macOS, tvOS, watchOS, and visionOS app code against Apple's App Store Review Guidelines for developers who need to catch rejection risks before submission.
About
app-store-review is a MIT-licensed Claude Code skill (version 1.0.0) that evaluates Swift, Objective-C, React Native, and Expo projects against Apple's complete App Store Review Guidelines. The skill walks guideline-by-guideline through privacy, payments, user-generated content, entitlements, and platform-specific rules for iOS, macOS, tvOS, watchOS, and visionOS targets. Developers reach for app-store-review when preparing App Store Connect submissions, auditing compliance after guideline updates, or debugging unexplained review rejections in mobile codebases. The checker is designed to flag guideline violations in source before upload, reducing rework cycles with App Review. Triggers include app review preparation, compliance checking, and submission readiness tasks across Apple platform apps.
- Evaluates code against all 6 main App Store Review Guideline sections including Safety, Performance, Business, Design, L
- Supports Swift, Objective-C, React Native and Expo projects.
- Identifies rejection risks for payments, user data, sensitive content, UGC moderation and Kids Category features.
- Delivers concrete compliance findings with references to specific guideline rules before you submit.
- Hard-gate review step: run before any App Store or TestFlight submission.
App Store Review by the numbers
- 1,008 all-time installs (skills.sh)
- +40 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #128 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/safaiyeh/app-store-review-skill --skill app-store-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1k |
|---|---|
| repo stars | ★ 254 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 21, 2026 |
| Repository | safaiyeh/app-store-review-skill ↗ |
How do you catch App Store rejection issues in code?
Automatically scan iOS, macOS, React Native or Expo code against Apple's full App Store Review Guidelines before submission.
Who is it for?
Mobile developers shipping iOS, macOS, tvOS, watchOS, or visionOS apps in Swift, Objective-C, React Native, or Expo who want pre-submission compliance review.
Skip if: Android Play Store compliance, backend-only API services with no Apple client, or teams that only need general code quality linting unrelated to App Store rules.
When should I use this skill?
A developer asks to review iOS/macOS/React Native/Expo code for App Store submission readiness, compliance checking, or rejection risk before upload.
What you get
Guideline-by-guideline compliance report with flagged rejection risks mapped to Apple App Store Review Guidelines sections.
- Guideline compliance report
- Flagged rejection risks by guideline section
By the numbers
- MIT-licensed skill version 1.0.0
- Covers 5 Apple platforms: iOS, macOS, tvOS, watchOS, and visionOS
Files
App Store Review Guidelines Checker
Comprehensive guide for evaluating iOS, macOS, tvOS, watchOS, and visionOS app code against Apple's App Store Review Guidelines. This skill covers EVERY guideline point to identify potential rejection issues before submission.
Supports: Swift, Objective-C, React Native, and Expo apps
Guidelines current through: Apple's June 8, 2026 App Review Guidelines update.
When to Apply
Use this skill when:
- Preparing an app for App Store submission
- Reviewing code for compliance issues
- Implementing features that may trigger review concerns
- Auditing existing apps for guideline violations
- Building features involving payments, user data, or sensitive content
Guideline Sections
Read individual rule files for detailed explanations, checklists, and code examples:
| Section | File | Key Topics |
|---|---|---|
| 1. Safety | rules/1-safety.md | Objectionable content, UGC moderation, Kids Category, physical harm, data security |
| 2. Performance | rules/2-performance.md | App completeness, metadata accuracy, hardware compatibility, software requirements |
| 3. Business | rules/3-business.md | In-app purchase, subscriptions, cryptocurrencies, other business models |
| 4. Design | rules/4-design.md | Copycats, minimum functionality, spam, extensions, Apple services, login |
| 5. Legal | rules/5-legal.md | Privacy, data collection, intellectual property, gambling, VPN, MDM |
Risk Levels by Category
| Risk Level | Category | Section | Common Rejection Reasons |
|---|---|---|---|
| CRITICAL | Privacy & Data | 5.1 | Missing privacy policy, unauthorized data collection |
| CRITICAL | Payments | 3.1 | Bypassing in-app purchase, unclear pricing |
| HIGH | Safety | 1.x | Objectionable content, inadequate UGC moderation |
| HIGH | Performance | 2.x | Crashes, incomplete features, deprecated APIs |
| MEDIUM | Design | 4.x | Copycat apps, minimum functionality issues |
| MEDIUM | Legal | 5.x | IP violations, gambling without license |
---
Quick Reference: High-Risk Rejection Patterns
Critical Issues (Immediate Rejection)
Swift:
// 🔴 Private API usage
let selector = NSSelectorFromString("_privateMethod")
// 🔴 Hardcoded secrets
let apiKey = "sk_live_xxxxx"
// 🔴 External payment for digital goods
func purchaseDigitalContent() {
openStripeCheckout() // Use StoreKit instead
}React Native / Expo:
// 🔴 Hardcoded secrets in JS bundle
const API_KEY = 'sk_live_xxxxx'; // REJECTION
// 🔴 External payment for digital goods
Linking.openURL('https://stripe.com/checkout'); // Use react-native-iap
// 🔴 Dynamic code execution
eval(downloadedCode); // REJECTION
// 🔴 Major feature changes via CodePush/expo-updates
// OTA updates for bug fixes only, not new features!High-Risk Issues
Swift:
// 🟡 Missing ATT when using ad SDKs
import FacebookAds // Without ATTrackingManager
// 🟡 Account creation without deletion
func createAccount() { } // But no deleteAccount()React Native / Expo:
// 🟡 Missing ATT (use expo-tracking-transparency)
import analytics from '@react-native-firebase/analytics';
analytics().logEvent('event'); // Without ATT prompt = REJECTION
// 🟡 Account deletion via website only
Linking.openURL('https://example.com/delete'); // Must be in-app!
// 🟡 Social login without Sign in with Apple
<GoogleSigninButton /> // Must also offer Apple login!Medium-Risk Issues
// 🟠 Vague purpose strings in Info.plist
"This app needs camera access" // Be specific!
// 🟠 WebView-only app (insufficient native functionality)
const App = () => <WebView source={{ uri: 'https://site.com' }} />;
// 🟠 References to Android in iOS app
const text = "Also available on Android"; // REJECTION
// 🟠 console.log in production
console.log('debug'); // Remove or wrap in __DEV__---
Pre-Submission Checklist
Privacy (Section 5.1)
- [ ] Privacy policy link in App Store Connect
- [ ] Privacy policy link accessible within app
- [ ] All purpose strings are specific and accurate
- [ ] App Privacy details completed in App Store Connect
- [ ] ATT implemented if tracking users
- [ ] Account deletion available if accounts exist
- [ ] Data minimization - only requesting necessary permissions
- [ ] User consent obtained before data collection
Payments (Section 3.1)
- [ ] StoreKit used for all digital purchases
- [ ] Restore purchases implemented
- [ ] Subscription terms clearly displayed
- [ ] Loot box odds disclosed if applicable
- [ ] No external payment for digital goods (unless entitled)
- [ ] Credits/currencies don't expire
Safety (Section 1.x)
- [ ] No objectionable content
- [ ] UGC moderation implemented (filter, report, block, contact)
- [ ] UGC violations can be removed quickly and backed by a remediation plan
- [ ] Kids and teens receive age-appropriate experiences inside the app
- [ ] Parental gates for Kids Category apps
- [ ] No false information or prank features
- [ ] Medical disclaimers if applicable
- [ ] No substance promotion
Performance (Section 2.x)
- [ ] No crashes or bugs
- [ ] All features complete and functional
- [ ] No placeholder content
- [ ] IPv6 tested and functional
- [ ] Demo account provided if needed
- [ ] Using only public APIs
- [ ] No deprecated APIs
- [ ] Proper background mode usage
Design (Section 4.x)
- [ ] Sufficient native functionality (not just web wrapper)
- [ ] No copycat concerns
- [ ] Original app name and branding
- [ ] No duplicate Bundle ID spam or low-effort saturated-category clones
- [ ] Live Activities, push notifications, and Game Center are not used for spam, phishing, or unsolicited messages
- [ ] Extensions comply with guidelines
- [ ] Login alternatives if using social login
- [ ] Not monetizing built-in capabilities
Legal (Section 5.x)
- [ ] No unlicensed third-party content
- [ ] Proper Apple trademark usage
- [ ] Gambling license if applicable
- [ ] VPN uses NEVPNManager API
- [ ] COPPA/GDPR compliance for kids
---
References
{
"name": "app-store-review",
"interface": {
"displayName": "App Store Review"
},
"plugins": [
{
"name": "app-store-review",
"source": {
"source": "local",
"path": "./"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Coding"
}
]
}
{
"name": "app-store-review",
"owner": {
"name": "safaiyeh"
},
"metadata": {
"description": "Apple App Store Review Guidelines skill for Claude Code"
},
"plugins": [
{
"name": "app-store-review",
"source": "./",
"description": "Evaluates code against Apple's App Store Review Guidelines for iOS, macOS, tvOS, watchOS, and visionOS apps",
"version": "1.0.1",
"author": {
"name": "safaiyeh"
},
"category": "compliance",
"tags": ["apple", "app-store", "review", "guidelines", "ios", "swift", "react-native"],
"strict": true
}
]
}
{
"name": "app-store-review",
"description": "Evaluates code against Apple's App Store Review Guidelines for iOS, macOS, tvOS, watchOS, and visionOS apps",
"version": "1.0.1",
"author": {
"name": "safaiyeh"
},
"homepage": "https://github.com/safaiyeh/app-store-review-skill",
"repository": "https://github.com/safaiyeh/app-store-review-skill",
"license": "MIT",
"keywords": ["apple", "app-store", "review", "guidelines", "ios", "macos", "swift", "react-native", "expo"]
}
{
"name": "app-store-review",
"version": "1.0.1",
"description": "Evaluates code against Apple's App Store Review Guidelines for iOS, macOS, tvOS, watchOS, and visionOS apps.",
"author": {
"name": "safaiyeh",
"url": "https://github.com/safaiyeh"
},
"homepage": "https://github.com/safaiyeh/app-store-review-skill",
"repository": "https://github.com/safaiyeh/app-store-review-skill",
"license": "MIT",
"keywords": [
"apple",
"app-store",
"review",
"guidelines",
"ios",
"macos",
"swift",
"react-native",
"expo"
],
"skills": "./",
"interface": {
"displayName": "App Store Review",
"shortDescription": "Audit apps for App Store review risk",
"longDescription": "Evaluate app code against Apple's App Store Review Guidelines before submission, with coverage for privacy, payments, safety, performance, design, and legal risks.",
"developerName": "safaiyeh",
"category": "Coding",
"capabilities": [
"Read"
],
"websiteURL": "https://github.com/safaiyeh/app-store-review-skill",
"defaultPrompt": [
"Review this app for App Store compliance."
],
"brandColor": "#0A84FF"
}
}
# Claude Code
.claude/
# Dependencies
node_modules/
# Build outputs
dist/
build/
# OS files
.DS_Store
Thumbs.db
# IDE
.idea/
.vscode/
*.swp
*.swo
# Logs
*.log
npm-debug.log*
# Environment
.env
.env.local
.env.*.local
interface:
display_name: "App Store Review"
short_description: "Audit App Store compliance before submission"
default_prompt: "Use $app-store-review to review this app for App Store compliance before submission."
MIT License
Copyright (c) 2026 safaiyeh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"version": "1.0.1",
"organization": "safaiyeh",
"date": "June 2026",
"abstract": "Exhaustive guide for evaluating iOS, macOS, tvOS, watchOS, and visionOS app code against Apple's App Store Review Guidelines, current through Apple's June 8, 2026 update. Covers ALL 5 major sections with EVERY guideline point: Section 1 Safety (1.1-1.7 including objectionable content, UGC, Kids Category, physical harm, developer info, data security), Section 2 Performance (2.1-2.5 including completeness, beta testing, metadata, hardware compatibility, software requirements), Section 3 Business (3.1-3.2 including IAP, subscriptions, cryptocurrencies, other business models), Section 4 Design (4.1-4.10 including copycats, minimum functionality, spam, extensions, Apple services, mini apps, login services, Apple Pay), and Section 5 Legal (5.1-5.5 including privacy, IP, gambling, VPN, MDM). Includes code patterns to flag, checklists for each guideline, and pre-submission verification.",
"references": [
"https://developer.apple.com/app-store/review/guidelines/",
"https://developer.apple.com/support/terms/apple-developer-program-license-agreement/",
"https://developer.apple.com/news/?id=a233fmpw",
"https://developer.apple.com/design/human-interface-guidelines/",
"https://developer.apple.com/documentation/storekit",
"https://developer.apple.com/documentation/apptrackingtransparency",
"https://developer.apple.com/documentation/localauthentication",
"https://developer.apple.com/documentation/networkextension/nevpnmanager",
"https://developer.apple.com/help/app-store-connect/",
"https://developer.apple.com/documentation/healthkit",
"https://developer.apple.com/documentation/callkit",
"https://developer.apple.com/documentation/classkit",
"https://developer.apple.com/documentation/arkit"
]
}
App Store Review Guidelines Skill
An AI agent skill that exhaustively evaluates iOS, macOS, tvOS, watchOS, and visionOS app code against every point in Apple's App Store Review Guidelines.
Supports: Swift, Objective-C, React Native, and Expo apps
Current through: Apple's official June 8, 2026 App Review Guidelines update
Installation
Codex
Add this repository as a Codex plugin marketplace:
codex plugin marketplace add safaiyeh/app-store-review-skillThen open Codex, run /plugins, choose the App Store Review marketplace, and install the app-store-review plugin. Start a new thread and invoke the skill with $app-store-review or ask for an App Store compliance review.
Codex also discovers direct local skills from ~/.agents/skills, but plugins are the recommended distribution path for reusable skills.
Claude Code Plugin Marketplace
/plugin marketplace add safaiyeh/app-store-review-skill
/plugin install app-store-review@app-store-reviewskills.sh
npx skills add safaiyeh/app-store-review-skillSetup
Supported AI Agents
This skill works with Codex and AI coding agents that support the skills.sh standard:
- Codex
- Claude Code
- Cursor
- Windsurf
- And other compatible agents
How It Works
1. Install the skill in your project using the command above 2. Start your AI agent in the project directory 3. Ask for an App Store review - the agent will automatically load relevant guidelines 4. Review the findings - the agent identifies potential rejection issues with code references
Example Prompts
"Review this app for App Store compliance"
"Check if my IAP implementation follows Apple's guidelines"
"Audit the privacy and data collection in this React Native app"
"What App Store issues might block my submission?"Telemetry
The skills CLI collects anonymous usage telemetry. To opt out:
SKILLS_NO_TELEMETRY=1 npx skills add safaiyeh/app-store-review-skillStructure
app-store-review-skill/
├── .agents/
│ └── plugins/marketplace.json # Codex plugin marketplace
├── .codex-plugin/
│ └── plugin.json # Codex plugin manifest
├── agents/
│ └── openai.yaml # Codex UI metadata
├── SKILL.md # Index with quick reference & checklist
└── rules/
├── 1-safety.md # Section 1: Safety guidelines
├── 2-performance.md # Section 2: Performance guidelines
├── 3-business.md # Section 3: Business guidelines
├── 4-design.md # Section 4: Design guidelines
└── 5-legal.md # Section 5: Legal guidelinesCoverage
This skill covers ALL 5 major sections with EVERY guideline point:
1. Safety
- 1.1 Objectionable Content (1.1.1-1.1.7)
- 1.2 User-Generated Content & Creator Content
- 1.3 Kids Category (parental gates, privacy, analytics)
- 1.4 Physical Harm (medical apps, drug dosage, substances)
- 1.5 Developer Information
- 1.6 Data Security
- 1.7 Reporting Criminal Activity
2. Performance
- 2.1 App Completeness (final versions, IAP)
- 2.2 Beta Testing
- 2.3 Accurate Metadata (2.3.1-2.3.13)
- 2.4 Hardware Compatibility (2.4.1-2.4.5)
- 2.5 Software Requirements (2.5.1-2.5.18)
3. Business
- 3.1 Payments (IAP, subscriptions, external links, crypto)
- 3.1.1-3.1.5 In-App Purchase rules
- 3.2 Other Business Models (acceptable/unacceptable)
4. Design
- 4.1 Copycats
- 4.2 Minimum Functionality
- 4.3 Spam
- 4.4 Extensions (keyboard, Safari)
- 4.5 Apple Sites and Services
- 4.7 Mini Apps, Chatbots, Game Emulators
- 4.8 Login Services
- 4.9 Apple Pay
- 4.10 Monetizing Built-In Capabilities
5. Legal
- 5.1 Privacy (data collection, use, sharing, health, kids, location)
- 5.2 Intellectual Property
- 5.3 Gaming, Gambling, Lotteries
- 5.4 VPN Apps
- 5.5 Mobile Device Management
Features
- Modular structure - Agent loads only relevant sections
- 2000+ lines of comprehensive guidelines
- Checklists for every guideline point
- Code patterns for Swift AND React Native/Expo
- Package references for both Expo and bare React Native
- Quick reference for high-risk rejection patterns
- Pre-submission checklist in main SKILL.md
React Native / Expo Support
Each rule file includes:
- TypeScript/JavaScript code patterns to flag
- Expo package recommendations (preferred)
- Bare React Native package alternatives
- React Native-specific checklists
Key packages covered:
expo-tracking-transparency/react-native-tracking-transparencyexpo-in-app-purchases/react-native-iapexpo-secure-store/react-native-keychainexpo-apple-authentication/@invertase/react-native-apple-authenticationexpo-local-authentication/react-native-biometrics
What It Checks
Critical Issues (Immediate Rejection)
- Private API usage
- Hardcoded secrets/credentials
- External payment for digital goods
- On-device cryptocurrency mining
- Dynamic code execution
High-Risk Issues
- Missing App Tracking Transparency
- Account creation without deletion
- IAP without restore purchases
- UGC without moderation
- UGC without removal workflows or a compliance improvement plan
- Kids apps without parental gates
- Live Activities or push notifications used for spam, phishing, or unsolicited messages
Medium-Risk Issues
- Vague purpose strings
- Over-requesting permissions
- Unjustified background modes
- References to other platforms
When It Triggers
The skill activates when working on:
- App Store submission preparation
- Code compliance review
- Payment/StoreKit implementation
- Privacy and data handling
- User-generated content features
- Kids Category apps
- Health/medical apps
- VPN/MDM apps
- Gambling/lottery apps
License
MIT
1. SAFETY
Apps should not include content that is offensive, insensitive, upsetting, intended to disgust, in exceptionally poor taste, or just plain creepy.
Current Apple Emphasis
Apple's June 8, 2026 update revised kid and teen safety guidance. When reviewing an app that children or teens may use, do not rely only on platform parental controls:
- [ ] Confirm the in-app experience is age-appropriate for the audience actually using it
- [ ] Check onboarding, discovery, recommendations, messaging, ads, and UGC surfaces for age-inappropriate exposure
- [ ] Treat inaccurate age ratings or weak age gates as safety risks, not just metadata issues
1.1 Objectionable Content
1.1.1 Defamatory, Discriminatory, or Mean-Spirited Content
REJECT if app contains:
- [ ] Content including references or commentary about religion, race, sexual orientation, gender, national/ethnic origin, or other targeted groups
- [ ] Content likely to humiliate, intimidate, or harm a targeted individual or group
- [ ] Discriminatory language in any text, assets, or user-facing content
Exception: Professional political satirists and humorists are generally exempt.
Code patterns to flag:
// Swift - FLAG: Review all hardcoded strings for discriminatory content
let strings = ["message", "alert", "notification"] // Audit for offensive language
// FLAG: User-facing text that targets groups
// Search for terms related to: religion, race, gender, nationality, orientation// React Native - FLAG: Review all hardcoded strings
const strings = {
message: "...", // Audit for offensive language
alert: "...",
};
// FLAG: Check i18n/localization files for discriminatory content
// en.json, es.json, etc.1.1.2 Realistic Portrayals of Violence
REJECT if app contains:
- [ ] Realistic portrayals of people or animals being killed, maimed, tortured, or abused
- [ ] Content that encourages violence
- [ ] "Enemies" that solely target a specific race, culture, real government, corporation, or any other real entity
Code patterns to flag:
// Swift - FLAG: Violence-related asset names
"kill_", "death_", "blood_", "gore_", "torture_", "maim_"// React Native - FLAG: Violence-related assets
import killIcon from './assets/kill_enemy.png'; // REVIEW
import deathAnimation from './assets/death.json'; // REVIEW
// FLAG: Enemy targeting specific real groups
type EnemyType = 'specificNationality' | 'specificReligion'; // REJECTION RISK1.1.3 Weapons and Dangerous Objects
REJECT if app:
- [ ] Depicts content encouraging illegal or reckless use of weapons and dangerous objects
- [ ] Facilitates the purchase of firearms or ammunition
Code patterns to flag:
// Swift - FLAG: Weapon purchase functionality
func purchaseFirearm()
func buyAmmunition()
// FLAG: Links to weapon retailers
"gunbroker.com", "ammo.com"// React Native - FLAG: Weapon purchase functionality
const purchaseFirearm = async () => { }; // REJECTION
const buyAmmunition = async () => { }; // REJECTION
// FLAG: Links to weapon retailers
Linking.openURL('https://gunbroker.com'); // REJECTION1.1.4 Overtly Sexual or Pornographic Material
REJECT if app contains:
- [ ] Explicit descriptions or displays of sexual organs or activities intended to stimulate erotic rather than aesthetic or emotional feelings
- [ ] "Hookup" apps that may include pornography
- [ ] Content that could facilitate prostitution or human trafficking/exploitation
Code patterns to flag:
// Swift - FLAG: Adult content indicators
"nsfw", "adult_content", "explicit", "xxx"
// FLAG: Dating/hookup functionality without moderation
func matchUsers() // Ensure proper moderation exists// React Native - FLAG: Adult content indicators
const isNSFW = true; // REVIEW
const contentRating = 'explicit'; // REVIEW
// FLAG: Dating/hookup functionality without moderation
const matchUsers = async () => { }; // Ensure moderation exists1.1.5 Inflammatory Religious Commentary
REJECT if app contains:
- [ ] Inflammatory religious commentary
- [ ] Inaccurate or misleading quotations of religious texts
1.1.6 False Information and Features
REJECT if app:
- [ ] Provides inaccurate device data
- [ ] Contains trick/joke functionality (e.g., fake location trackers)
- [ ] Enables anonymous or prank phone calls or SMS/MMS messaging
Note: Stating "for entertainment purposes" does NOT overcome this guideline.
Code patterns to flag:
// Swift - FLAG: Fake device data
func getFakeLocation() -> CLLocation // REJECTION
func spoofDeviceID() -> String // REJECTION
// FLAG: Prank call/SMS functionality
func makeAnonymousCall() // REJECTION
func sendPrankSMS() // REJECTION// React Native - FLAG: Fake device data
const getFakeLocation = (): Location => { }; // REJECTION
const spoofDeviceID = (): string => { }; // REJECTION
// FLAG: Using react-native-device-info to spoof
import DeviceInfo from 'react-native-device-info';
const fakeId = 'spoofed-id'; // Don't return fake data
// FLAG: Prank functionality
const makeAnonymousCall = () => { }; // REJECTION
const sendPrankSMS = () => { }; // REJECTION1.1.7 Harmful Concepts Exploiting Current Events
REJECT if app:
- [ ] Capitalizes or seeks to profit on violent conflicts
- [ ] Exploits terrorist attacks
- [ ] Exploits epidemics or health crises
---
1.2 User-Generated Content
Apps with user-generated content present particular challenges, ranging from intellectual property infringement to anonymous bullying. To prevent abuse, apps with UGC or social networking services MUST include:
Required UGC Features (ALL MANDATORY)
- [ ] Content Filtering: A method for filtering objectionable material from being posted
- [ ] Reporting Mechanism: A way to report offensive content with timely responses
- [ ] User Blocking: The ability to block abusive users from the service
- [ ] Contact Information: Published contact information so users can easily reach you
Swift implementation:
// REQUIRED: Content filtering
protocol ContentFilter {
func filterContent(_ content: String) -> FilterResult
func moderateImage(_ image: UIImage) -> ModerationResult
}
// REQUIRED: Report mechanism
func reportContent(
contentId: String,
reason: ReportReason,
additionalInfo: String?
) async throws
// REQUIRED: Block user
func blockUser(_ userId: String) async throws
func getBlockedUsers() -> [User]
// REQUIRED: Support contact
var supportEmail: String { get }
var supportURL: URL { get }React Native implementation:
// REQUIRED: Content filtering
interface ContentFilter {
filterContent(content: string): Promise<FilterResult>;
moderateImage(imageUri: string): Promise<ModerationResult>;
}
// Example using a moderation API
const filterContent = async (content: string): Promise<FilterResult> => {
// Use a service like OpenAI Moderation, Perspective API, or custom ML
const response = await fetch('https://api.example.com/moderate', {
method: 'POST',
body: JSON.stringify({ content }),
});
return response.json();
};
// REQUIRED: Report mechanism
const reportContent = async (
contentId: string,
reason: ReportReason,
additionalInfo?: string
): Promise<void> => {
await api.post('/reports', { contentId, reason, additionalInfo });
};
// REQUIRED: Block user
const blockUser = async (userId: string): Promise<void> => {
await api.post(`/users/${userId}/block`);
};
const getBlockedUsers = async (): Promise<User[]> => {
return api.get('/users/blocked');
};
// REQUIRED: Support contact - expose in app
const SUPPORT_EMAIL = 'support@example.com';
const SUPPORT_URL = 'https://example.com/support';Content That Results in Removal
Apps primarily used for the following will be removed WITHOUT notice:
- Pornographic content
- Chatroulette-style experiences
- Random or anonymous chat
- Objectification of real people (e.g., "hot-or-not" voting)
- Making physical threats
- Bullying
Incidental Mature "NSFW" Content
If your app includes user-generated content from a web-based service:
- [ ] Mature content MUST be hidden by default
- [ ] Only displayed when user explicitly enables it via your website
- [ ] Violating content must be removable quickly when detected or reported
- [ ] App Review may require a concrete compliance improvement plan before the app can stay on the App Store
- [ ] Egregious or repeated UGC failures can trigger immediate app removal and Developer Program removal
// React Native - REQUIRED: NSFW content hidden by default
const [showMatureContent, setShowMatureContent] = useState(false);
// Must be enabled via website, not in-app toggle for Kids safety
// Check user preference from backend (set via website)
useEffect(() => {
const checkMatureContentSetting = async () => {
const setting = await api.get('/user/settings/mature-content');
setShowMatureContent(setting.enabled); // Set via website only
};
checkMatureContentSetting();
}, []);Developer Responsibility for Violating UGC
Apple's current 1.2 language makes the developer responsible for removing content that violates the guideline, the app's terms of service, or community standards. If Apple finds violating content, expect to remove it and explain how compliance will improve.
Review for:
- [ ] Admin/moderator tooling that can remove posts, comments, profiles, messages, media, and creator content
- [ ] Abuse queues or dashboards with timestamps, severity, status, and reviewer actions
- [ ] Escalation paths for threats, bullying, sexual content, child safety issues, and repeat offenders
- [ ] Audit logs that can support an App Review response or remediation plan
- [ ] Clear terms/community standards surfaced to users and enforced consistently
// React Native/backend contract - REQUIRED for UGC apps
type ModerationStatus = 'pending' | 'actioned' | 'dismissed' | 'escalated';
interface ModerationAction {
reportId: string;
contentId: string;
action: 'remove_content' | 'suspend_user' | 'age_restrict' | 'dismiss';
reason: string;
status: ModerationStatus;
}
const removeViolatingContent = async (action: ModerationAction): Promise<void> => {
await api.post('/moderation/actions', action);
};1.2.1 Creator Content
Apps featuring content from "creators" must:
- [ ] Properly moderate content per Guideline 1.2
- [ ] Follow payment guidelines per 3.1.1
- [ ] Communicate which content requires additional purchases
- [ ] Not change core features/functionality of native app
1.2.1(a) Age Rating and Access Restrictions
- [ ] Provide way for users to identify content exceeding app's age rating
- [ ] Use age restriction mechanism based on verified or declared age
- [ ] Limit access by underage users to age-inappropriate content
---
1.3 Kids Category
If participating in Kids Category, you MUST follow these rules:
Core Requirements
- [ ] No links out of app unless behind parental gate
- [ ] No purchasing opportunities unless behind parental gate
- [ ] No other distractions to kids unless behind parental gate
- [ ] Must continue meeting Kids Category requirements in all future updates
Swift parental gate:
// REQUIRED: Parental gate for Kids apps
class ParentalGate {
// Must require adult verification - examples:
// - Math problem (e.g., "What is 15 + 27?")
// - Date of birth entry
// - Written instructions to have parent complete
func presentGate(completion: @escaping (Bool) -> Void) {
// Implementation must be non-trivial for children
}
}
// REQUIRED: Gate before external links
func openExternalLink(_ url: URL) {
parentalGate.presentGate { verified in
if verified {
UIApplication.shared.open(url)
}
}
}
// REQUIRED: Gate before purchases
func initiatePurchase(_ product: SKProduct) {
parentalGate.presentGate { verified in
if verified {
// Proceed with purchase
}
}
}React Native parental gate:
// REQUIRED: Parental gate for Kids apps
import { Linking } from 'react-native';
interface ParentalGateProps {
onVerified: () => void;
onCancelled: () => void;
}
const ParentalGate: React.FC<ParentalGateProps> = ({ onVerified, onCancelled }) => {
const [answer, setAnswer] = useState('');
// Must be non-trivial for children
const correctAnswer = 15 + 27; // = 42
const verify = () => {
if (parseInt(answer) === correctAnswer) {
onVerified();
}
};
return (
<Modal>
<Text>For grown-ups only!</Text>
<Text>What is 15 + 27?</Text>
<TextInput
keyboardType="numeric"
value={answer}
onChangeText={setAnswer}
/>
<Button title="Submit" onPress={verify} />
<Button title="Cancel" onPress={onCancelled} />
</Modal>
);
};
// REQUIRED: Gate before external links
const openExternalLink = async (url: string) => {
const verified = await showParentalGate();
if (verified) {
Linking.openURL(url);
}
};
// REQUIRED: Gate before purchases (using react-native-iap)
import * as IAP from 'react-native-iap';
const initiatePurchase = async (sku: string) => {
const verified = await showParentalGate();
if (verified) {
await IAP.requestPurchase({ sku });
}
};Privacy Requirements for Kids Category
- [ ] Comply with COPPA, GDPR, and all applicable children's privacy laws
- [ ] May NOT send personally identifiable information to third parties
- [ ] May NOT send device information to third parties
- [ ] Should NOT include third-party analytics (safer experience)
- [ ] Should NOT include third-party advertising (safer experience)
// React Native - Kids Category: Remove these packages
// ❌ @react-native-firebase/analytics
// ❌ react-native-facebook-sdk
// ❌ @segment/analytics-react-native
// ❌ react-native-appsflyer
// ❌ Any advertising SDK
// ❌ BAD: Third-party analytics in Kids app
import analytics from '@react-native-firebase/analytics';
analytics().logEvent('screen_view'); // REJECTION for Kids Category
// ✅ GOOD: No third-party analytics, or use compliant ones only
// If analytics needed, must not collect IDFA, PII, location, or device infoLimited Exceptions for Analytics/Advertising
Third-party analytics MAY be permitted if services:
- [ ] Do NOT collect or transmit IDFA
- [ ] Do NOT collect identifiable information (name, DOB, email)
- [ ] Do NOT use location
- [ ] Do NOT use device information that could identify users
Third-party contextual advertising MAY be permitted if services:
- [ ] Have publicly documented practices for Kids Category apps
- [ ] Include human review of ad creatives for age appropriateness
---
1.4 Physical Harm
1.4.1 Medical Apps
Medical apps providing data or information for diagnosis/treatment are reviewed with greater scrutiny.
Requirements:
- [ ] Clearly disclose data and methodology supporting accuracy claims
- [ ] If accuracy/methodology cannot be validated, app will be REJECTED
- [ ] Remind users to check with doctor before making medical decisions
- [ ] Submit regulatory clearance documentation if received
Apps NOT permitted (claiming to use only device sensors for):
- X-rays
- Blood pressure measurement
- Body temperature measurement
- Blood glucose measurement
- Blood oxygen measurement
Code patterns to flag:
// Swift - FLAG: Medical measurement claims
func measureBloodPressure() // REJECTION without proper hardware
func measureBloodGlucose() // REJECTION without proper hardware
func measureBloodOxygen() // REJECTION without proper hardware
func measureTemperature() // REJECTION without proper hardware
// REQUIRED: Medical disclaimer
let medicalDisclaimer = """
This app is not intended to diagnose, treat, cure, or prevent any disease.
Always consult with a qualified healthcare provider before making medical decisions.
"""// React Native - FLAG: Medical measurement claims
const measureBloodPressure = async () => { }; // REJECTION without hardware
const measureBloodGlucose = async () => { }; // REJECTION without hardware
const measureBloodOxygen = async () => { }; // REJECTION without hardware
const measureTemperature = async () => { }; // REJECTION without hardware
// REQUIRED: Medical disclaimer
const MEDICAL_DISCLAIMER = `
This app is not intended to diagnose, treat, cure, or prevent any disease.
Always consult with a qualified healthcare provider before making medical decisions.
`;
// Display on first launch and in settings
const MedicalDisclaimer: React.FC = () => (
<View>
<Text>{MEDICAL_DISCLAIMER}</Text>
<Button title="I Understand" onPress={acceptDisclaimer} />
</View>
);1.4.2 Drug Dosage Calculators
Drug dosage calculators MUST come from:
- [ ] The drug manufacturer, OR
- [ ] A hospital, OR
- [ ] A university, OR
- [ ] A health insurance company, OR
- [ ] A pharmacy, OR
- [ ] Another approved entity, OR
- [ ] Have FDA approval (or international equivalent)
1.4.3 Substance Consumption
NOT permitted:
- [ ] Apps encouraging tobacco consumption
- [ ] Apps encouraging vape product consumption
- [ ] Apps encouraging illegal drug use
- [ ] Apps encouraging excessive alcohol consumption
- [ ] Apps encouraging minors to consume any of these substances
- [ ] Facilitating sale of controlled substances (except licensed pharmacies/dispensaries)
Code patterns to flag:
// Swift - FLAG: Substance promotion
"tobacco", "vaping", "drug_use", "alcohol_challenge"
// FLAG: Substance purchase (unless licensed pharmacy)
func purchaseControlledSubstance() // REJECTION unless licensed// React Native - FLAG: Substance promotion
const features = ['tobacco_tracker', 'vaping_counter']; // REJECTION
const alcoholChallenge = () => { }; // REJECTION
// FLAG: Substance purchase
const purchaseControlledSubstance = async () => { }; // REJECTION unless licensed1.4.4 DUI Checkpoints
- [ ] May ONLY display DUI checkpoints published by law enforcement agencies
- [ ] Must NEVER encourage drunk driving or reckless behavior
1.4.5 Physical Harm Activities
- [ ] Must NOT urge customers to participate in activities risking physical harm (bets, challenges)
- [ ] Must NOT urge customers to use devices in ways risking physical harm
---
1.5 Developer Information
Requirements
- [ ] App and Support URL must include easy way to contact you
- [ ] Failure to include accurate, up-to-date contact information may violate law
- [ ] Particularly important for apps used in classrooms
Wallet Passes
- [ ] Must include valid contact information from issuer
- [ ] Must be signed with dedicated certificate assigned to brand/trademark owner
---
1.6 Data Security
- [ ] Implement appropriate security measures for user information handling
- [ ] Prevent unauthorized use, disclosure, or access by third parties
Swift implementation:
// REQUIRED: Secure data storage
import Security
class SecureStorage {
// Use Keychain for sensitive data
func storeCredential(_ credential: String, for key: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: credential.data(using: .utf8)!
]
SecItemAdd(query as CFDictionary, nil)
}
}
// REQUIRED: HTTPS for network communications
let configuration = URLSessionConfiguration.default
configuration.tlsMinimumSupportedProtocolVersion = .TLSv12
// BAD: Hardcoded secrets
let apiKey = "sk_live_xxxxx" // NEVER DO THIS
let password = "admin123" // NEVER DO THIS
// GOOD: Environment/secure storage
let apiKey = ProcessInfo.processInfo.environment["API_KEY"]
let apiKey = try SecureStorage.shared.retrieveCredential(for: "api_key")React Native implementation:
// REQUIRED: Secure data storage
import * as Keychain from 'react-native-keychain';
import EncryptedStorage from 'react-native-encrypted-storage';
// Use Keychain for credentials
const storeCredential = async (key: string, value: string) => {
await Keychain.setGenericPassword(key, value);
};
const getCredential = async (key: string) => {
const credentials = await Keychain.getGenericPassword();
return credentials ? credentials.password : null;
};
// Use EncryptedStorage for other sensitive data
const storeSecureData = async (key: string, value: string) => {
await EncryptedStorage.setItem(key, value);
};
// ❌ BAD: Hardcoded secrets
const API_KEY = 'sk_live_xxxxx'; // NEVER DO THIS
const PASSWORD = 'admin123'; // NEVER DO THIS
// ❌ BAD: Secrets in JavaScript bundle
// .env files get bundled into JS - use native modules for secrets
// ✅ GOOD: Use react-native-config with native-side secrets
import Config from 'react-native-config';
const apiKey = Config.API_KEY; // Set in native build, not JS
// ✅ GOOD: Fetch secrets from secure backend
const getApiKey = async () => {
const response = await secureApi.get('/config/api-key');
return response.data.key;
};
// REQUIRED: HTTPS for all network requests
// Ensure no HTTP endpoints in your app
// Check fetch() calls, axios baseURL, etc.
const api = axios.create({
baseURL: 'https://api.example.com', // Must be HTTPS
});
// Enable SSL pinning for sensitive apps
import { fetch } from 'react-native-ssl-pinning';---
1.7 Reporting Criminal Activity
- [ ] Apps for reporting criminal activity MUST involve local law enforcement
- [ ] Can ONLY be offered in countries/regions where such involvement is active
---
React Native Packages Reference
| Guideline | Expo Package | Bare RN Package |
|---|---|---|
| UGC Moderation | Custom API integration | Custom API integration |
| Parental Gate | Custom implementation | Custom implementation |
| Kids Analytics | ❌ Remove all analytics | ❌ Remove all analytics |
| Secure Storage | expo-secure-store | react-native-keychain |
| SSL Pinning | - | react-native-ssl-pinning |
| Linking | expo-linking | react-native Linking |
| IAP (with parental gate) | expo-in-app-purchases | react-native-iap |
2. PERFORMANCE
2.1 App Completeness
2.1(a) Final Version Requirements
ALL submissions must be final versions with:
- [ ] All necessary metadata complete
- [ ] Fully functional URLs (no broken links)
- [ ] No placeholder text ("Lorem ipsum", "Coming soon", "TBD")
- [ ] No empty websites
- [ ] No temporary content
- [ ] Tested on-device for bugs and stability
Swift code patterns to flag:
// FLAG: Placeholder content
"Lorem ipsum"
"Coming soon"
"TBD"
"TODO"
"FIXME" // In user-facing strings
"placeholder"
"test_image"
"sample_data"
// FLAG: Debug code in production
#if DEBUG
// Ensure debug-only code doesn't affect production
#endif
print("Debug:") // Remove debug prints
NSLog("Test") // Remove test logsReact Native code patterns to flag:
// FLAG: Placeholder content in components
<Text>Lorem ipsum dolor sit amet</Text> // REJECTION
<Text>Coming soon!</Text> // REJECTION
<Text>TBD</Text> // REJECTION
<Image source={require('./placeholder.png')} /> // REJECTION
// FLAG: Placeholder in localization files (en.json, etc.)
{
"welcome": "Lorem ipsum", // REJECTION
"feature": "Coming soon" // REJECTION
}
// FLAG: Debug code in production
console.log('Debug:', data); // Remove before submission
console.warn('Test warning'); // Remove before submission
__DEV__ && console.log('Dev only'); // OK - but verify
// FLAG: Check for leftover TODO/FIXME in user-facing code
// TODO: implement this feature // Not in user-facing strings!
// ✅ GOOD: Use __DEV__ for debug-only code
if (__DEV__) {
// This won't run in production builds
console.log('Debug info');
}Demo Account Requirements:
- [ ] If app includes login, provide demo account credentials
- [ ] Enable backend services before submission
- [ ] If demo account not possible due to legal/security, may include built-in demo mode (with prior Apple approval)
- [ ] Demo mode must exhibit app's full features and functionality
2.1(b) In-App Purchase Requirements
- [ ] All in-app purchases must be complete and up-to-date
- [ ] All IAPs must be visible to reviewer
- [ ] All IAPs must be functional
- [ ] If any IAP cannot be reviewed, explain in review notes
---
2.2 Beta Testing
- [ ] Demos, betas, and trial versions do NOT belong on App Store
- [ ] Use TestFlight for beta distribution
- [ ] TestFlight apps must be intended for public distribution
- [ ] TestFlight apps must comply with App Review Guidelines
- [ ] Cannot distribute TestFlight apps for compensation (including crowdfunding rewards)
- [ ] Significant updates to beta builds require TestFlight App Review
---
2.3 Accurate Metadata
All metadata must accurately reflect core app experience and remain up-to-date.
2.3.1 Hidden/Undocumented Features and Misleading Marketing
2.3.1(a) Feature Disclosure
- [ ] Do NOT include hidden, dormant, or undocumented features
- [ ] App functionality must be clear to end users and App Review
- [ ] All new features must be described with specificity in Notes for Review
- [ ] Generic descriptions will be REJECTED
- [ ] All new features must be accessible for review
Misleading marketing grounds for REMOVAL:
- Promoting content/services app doesn't offer
- iOS virus/malware scanners (not actually possible)
- Promoting false prices
2.3.1(b) Egregious Behavior
Egregious or repeated behavior results in removal from Apple Developer Program.
2.3.2 In-App Purchase Disclosure
- [ ] Description, screenshots, and previews must clearly indicate items requiring additional purchases
- [ ] IAP Display Name, Screenshot, and Description must be appropriate for public audience
- [ ] App must properly handle
SKPaymentTransactionObserverpaymentQueuemethod for seamless IAP completion
Swift implementation:
// REQUIRED: Proper IAP observer handling
class PaymentObserver: NSObject, SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
// Deliver content
completeTransaction(transaction)
case .failed:
// Handle failure
failTransaction(transaction)
case .restored:
// Restore content
restoreTransaction(transaction)
case .deferred, .purchasing:
break
@unknown default:
break
}
}
}
}React Native implementation (react-native-iap):
// REQUIRED: Proper IAP handling with react-native-iap
import * as IAP from 'react-native-iap';
// Initialize on app start
useEffect(() => {
const initIAP = async () => {
await IAP.initConnection();
};
const purchaseUpdateSubscription = IAP.purchaseUpdatedListener(
async (purchase) => {
const receipt = purchase.transactionReceipt;
if (receipt) {
// Validate receipt on your server
await validateReceipt(receipt);
// Deliver content
await deliverContent(purchase.productId);
// Finish transaction - CRITICAL!
await IAP.finishTransaction({ purchase });
}
}
);
const purchaseErrorSubscription = IAP.purchaseErrorListener((error) => {
console.warn('Purchase error:', error);
// Handle error appropriately
});
initIAP();
return () => {
purchaseUpdateSubscription.remove();
purchaseErrorSubscription.remove();
IAP.endConnection();
};
}, []);
// REQUIRED: Restore purchases functionality
const restorePurchases = async () => {
try {
const purchases = await IAP.getAvailablePurchases();
for (const purchase of purchases) {
await deliverContent(purchase.productId);
}
} catch (error) {
console.error('Restore failed:', error);
}
};2.3.3 Screenshots
- [ ] Must show app in use
- [ ] Must NOT be merely title art, login page, or splash screen
- [ ] May include text/image overlays demonstrating input mechanisms
- [ ] May show extended functionality (Touch Bar, etc.)
2.3.4 Previews
- [ ] May ONLY use video screen captures of app itself
- [ ] Stickers/iMessage extensions may show Messages app experience
- [ ] May add narration and overlays to explain unclear functionality
2.3.5 Category Selection
- [ ] Select most appropriate category
- [ ] Apple may change category if significantly off-base
2.3.6 Age Rating
- [ ] Answer age rating questions honestly
- [ ] Rating must align with parental controls
- [ ] Mis-rating may trigger government regulator inquiry
- [ ] Responsible for complying with local content rating requirements
2.3.7 App Name, Keywords, and Metadata Integrity
Requirements:
- [ ] App name must be unique (max 30 characters)
- [ ] Keywords must accurately describe app
Do NOT include in metadata:
- [ ] Trademarked terms you don't own
- [ ] Popular app names
- [ ] Pricing information
- [ ] Irrelevant phrases to game the system
App subtitles must:
- [ ] Follow standard metadata rules
- [ ] Not include inappropriate content
- [ ] Not reference other apps
- [ ] Not make unverifiable product claims
2.3.8 Age-Appropriate Metadata
- [ ] Metadata must be appropriate for ALL audiences
- [ ] Icons, screenshots, previews must adhere to 4+ rating (even if app is rated higher)
- [ ] Do NOT depict violence, weapons, or mature content in metadata
- [ ] Terms "For Kids" and "For Children" reserved for Kids Category
- [ ] Ensure all icon variants (small, large, Watch, alternates) are similar
2.3.9 Rights and Fictional Information
- [ ] Secure rights to all materials in icons, screenshots, previews
- [ ] Display fictional account information instead of real person data
2.3.10 Platform Focus and Metadata Relevance
- [ ] App should focus on Apple platforms it supports
- [ ] Do NOT include names, icons, or imagery of other platforms (Android, Windows)
- [ ] Do NOT include alternative app marketplace references
- [ ] App metadata must focus on app itself
Code patterns to flag:
// Swift - FLAG: References to other platforms in user-facing content
"Android"
"Google Play"
"Windows"
"Download on Play Store"// React Native - FLAG: References to other platforms
// Check all user-facing strings!
const strings = {
download: "Also available on Android", // REJECTION
share: "Share on Google Play", // REJECTION
};
// FLAG: Platform-specific code leaking to iOS
import { Platform } from 'react-native';
const message = Platform.OS === 'ios'
? "Welcome to our app"
: "Welcome to Android"; // Make sure iOS doesn't see Android text!
// FLAG: Check assets for other platform logos
import googlePlayBadge from './assets/google-play-badge.png'; // Remove from iOS2.3.11 Pre-Order Accuracy
- [ ] Pre-order apps must be complete and deliverable as submitted
- [ ] Released app must NOT be materially different from advertised
- [ ] Material changes (e.g., business model) require restarting pre-order sales
2.3.12 "What's New" Text
- [ ] Clearly describe new features and product changes
- [ ] Bug fixes, security updates, performance improvements may use generic description
- [ ] Significant changes must be specifically listed
2.3.13 In-App Events
- [ ] Events must fall within event type in App Store Connect
- [ ] Event metadata must be accurate and pertain to event (not app generally)
- [ ] Events must happen at selected times/dates across storefronts
- [ ] Event deep link must direct to proper destination
---
2.4 Hardware Compatibility
2.4.1 Multi-Device Support
- [ ] iPhone apps should run on iPad whenever possible
- [ ] Encouraged to build apps for all devices
// Swift - REQUIRED: Check device support
// In Info.plist - only include truly required capabilities
<key>UIRequiredDeviceCapabilities</key>
<array>
<!-- Only include what's actually required -->
</array>// React Native - iPad support
// Ensure your app works on iPad - test thoroughly!
// In Xcode: Check "iPad" under Deployment Info
// Check for tablet-specific layouts
import { useWindowDimensions } from 'react-native';
const App = () => {
const { width } = useWindowDimensions();
const isTablet = width >= 768;
return isTablet ? <TabletLayout /> : <PhoneLayout />;
};2.4.2 Power Efficiency and Device Strain
Apps must NOT:
- [ ] Rapidly drain battery
- [ ] Generate excessive heat
- [ ] Put unnecessary strain on device resources
- [ ] Encourage placing device under mattress/pillow while charging
- [ ] Perform excessive write cycles to SSD
- [ ] Run unrelated background processes (e.g., cryptocurrency mining)
Swift code patterns to flag:
// FLAG: Aggressive polling
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
// REJECTION RISK: Too frequent
}
// FLAG: Continuous location updates without need
locationManager.startUpdatingLocation() // Use significant changes if possible
// FLAG: Crypto mining
"bitcoin", "mining", "hash_rate", "proof_of_work" // On-device = REJECTION
// FLAG: Excessive disk writes
for i in 0..<1000000 {
try data.write(to: url) // REJECTION RISK
}React Native code patterns to flag:
// FLAG: Aggressive polling
useEffect(() => {
const interval = setInterval(() => {
fetchData(); // Every 100ms = REJECTION RISK
}, 100);
return () => clearInterval(interval);
}, []);
// ✅ GOOD: Reasonable polling interval
const interval = setInterval(fetchData, 30000); // 30 seconds
// FLAG: Continuous location updates
import Geolocation from '@react-native-community/geolocation';
Geolocation.watchPosition(callback, error, {
enableHighAccuracy: true,
distanceFilter: 0, // Updates on ANY movement = battery drain
});
// ✅ GOOD: Use distance filter
Geolocation.watchPosition(callback, error, {
distanceFilter: 100, // Only update every 100 meters
});
// FLAG: Crypto mining
const mineBlock = () => { }; // REJECTION
const calculateHash = () => { }; // If for mining = REJECTION
// FLAG: Excessive AsyncStorage writes
for (let i = 0; i < 1000000; i++) {
await AsyncStorage.setItem(`key_${i}`, data); // REJECTION RISK
}2.4.3 Apple TV Hardware Input Requirements
- [ ] App must be usable without hardware beyond Siri remote or game controllers
- [ ] May provide enhanced functionality with other peripherals
- [ ] If requiring game controller, clearly explain in metadata
2.4.4 Device Restart and System Settings
Apps must NEVER:
- [ ] Suggest or require device restart unrelated to core functionality
- [ ] Encourage turning off Wi-Fi
- [ ] Encourage disabling security features
- [ ] Require system setting modifications unrelated to app
2.4.5 Mac App Store Additional Requirements
2.4.5(i) Sandboxing and File System
- [ ] Must be appropriately sandboxed
- [ ] Only use appropriate macOS APIs for modifying user data
2.4.5(ii) Packaging and Installation
- [ ] Must be packaged using Xcode technologies
- [ ] No third-party installers
- [ ] Must be self-contained, single app installation bundles
- [ ] Cannot install code/resources in shared locations
2.4.5(iii) Auto-Launch and Startup Code
- [ ] May NOT auto-launch without consent
- [ ] May NOT spawn processes continuing after user quits
- [ ] Should NOT automatically add Dock icons or desktop shortcuts
2.4.5(iv) Code and Resource Installation
- [ ] May NOT download or install standalone apps, kexts, additional code
- [ ] Cannot add functionality significantly changing from reviewed version
2.4.5(v) Privilege Escalation
- [ ] May NOT request escalation to root privileges
- [ ] May NOT use setuid attributes
2.4.5(vi) Licensing and Copy Protection
- [ ] May NOT present license screen at launch
- [ ] May NOT require license keys
- [ ] May NOT implement own copy protection
2.4.5(vii) App Store Update Distribution
- [ ] Must use Mac App Store to distribute updates
2.4.5(viii) Operating System Compatibility
- [ ] Must run on currently shipping OS
- [ ] May NOT use deprecated or optionally installed technologies (Java, etc.)
2.4.5(ix) Language and Localization
- [ ] Must contain all language/localization support in single app bundle
---
2.5 Software Requirements
2.5.1 Public APIs and Current OS
- [ ] Apps may ONLY use public APIs
- [ ] Must run on currently shipping OS
- [ ] Keep apps up-to-date
- [ ] Phase out deprecated features, frameworks, technologies
- [ ] Use APIs/frameworks for intended purposes
- [ ] Indicate framework integration in app description
Swift code patterns to flag:
// FLAG: Private API usage
let selector = NSSelectorFromString("_privateMethod") // REJECTION
perform(selector)
// FLAG: Accessing private frameworks
@import PrivateFramework; // REJECTION
// FLAG: Using undocumented methods
objc_msgSend(self, sel_registerName("_hiddenMethod")) // REJECTION
// REQUIRED: Only use documented public APIs
import UIKit
import SwiftUI
import StoreKit // All public frameworksReact Native code patterns to flag:
// FLAG: Accessing private native APIs via native modules
// In your native module (iOS)
// Don't call private Objective-C methods!
// FLAG: Using deprecated React Native APIs
import { NativeModules } from 'react-native';
NativeModules.SomeDeprecatedModule; // Check if deprecated
// ✅ GOOD: Use maintained, public packages
import { Camera } from 'expo-camera'; // Public API
import * as IAP from 'react-native-iap'; // Public StoreKit wrapper2.5.2 Self-Contained Bundles
- [ ] Apps should be self-contained in bundles
- [ ] May NOT read/write data outside designated container area
- [ ] May NOT download, install, or execute code that changes app features/functionality
Exception for Educational Code:
- [ ] Educational apps teaching executable code may download code if:
- Code not used for other purposes
- Source code completely viewable and editable by user
Swift code patterns to flag:
// FLAG: Dynamic code execution
let script = downloadScript()
JSContext().evaluateScript(script) // REJECTION unless educational
// FLAG: Code injection
dlopen("downloaded_library.dylib", RTLD_NOW) // REJECTION
// FLAG: Writing outside container
FileManager.default.createFile(atPath: "/usr/local/bin/app") // REJECTIONReact Native code patterns to flag:
// ⚠️ CRITICAL: CodePush and OTA Updates
// CodePush IS allowed but with restrictions!
// ✅ ALLOWED: Bug fixes and minor changes via CodePush
import codePush from 'react-native-code-push';
codePush.sync(); // OK for bug fixes
// ❌ NOT ALLOWED: Significant feature changes via CodePush
// - Adding new screens/features
// - Changing app's primary purpose
// - Bypassing App Review for major updates
// FLAG: Executing downloaded JavaScript
const downloadedCode = await fetch('https://example.com/code.js');
eval(downloadedCode); // REJECTION
// FLAG: Dynamic requires
const module = require(dynamicPath); // REJECTION RISK
// ✅ GOOD: Static imports only
import { MyComponent } from './MyComponent';2.5.3 Malicious Code
Apps will be REJECTED for:
- [ ] Transmitting viruses
- [ ] Transmitting files, code, programs harming/disrupting OS or hardware
- [ ] Disrupting Push Notifications or Game Center
Egregious violations result in removal from Apple Developer Program.
2.5.4 Multitasking Background Services
Background services may ONLY be used for intended purposes:
- [ ] VoIP
- [ ] Audio playback
- [ ] Location
- [ ] Task completion
- [ ] Local notifications
Swift configuration:
// REQUIRED: Proper background mode usage
// In Info.plist
<key>UIBackgroundModes</key>
<array>
<string>audio</string> <!-- Only if playing audio -->
<string>location</string> <!-- Only if tracking location -->
<string>voip</string> <!-- Only if VoIP app -->
<string>fetch</string> <!-- Only if fetching content -->
</array>
// FLAG: Misusing background modes
// Using audio background mode just to keep app alive = REJECTIONReact Native background handling:
// FLAG: Misusing background modes
// Don't add background modes you don't actually need!
// Check ios/[AppName]/Info.plist for UIBackgroundModes
// Only include modes you legitimately use
// ✅ GOOD: Proper background audio (react-native-track-player)
import TrackPlayer from 'react-native-track-player';
// Requires audio background mode - legitimate use
// ✅ GOOD: Proper background location
import Geolocation from 'react-native-geolocation-service';
// Requires location background mode - only for navigation/fitness apps
// ❌ BAD: Playing silent audio to keep app alive
TrackPlayer.play(silentAudio); // REJECTION
// For background tasks, use proper APIs:
import BackgroundFetch from 'react-native-background-fetch';
// Configure in Info.plist with 'fetch' background mode2.5.5 IPv6-Only Networks
- [ ] Apps MUST be fully functional on IPv6-only networks
Swift implementation:
// REQUIRED: IPv6 compatibility
// Test with Network Link Conditioner: "100% Loss" for IPv4
// BAD: Hardcoded IPv4 addresses
let server = "192.168.1.1" // REJECTION RISK
// GOOD: Use hostnames
let server = "api.example.com"React Native implementation:
// REQUIRED: IPv6 compatibility
// Test your app with Network Link Conditioner!
// ❌ BAD: Hardcoded IPv4 addresses
const API_URL = 'http://192.168.1.1:3000'; // REJECTION RISK
// ✅ GOOD: Use hostnames
const API_URL = 'https://api.example.com';
// Check all fetch() calls and API configurations
const api = axios.create({
baseURL: 'https://api.example.com', // Hostname, not IP
});
// Also check: WebSocket connections, native module configs
const socket = new WebSocket('wss://api.example.com/ws'); // Good2.5.6 Web Browser Requirements
- [ ] Apps browsing web MUST use appropriate WebKit framework
- [ ] May apply for entitlement to use alternative web browser engine
Swift implementation:
// REQUIRED: Use WebKit
import WebKit
let webView = WKWebView(frame: .zero)
// FLAG: Custom browser engines without entitlement
// JavaScriptCore for full browser functionality = REJECTIONReact Native implementation:
// REQUIRED: Use react-native-webview (uses WKWebView on iOS)
import { WebView } from 'react-native-webview';
const MyWebView = () => (
<WebView
source={{ uri: 'https://example.com' }}
// Uses WKWebView on iOS - compliant!
/>
);
// ✅ react-native-webview uses WKWebView internally
// No additional configuration needed for compliance2.5.8 Alternate Desktop/Home Screen Environments
- [ ] Apps creating alternate desktop/home screen environments will be REJECTED
2.5.9 Standard UI Element Alterations
Apps will be REJECTED for:
- [ ] Altering or disabling Volume Up/Down switches
- [ ] Altering or disabling Ring/Silent switch
- [ ] Altering other native UI elements/behaviors
- [ ] Blocking links users expect to work
2.5.11 SiriKit and Shortcuts
2.5.11(i) Appropriate Intent Registration
- [ ] Only register intents app can handle without additional support
- [ ] Only register intents users would expect from stated functionality
2.5.11(ii) Vocabulary and Aliases
- [ ] Vocabulary and phrases must pertain to app and Siri functionality
- [ ] Aliases must relate directly to app or company name
- [ ] No generic terms
- [ ] No third-party app names/services
2.5.11(iii) Direct Request Resolution
- [ ] Resolve Siri requests in most direct way possible
- [ ] Do NOT insert ads or marketing between request and fulfillment
- [ ] Only request disambiguation when required
2.5.12 CallKit, SMS Blocking, and Spam Identification
- [ ] Only block phone numbers confirmed as spam
- [ ] Clearly identify features in marketing text
- [ ] Explain criteria for blocked/spam lists
- [ ] May NOT use data for tracking, user profiles, or selling
2.5.13 Facial Recognition
- [ ] Apps using facial recognition for authentication MUST use LocalAuthentication
- [ ] Do NOT use ARKit or other facial recognition for this purpose
- [ ] Must use alternate authentication for users under 13
Swift implementation:
// REQUIRED: Use LocalAuthentication for face auth
import LocalAuthentication
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate to access account") { success, error in
// Handle result
}
// FLAG: Using ARKit for authentication
ARFaceTrackingConfiguration() // For authentication = REJECTIONReact Native implementation:
// REQUIRED: Use react-native-biometrics or expo-local-authentication
import ReactNativeBiometrics from 'react-native-biometrics';
const rnBiometrics = new ReactNativeBiometrics();
const authenticate = async () => {
const { success } = await rnBiometrics.simplePrompt({
promptMessage: 'Authenticate to access account',
});
return success;
};
// Or with Expo:
import * as LocalAuthentication from 'expo-local-authentication';
const authenticate = async () => {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate to access account',
});
return result.success;
};
// FLAG: Using vision/ML for face auth
import { Camera } from 'react-native-camera';
// Don't use camera-based face recognition for auth!
// Must use device biometrics (Face ID/Touch ID)2.5.14 Recording and User Activity Logging
- [ ] Must request explicit user consent when recording/logging user activity
- [ ] Must provide clear visual and/or audible indication when recording
- [ ] Applies to: camera, microphone, screen recordings, other user inputs
2.5.15 File Selection and Display
- [ ] Apps enabling file viewing/selection should include items from Files app and iCloud documents
2.5.16 Widgets, Extensions, and Notifications
- [ ] Must be related to app content and functionality
2.5.16(a) App Clips
- [ ] All App Clip features must be in main app binary
- [ ] App Clips cannot contain advertising
2.5.17 Matter Support
- [ ] Apps supporting Matter must use Apple's support framework for pairing
- [ ] Other Matter software components must be CSA certified
2.5.18 Display Advertising
Ads limited to main app binary only. NOT allowed in:
- [ ] Extensions
- [ ] App Clips
- [ ] Widgets
- [ ] Notifications
- [ ] Keyboards
- [ ] watchOS apps
Ad requirements:
- [ ] Appropriate for app's age rating
- [ ] Allow user to see all targeting information without leaving app
- [ ] No targeted/behavioral advertising based on:
- Health/medical data (HealthKit)
- School/classroom data (ClassKit)
- Kids data (Kids Category apps)
Interstitial/blocking ads must:
- [ ] Clearly indicate they are ads
- [ ] Not manipulate/trick users into tapping
- [ ] Provide easily accessible close/skip buttons (large enough for easy dismissal)
- [ ] Apps must include ability to report inappropriate ads
---
React Native Packages Reference
| Guideline | Expo Package | Bare RN Package |
|---|---|---|
| In-App Purchase | expo-in-app-purchases | react-native-iap |
| Biometric Auth | expo-local-authentication | react-native-biometrics |
| WebView | - | react-native-webview |
| Background Tasks | expo-background-fetch, expo-task-manager | react-native-background-fetch |
| Location | expo-location | react-native-geolocation-service |
| OTA Updates | expo-updates | react-native-code-push |
| Secure Storage | expo-secure-store | react-native-keychain |
| Device Info | expo-device | react-native-device-info |
React Native Pre-Submission Checklist
- [ ] Remove all
console.logstatements (or wrap in__DEV__) - [ ] Remove placeholder content from all screens
- [ ] Test on real iOS device (not just simulator)
- [ ] Test IAP flow end-to-end including restore
- [ ] Verify no hardcoded IP addresses
- [ ] Check all background modes are legitimately used
- [ ] Ensure CodePush only delivers bug fixes, not features
- [ ] Test on IPv6-only network
- [ ] Verify biometrics use LocalAuthentication APIs
3. BUSINESS
3.1 Payments
3.1.1 In-App Purchase
MUST use In-App Purchase for unlocking:
- [ ] Features or functionality
- [ ] Subscriptions
- [ ] In-game currencies
- [ ] Game levels
- [ ] Access to premium content
- [ ] Unlocking full version
Apps may NOT use own mechanisms such as:
- [ ] License keys
- [ ] Augmented reality markers
- [ ] QR codes
- [ ] Cryptocurrencies and cryptocurrency wallets
Swift implementation:
// REQUIRED: Use StoreKit for digital goods
import StoreKit
// Good: Using In-App Purchase
func purchasePremiumFeature() async throws {
let product = try await Product.products(for: ["com.app.premium"]).first!
let result = try await product.purchase()
// Handle purchase result
}
// BAD: External payment for digital goods
func purchasePremiumFeature() {
openStripeCheckout() // REJECTION for digital content
openPayPalCheckout() // REJECTION for digital content
}React Native implementation (react-native-iap):
// REQUIRED: Use react-native-iap for digital goods
import * as IAP from 'react-native-iap';
const productIds = ['com.app.premium', 'com.app.coins_100'];
// Initialize and get products
const initializeIAP = async () => {
await IAP.initConnection();
const products = await IAP.getProducts({ skus: productIds });
return products;
};
// ✅ GOOD: Using In-App Purchase
const purchasePremiumFeature = async (sku: string) => {
try {
await IAP.requestPurchase({ sku });
} catch (error) {
console.error('Purchase failed:', error);
}
};
// ❌ BAD: External payment for digital goods
const purchasePremiumFeature = () => {
// REJECTION for digital content!
Linking.openURL('https://stripe.com/checkout');
Linking.openURL('https://paypal.com/checkout');
};
// ✅ OK: External payment for physical goods
const purchasePhysicalProduct = () => {
// Physical goods CAN use external payment
Linking.openURL('https://yourstore.com/checkout');
};Tips and Credits:
- [ ] Apps may use IAP currencies to enable "tipping" developers or content providers
Credits and Currency:
- [ ] Credits/in-game currencies purchased via IAP may NOT expire
- [ ] Must have restore mechanism for restorable purchases
Gifting:
- [ ] Apps may enable gifting of IAP-eligible items
- [ ] Gifts may only be refunded to original purchaser
- [ ] Gifts may NOT be exchanged
Mac App Store Exception:
- [ ] Mac apps may host plug-ins enabled with mechanisms other than App Store
Loot Boxes:
- [ ] Apps offering randomized virtual items MUST disclose odds BEFORE purchase
Swift implementation:
// REQUIRED: Loot box odds disclosure
struct LootBox {
let items: [(item: Item, probability: Double)]
func displayOdds() -> String {
return items.map { "\($0.item.name): \($0.probability * 100)%" }
.joined(separator: "\n")
}
}
// Must show this to user BEFORE purchase
let oddsView = LootBoxOddsView(lootBox: currentLootBox)React Native implementation:
// REQUIRED: Loot box odds disclosure
interface LootBoxItem {
name: string;
rarity: 'common' | 'rare' | 'epic' | 'legendary';
probability: number; // 0-1
}
const lootBoxItems: LootBoxItem[] = [
{ name: 'Common Sword', rarity: 'common', probability: 0.60 },
{ name: 'Rare Shield', rarity: 'rare', probability: 0.25 },
{ name: 'Epic Armor', rarity: 'epic', probability: 0.12 },
{ name: 'Legendary Crown', rarity: 'legendary', probability: 0.03 },
];
// REQUIRED: Show odds BEFORE purchase
const LootBoxOddsModal: React.FC = () => (
<Modal visible={showOdds}>
<Text style={styles.title}>Drop Rates</Text>
{lootBoxItems.map((item) => (
<View key={item.name} style={styles.row}>
<Text>{item.name}</Text>
<Text>{(item.probability * 100).toFixed(1)}%</Text>
</View>
))}
<Button title="Purchase" onPress={purchaseLootBox} />
</Modal>
);
// Must display odds before allowing purchase
const handleLootBoxPurchase = () => {
setShowOdds(true); // Show odds first!
};Digital Gift Cards:
- [ ] Digital gift cards redeemable for digital goods/services can ONLY be sold using IAP
- [ ] Physical gift cards mailed to customers may use other payment methods
Free Trial Periods (Non-Subscription):
- [ ] Use Non-Consumable IAP at Price Tier 0
- [ ] Naming convention: "XX-day Trial"
- [ ] Must clearly identify: duration, content no longer accessible after trial, downstream charges
NFTs:
- [ ] May use IAP for minting, listing, transferring NFT services
- [ ] May allow users to view their own NFTs
- [ ] NFT ownership may NOT unlock features/functionality
- [ ] May allow browsing others' NFT collections
- [ ] Outside US: No external purchase links for NFTs
3.1.1(a) Link to Other Purchase Methods
StoreKit External Purchase Link Entitlements:
- [ ] Available in specific regions only
- [ ] May include link to developer website for other purchase methods
- [ ] May inform users of lower prices elsewhere
- [ ] US storefront apps may include external purchase links without entitlement
Music Streaming Services Entitlements:
- [ ] May include link/buy button to developer website
- [ ] May invite users to provide email for purchase links
- [ ] Limited to specific storefronts
Fraud and Misconduct: Misleading marketing, scams, or fraud results in removal from App Store and potentially Developer Program.
3.1.2 Subscriptions
Core Requirements:
- [ ] Must provide ongoing value to customer
- [ ] Minimum 7-day subscription period
- [ ] Must be available across all user's devices
3.1.2(a) Permissible Uses
Examples of appropriate subscriptions:
- [ ] New game levels
- [ ] Episodic content
- [ ] Multiplayer support
- [ ] Consistent, substantive updates
- [ ] Access to large/continually updated media content
- [ ] Software as a service (SAAS)
- [ ] Cloud support
Additional rules:
- [ ] Subscriptions may be offered alongside à la carte offerings
- [ ] Gaming subscription services may share subscription across third-party apps
- [ ] Games must be downloaded from App Store
- [ ] Must avoid duplicate payment by subscriber
- [ ] Must not disadvantage non-subscriber customers
- [ ] Must work on all user's devices
- [ ] User should get value without additional tasks (posting to social media, uploading contacts, etc.)
- [ ] May include consumable credits, gems, currencies
- [ ] When changing to subscription model, don't remove functionality existing users paid for
- [ ] May offer free trial periods
Scams: Apps attempting to scam users or use bait-and-switch tactics will be REMOVED.
Cellular Carrier Bundling:
- [ ] Requires prior Apple approval
- [ ] Cannot include access to or discounts on consumable items
- [ ] Must terminate coincident with cellular data plan
3.1.2(b) Upgrades and Downgrades
- [ ] Users should have seamless upgrade/downgrade experience
- [ ] Users should NOT be able to inadvertently subscribe to multiple variations of same thing
3.1.2(c) Subscription Information
Before asking customer to subscribe, clearly describe:
- [ ] What user will get for the price
- [ ] How many issues per month? How much cloud storage? What access?
- [ ] Comply with Schedule 2 of Developer Program License Agreement
// REQUIRED: Clear subscription information
struct SubscriptionInfo {
let price: String
let period: String
let features: [String]
let renewalTerms: String
let cancellationInstructions: String
var displayText: String {
"""
\(price) per \(period)
Includes:
\(features.map { "• \($0)" }.joined(separator: "\n"))
\(renewalTerms)
To cancel: \(cancellationInstructions)
"""
}
}3.1.3 Other Purchase Methods
The following may use purchase methods other than IAP:
3.1.3(a) "Reader" Apps
- [ ] May allow access to previously purchased content (magazines, newspapers, books, audio, music, video)
- [ ] May offer free tier account creation
- [ ] May offer account management for existing customers
- [ ] May apply for External Link Account Entitlement
3.1.3(b) Multiplatform Services
- [ ] May allow access to content acquired on other platforms/web
- [ ] Includes consumable items in multi-platform games
- [ ] Must also be available as IAP within the app
3.1.3(c) Enterprise Services
- [ ] Apps sold directly to organizations for employees/students
- [ ] Consumer/single user/family sales MUST use IAP
3.1.3(d) Person-to-Person Services
- [ ] Real-time services between TWO individuals (tutoring, medical consultations, real estate tours, fitness training)
- [ ] One-to-few and one-to-many MUST use IAP
3.1.3(e) Goods and Services Outside of the App
- [ ] Physical goods consumed outside app MUST use payment methods other than IAP (Apple Pay, credit card)
3.1.3(f) Free Stand-alone Apps
- [ ] Free companions to paid web tools (VoIP, cloud storage, email, web hosting)
- [ ] No purchasing inside app
- [ ] No calls to action for purchase outside app
3.1.3(g) Advertising Management Apps
- [ ] Apps for advertisers to manage campaigns across media types
- [ ] Don't display advertisements themselves
- [ ] Digital purchases for in-app content MUST use IAP
3.1.4 Hardware-Specific Content
- [ ] Features dependent on specific hardware may unlock without IAP
- [ ] Features working with optional physical products may unlock without IAP (if IAP option also available)
- [ ] May NOT require purchase of unrelated products
- [ ] May NOT require advertising/marketing activities to unlock
3.1.5 Cryptocurrencies
(i) Wallets
- [ ] May facilitate virtual currency storage
- [ ] Must be from developers enrolled as organization
(ii) Mining
- [ ] Apps may NOT mine on device
- [ ] Cloud-based mining is allowed
Swift patterns to flag:
// FLAG: On-device crypto mining
func mineBlock() { } // REJECTION
func calculateHash() { } // If for mining = REJECTION
// OK: Cloud-based mining reference
func checkCloudMiningStatus() { } // AllowedReact Native patterns to flag:
// ❌ FLAG: On-device crypto mining
const mineBlock = () => { }; // REJECTION
const calculateHash = (data: string) => { }; // If for mining = REJECTION
import CryptoMiner from 'some-crypto-lib'; // REJECTION
// ❌ FLAG: Crypto rewards for tasks
const rewardCrypto = () => {
// Cannot reward crypto for:
// - Downloading other apps
// - Social media posts
// - Referrals
};
// ✅ OK: Cloud-based mining status
const checkCloudMiningStatus = async () => {
const status = await api.get('/mining/status');
return status;
};
// ✅ OK: Crypto wallet apps (must be organization account)
// Use established libraries like ethers.js for wallet functionality
import { ethers } from 'ethers';(iii) Exchanges
- [ ] May facilitate transactions on approved exchange
- [ ] Only in countries with appropriate licensing/permissions
(iv) Initial Coin Offerings
- [ ] ICOs, futures trading, crypto-securities trading must come from:
- Established banks
- Securities firms
- Futures commission merchants (FCM)
- Other approved financial institutions
- [ ] Must comply with all applicable law
(v) Task Completion Restrictions
- [ ] May NOT offer cryptocurrency for completing tasks:
- Downloading other apps
- Encouraging others to download
- Posting to social networks
---
3.2 Other Business Model Issues
3.2.1 Acceptable
(i) Self-Promotion
- [ ] May display own apps for purchase/promotion (if not merely a catalog)
(ii) Third-Party App Collections
- [ ] May display/recommend third-party apps for specific approved needs
- [ ] Must provide robust editorial content (not mere storefront)
(iii) Rental Content Expiration
- [ ] May disable access to rental content after rental period expires
- [ ] All other items/services may NOT expire
(iv) Wallet Passes
- [ ] May be used for: payments, offers, identification (movie tickets, coupons, VIP credentials)
- [ ] Other uses may result in rejection and credential revocation
(v) Insurance Apps
- [ ] Must be free
- [ ] Must be legally compliant in distributed regions
- [ ] Cannot use IAP
(vi) Approved Nonprofits
- [ ] Approved nonprofits may fundraise directly
- [ ] Must offer Apple Pay support
- [ ] Must disclose fund usage
- [ ] Must abide by all laws
- [ ] Must ensure tax receipts available to donors
- [ ] Nonprofit platforms must ensure all listed nonprofits are approved
(vii) Monetary Gifts
- [ ] May enable monetary gifts between individuals
- [ ] Must be completely optional
- [ ] 100% of funds must go to receiver
- [ ] Gifts connected to digital content MUST use IAP
(viii) Financial Services
- [ ] Trading, investing, money management apps must be from financial institution
- [ ] Must have necessary licensing/permissions
3.2.2 Unacceptable
(i) App Store-Like Interfaces
- [ ] Cannot create interface displaying third-party apps similar to App Store
(iii) Ad Manipulation
- [ ] Cannot artificially increase ad impressions or click-throughs
- [ ] Apps predominantly for displaying ads will be REJECTED
(iv) Unauthorized Fundraising
- [ ] Cannot collect funds for charities unless approved nonprofit
- [ ] Charity apps must be free and collect funds outside app (Safari, SMS)
(v) Arbitrary User Restriction
- [ ] Cannot arbitrarily restrict users by location or carrier
(vii) Artificial Status Manipulation
- [ ] Cannot artificially manipulate user visibility/status/rank on other services
(viii) Derivatives Trading
- [ ] Binary options trading apps NOT permitted
- [ ] CFDs and other derivatives apps must be properly licensed
(ix) Personal Loan Apps
Must clearly disclose:
- [ ] Equivalent maximum APR
- [ ] Payment due date
- [ ] May NOT charge APR higher than 36% (including costs/fees)
- [ ] May NOT require repayment in full in 60 days or less
(x) Forced Actions
Apps must NOT force users to:
- [ ] Rate the app
- [ ] Review the app
- [ ] Download other apps
- [ ] Other store-related actions
In order to access functionality, content, or use the app.
---
React Native Packages Reference
| Guideline | Relevant Packages |
|---|---|
| In-App Purchase | react-native-iap |
| Subscriptions | react-native-iap (handles subscriptions too) |
| Apple Pay | @stripe/stripe-react-native, react-native-payments |
| Crypto Wallets | ethers, web3 (organization accounts only) |
React Native IAP Best Practices
// Complete IAP setup for React Native
import * as IAP from 'react-native-iap';
const itemSkus = Platform.select({
ios: ['com.app.premium', 'com.app.subscription_monthly'],
android: ['premium', 'subscription_monthly'],
});
// 1. Initialize on app start
useEffect(() => {
const init = async () => {
try {
await IAP.initConnection();
// Get available products
const products = await IAP.getProducts({ skus: itemSkus });
setProducts(products);
} catch (error) {
console.error('IAP init failed:', error);
}
};
// 2. Listen for purchase updates
const purchaseListener = IAP.purchaseUpdatedListener(async (purchase) => {
if (purchase.transactionReceipt) {
// Validate on server
await validatePurchase(purchase);
// Deliver content
await deliverContent(purchase.productId);
// CRITICAL: Finish transaction
await IAP.finishTransaction({ purchase, isConsumable: false });
}
});
init();
return () => {
purchaseListener.remove();
IAP.endConnection();
};
}, []);
// 3. REQUIRED: Restore purchases button
const RestorePurchasesButton = () => (
<Button
title="Restore Purchases"
onPress={async () => {
const purchases = await IAP.getAvailablePurchases();
for (const purchase of purchases) {
await deliverContent(purchase.productId);
}
Alert.alert('Restored', 'Your purchases have been restored.');
}}
/>
);4. DESIGN
4.1 Copycats
4.1(a) Original Ideas
- [ ] Create your own ideas
- [ ] Don't copy popular apps with minor changes
- [ ] Risk of IP infringement claims
4.1(b) Impersonation
- [ ] Submitting apps impersonating other apps/services violates Developer Code of Conduct
- [ ] May result in removal from Developer Program
4.1(c) Trademark and Brand Usage
- [ ] Cannot use another developer's icon, brand, or product name without approval
---
4.2 Minimum Functionality
Apps should include features, content, and UI elevating it beyond a repackaged website.
React Native - WebView-only apps will be REJECTED:
// ❌ BAD: Pure WebView wrapper (REJECTION)
const App = () => (
<WebView source={{ uri: 'https://mywebsite.com' }} />
);
// ❌ BAD: WebView with minimal native chrome
const App = () => (
<SafeAreaView>
<WebView source={{ uri: 'https://mywebsite.com' }} />
</SafeAreaView>
);
// ✅ GOOD: Hybrid app with native features
const App = () => (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={NativeHomeScreen} />
<Tab.Screen name="Profile" component={NativeProfileScreen} />
<Tab.Screen name="WebContent" component={WebViewScreen} />
</Tab.Navigator>
</NavigationContainer>
);
// ✅ GOOD: WebView with significant native functionality
const App = () => {
// Native features that justify the app
const { pushToken } = usePushNotifications();
const { biometricAuth } = useBiometrics();
const { offlineData } = useOfflineStorage();
return (
<View>
<NativeHeader />
<WebView source={{ uri: 'https://mywebsite.com' }} />
<NativeTabBar />
</View>
);
};4.2.1 ARKit Requirements
- [ ] Must provide rich, integrated AR experiences
- [ ] Merely dropping model into AR view is NOT enough
4.2.2 Marketing Content Restrictions
- [ ] Apps shouldn't primarily be marketing materials
- [ ] Not advertisements, web clippings, content aggregators, or link collections
4.2.3 App Independence and Resource Downloads
4.2.3(i) Standalone Functionality
- [ ] App should work without requiring installation of another app
4.2.3(ii) Download Disclosure
- [ ] If additional resources needed for initial launch, disclose download size
- [ ] Prompt users before downloading
4.2.6 Template and App Generation Services
- [ ] Template apps must be submitted by content provider, not service
- [ ] Services should offer tools for clients to create customized apps
- [ ] Apps should provide unique customer experiences
- [ ] Alternative: Single binary hosting all client content (picker model)
4.2.7 Remote Desktop Clients
If mirroring specific software/services (not generic host device):
4.2.7(a) Connection Requirements
- [ ] Only connect to user-owned host device (PC or game console)
- [ ] Must be on local AND LAN-based network
4.2.7(b) Software Execution
- [ ] All software/services fully executed on host device
- [ ] Rendered on host device screen
- [ ] May not use APIs beyond what's required to stream
4.2.7(c) Account Management
- [ ] All account creation/management from host device
4.2.7(d) UI Requirements
- [ ] UI must NOT resemble iOS or App Store
- [ ] No store-like interface
- [ ] No ability to browse/select/purchase software not already owned
4.2.7(e) Cloud-Based Limitations
- [ ] Thin clients for cloud-based apps NOT appropriate
---
4.3 Spam
4.3(a) Bundle ID Restrictions
- [ ] Don't create multiple Bundle IDs for the same app
- [ ] Do not submit separate variants for each city, sports team, university, location, customer, or minor content change
- [ ] Use one app with search, configuration, downloadable content, or in-app purchase for legitimate variations
- [ ] Flag app-generator workflows that mint many near-identical apps from the same template
4.3(b) Category Saturation
- [ ] Do not submit apps that are indistinguishable from what is already widely available
- [ ] Opportunistic variants of existing categories or popular apps degrade App Store discovery and overall quality
- [ ] Saturated examples include dating, flashlight, sound effects, wallpaper, simple timers, and fortune telling apps
- [ ] New submissions in saturated categories need a meaningfully different or improved experience
- [ ] Existing apps in these categories may be removed if they are not updated, improved, or attracting customers
- [ ] Low-quality or low-effort examples include drinking games, Kama Sutra, fart, and burp apps
- [ ] Repeated submissions of mediocre, low-quality, or low-effort apps may lead to Developer Program removal
Code and repository patterns to flag:
// FLAG: white-label spam with only metadata or asset swaps
const APP_VARIANT = 'city_042'; // Review whether this should be one searchable app
const ENABLED_TEAM = 'team_red'; // Review if separate Bundle IDs exist per team---
4.4 Extensions
- [ ] Comply with App Extension Programming Guide
- [ ] Include functionality (help screens, settings)
- [ ] Clearly disclose extensions in marketing text
- [ ] Extensions may NOT include marketing, advertising, or IAP
4.4.1 Keyboard Extensions
Must:
- [ ] Provide keyboard input functionality
- [ ] Follow Sticker guidelines for images/emoji
- [ ] Provide method to progress to next keyboard
- [ ] Remain functional without full network access
- [ ] Only collect user activity to enhance keyboard functionality
Must NOT:
- [ ] Launch other apps (besides Settings)
- [ ] Repurpose keyboard buttons for other behaviors
4.4.2 Safari Extensions
- [ ] Must run on current Safari version
- [ ] May not interfere with System or Safari UI
- [ ] Must never include malicious or misleading content/code
- [ ] Should not claim access to more websites than necessary
---
4.5 Apple Sites and Services
4.5.1 Approved RSS Feeds and Scraping
- [ ] May use approved Apple RSS feeds
- [ ] May NOT scrape Apple sites (apple.com, iTunes, App Store, App Store Connect, developer portal)
- [ ] May NOT create rankings using Apple information
4.5.2 Apple Music
4.5.2(i) MusicKit on iOS
- [ ] Users must initiate playback
- [ ] Must provide standard media controls (play, pause, skip)
- [ ] May NOT require payment or monetize access to Apple Music
- [ ] Do NOT download/upload/share music files except as permitted
4.5.2(ii) Additional Licenses
- [ ] MusicKit is not replacement for synchronization/adaptation rights
- [ ] Cover art/metadata only for playback/playlists
- [ ] Not for marketing without rights-holder authorization
- [ ] Follow Apple Music Identity Guidelines
4.5.2(iii) Apple Music User Data
- [ ] Clearly disclose data access in purpose string
- [ ] Only share data to support/improve app experience
- [ ] NOT for user identification or ad targeting
4.5.3 Do Not Spam, Phish, or Send Unsolicited Messages via Apple Services
- [ ] Do NOT spam via Game Center, Push Notifications, Live Activities, or other Apple services
- [ ] Do NOT phish customers through Apple services
- [ ] Do NOT send unsolicited messages through Apple services
- [ ] Do NOT exploit Player IDs, aliases, or other information
Live Activities review points:
- [ ] Live Activities are tied to a user-initiated, time-bound activity
- [ ] Activity updates are factual and directly related to the active event
- [ ] ActivityKit push updates are not used as an extra marketing or messaging channel
- [ ] Users can end or control the Live Activity where appropriate
// FLAG: Live Activity used for unsolicited promotion instead of active event state
try await activity.update(ActivityContent(
state: PromotionState(message: "Limited time offer"),
staleDate: nil
))4.5.4 Push Notifications
- [ ] NOT required for app to function
- [ ] NOT for sensitive personal/confidential information
- [ ] NOT for promotions/marketing UNLESS opt-in
- [ ] Opt-in must be via consent language in app UI
- [ ] Must provide opt-out method
4.5.5 Game Center Player IDs
- [ ] Only use as approved by Game Center terms
- [ ] Do NOT display or share with third parties
4.5.6 Apple Emoji
- [ ] May use Unicode characters rendering as Apple emoji
- [ ] Apple emoji may NOT be used on other platforms
- [ ] May NOT embed directly in app binary
---
4.7 Mini Apps, Mini Games, Streaming Games, Chatbots, Plug-ins, Game Emulators
4.7.1 Software Requirements
- [ ] Follow all privacy guidelines (5.1)
- [ ] Include content filtering method
- [ ] Include content reporting mechanism
- [ ] Provide timely responses to concerns
- [ ] Include user blocking capability
- [ ] Follow payment guidelines (3.1)
4.7.2 Platform APIs and Technologies
- [ ] May NOT extend/expose native platform APIs without Apple permission
4.7.3 Data and Privacy Permissions
- [ ] May NOT share data/permissions to software without explicit user consent each instance
4.7.4 Software Index and Metadata
- [ ] Must provide index of software and metadata
- [ ] Must include universal links to all software
4.7.5 Age Rating and Age Restrictions
- [ ] Must provide way to identify content exceeding age rating
- [ ] Must use age restriction mechanism (verified or declared age)
- [ ] Must limit underage user access
---
4.8 Login Services
If using third-party/social login (Facebook, Google, Twitter, LinkedIn, Amazon, WeChat), must ALSO offer alternative login with:
- [ ] Only collects name and email
- [ ] Allows keeping email private
- [ ] Does NOT collect interactions for advertising without consent
Exceptions (alternative NOT required if):
- [ ] Exclusively using company's own account system
- [ ] Alternative marketplace with marketplace-specific login
- [ ] Education/enterprise app requiring existing accounts
- [ ] Government/industry citizen identification system
- [ ] Client for specific third-party service requiring direct sign-in
Swift implementation:
// REQUIRED: If using social login, also offer alternative
class LoginViewController {
@IBAction func signInWithApple() {
// Sign in with Apple (recommended alternative)
}
@IBAction func signInWithGoogle() {
// Third-party login
}
@IBAction func signInWithEmail() {
// Email-only alternative (name + email only)
}
}React Native implementation:
// REQUIRED: If using social login, must offer Sign in with Apple
import { appleAuth } from '@invertase/react-native-apple-authentication';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
const LoginScreen = () => {
// ✅ REQUIRED: Sign in with Apple (if any social login is offered)
const signInWithApple = async () => {
const credential = await appleAuth.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
});
// User can hide their email - respect this!
const { email, fullName } = credential;
await authenticateWithBackend(credential);
};
// Third-party login
const signInWithGoogle = async () => {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
await authenticateWithBackend(userInfo);
};
// Email-only alternative (collects only name + email)
const signInWithEmail = async (email: string, name: string) => {
await authenticateWithBackend({ email, name });
};
return (
<View>
{/* If you have Google login, you MUST have Apple login */}
<AppleButton onPress={signInWithApple} />
<GoogleSigninButton onPress={signInWithGoogle} />
<Button title="Continue with Email" onPress={showEmailForm} />
</View>
);
};
// Packages needed:
// - @invertase/react-native-apple-authentication
// - @react-native-google-signin/google-signin---
4.9 Apple Pay
- [ ] Provide all material purchase information PRIOR to sale
- [ ] Use Apple Pay branding and UI correctly
Recurring payments must disclose:
- [ ] Length of renewal term and auto-continuation
- [ ] What's provided each period
- [ ] Actual charges
- [ ] How to cancel
---
4.10 Monetizing Built-In Capabilities
May NOT monetize:
- [ ] Push Notifications
- [ ] Camera
- [ ] Gyroscope
- [ ] Other built-in hardware capabilities
- [ ] Apple Music access
- [ ] iCloud storage
- [ ] Screen Time APIs
- [ ] Other Apple services and technologies
---
React Native Packages Reference
| Guideline | Expo Package | Bare RN Package |
|---|---|---|
| Sign in with Apple | expo-apple-authentication | @invertase/react-native-apple-authentication |
| Google Sign-In | expo-auth-session | @react-native-google-signin/google-signin |
| Facebook Login | expo-auth-session | react-native-fbsdk-next |
| WebView | - | react-native-webview |
| Push Notifications | expo-notifications | @react-native-firebase/messaging |
| Linking | expo-linking | react-native Linking |
React Native Design Checklist
- [ ] If using social login (Google, Facebook, etc.), MUST include Sign in with Apple
- [ ] App has meaningful native functionality beyond WebView
- [ ] No App Store-like interfaces for third-party content
- [ ] Extensions/widgets don't contain ads or IAP
- [ ] Push notifications are optional and have opt-out
- [ ] App is responsive and functional on iPad (reviewers test on iPads)
---
Practical Review Considerations (Unofficial)
Note: The following are not official App Store Review Guidelines but are important practical considerations based on how Apple reviews apps.
iPad Responsiveness
Apple reviewers frequently test apps on iPads, even for iPhone-only apps. Your app must be responsive and functional on iPad to avoid rejection.
- [ ] Test your app on iPad simulator before submission
- [ ] Ensure layouts don't break on larger screens
- [ ] UI elements should be appropriately sized and positioned
- [ ] Navigation should work correctly on iPad
- [ ] Modal presentations should display properly
React Native implementation:
import { useWindowDimensions, Platform } from 'react-native';
// ✅ GOOD: Responsive design that works on iPad
const ResponsiveComponent = () => {
const { width, height } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={[
styles.container,
isTablet && styles.tabletContainer
]}>
{/* Adapt layout based on screen size */}
</View>
);
};
// ✅ GOOD: Check if running on iPad
const isIPad = Platform.OS === 'ios' && Platform.isPad;
// ✅ GOOD: Use flex layouts that adapt to screen size
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
tabletContainer: {
paddingHorizontal: 48,
maxWidth: 800,
alignSelf: 'center',
},
});Expo considerations:
import { useWindowDimensions } from 'react-native';
import Constants from 'expo-constants';
// Check device type in Expo
const deviceType = Constants.deviceType;
// 1 = Phone, 2 = Tablet, 3 = DesktopSwift implementation:
import UIKit
// ✅ GOOD: Check if running on iPad
let isIPad = UIDevice.current.userInterfaceIdiom == .pad
// ✅ GOOD: Adaptive layouts using size classes
class ResponsiveViewController: UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateLayoutForCurrentTraitCollection()
}
private func updateLayoutForCurrentTraitCollection() {
if traitCollection.horizontalSizeClass == .regular {
// iPad or large screen layout
configureTabletLayout()
} else {
// iPhone or compact layout
configurePhoneLayout()
}
}
private func configureTabletLayout() {
// Wider margins, larger touch targets, split views
}
private func configurePhoneLayout() {
// Standard phone layout
}
}
// ✅ GOOD: Using Auto Layout constraints that adapt
class AdaptiveView: UIView {
private func setupConstraints() {
NSLayoutConstraint.activate([
// Use readable content guide for text on iPad
contentView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
])
}
}
// ✅ GOOD: SwiftUI adaptive layout
import SwiftUI
struct ResponsiveView: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
var body: some View {
if horizontalSizeClass == .regular {
// iPad layout
HStack {
SidebarView()
ContentView()
}
} else {
// iPhone layout
NavigationStack {
ContentView()
}
}
}
}5. LEGAL
5.1 Privacy
5.1.1 Data Collection and Storage
(i) Privacy Policies
MUST include privacy policy link in:
- [ ] App Store Connect metadata
- [ ] Within app (easily accessible)
Privacy policy MUST:
- [ ] Identify what data is collected
- [ ] Identify how data is collected
- [ ] Identify all uses of data
- [ ] Confirm third parties provide equal protection
- [ ] Explain data retention/deletion policies
- [ ] Describe how user can revoke consent and request deletion
(ii) Permission
- [ ] Secure user consent for data collection (even anonymous data)
- [ ] Paid functionality must NOT depend on granting data access
- [ ] Provide easy way to withdraw consent
- [ ] Purpose strings must clearly describe data use
- [ ] GDPR compliance if relying on legitimate interest
Permission Usage Description Checklist
CRITICAL: Apple reviewers will reject apps with missing or vague permission usage descriptions. Every permission your app requests MUST have a clear, specific description explaining WHY and HOW the data is used.
Before submission, verify:
- [ ] All required permission keys are present in Info.plist
- [ ] Each description explains the specific feature that uses the permission
- [ ] Descriptions are user-friendly (not technical jargon)
- [ ] Descriptions match actual app functionality
- [ ] No generic phrases like "for app functionality" or "required for this app"
Complete Info.plist Permission Reference
Info.plist purpose strings (Swift & React Native):
<!-- REQUIRED: Clear purpose strings in Info.plist -->
<!-- These apply to BOTH native Swift AND React Native apps -->
<!-- CAMERA -->
<key>NSCameraUsageDescription</key>
<string>Camera is used to scan product barcodes for price comparison</string>
<!-- PHOTO LIBRARY -->
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo access allows you to upload images for your profile</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save edited photos to your photo library</string>
<!-- LOCATION -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location helps find nearby stores with the products you're looking for</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Background location is used to notify you when you're near a saved store</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Background location tracks your running route even when the app is closed</string>
<!-- MICROPHONE -->
<key>NSMicrophoneUsageDescription</key>
<string>Microphone is used for voice search and video calls with support</string>
<!-- CONTACTS -->
<key>NSContactsUsageDescription</key>
<string>Contacts help you find friends already using the app</string>
<!-- CALENDARS -->
<key>NSCalendarsUsageDescription</key>
<string>Calendar access lets you add event reminders directly to your calendar</string>
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>Add workout sessions to your calendar</string>
<!-- REMINDERS -->
<key>NSRemindersUsageDescription</key>
<string>Create reminders for tasks you save in the app</string>
<!-- BLUETOOTH -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth connects to your fitness tracker to sync workout data</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Connect to Bluetooth devices for data transfer</string>
<!-- HEALTH -->
<key>NSHealthShareUsageDescription</key>
<string>Read your step count and workout data to track fitness goals</string>
<key>NSHealthUpdateUsageDescription</key>
<string>Save workout sessions and calories burned to Apple Health</string>
<!-- MOTION -->
<key>NSMotionUsageDescription</key>
<string>Motion data is used to count your steps and track physical activity</string>
<!-- FACE ID -->
<key>NSFaceIDUsageDescription</key>
<string>Use Face ID to securely unlock the app and authorize payments</string>
<!-- SPEECH RECOGNITION -->
<key>NSSpeechRecognitionUsageDescription</key>
<string>Voice commands let you navigate the app hands-free</string>
<!-- SIRI -->
<key>NSSiriUsageDescription</key>
<string>Add Siri shortcuts to quickly start your favorite workout</string>
<!-- TRACKING -->
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you</string>
<!-- APPLE MUSIC -->
<key>NSAppleMusicUsageDescription</key>
<string>Access your Apple Music library to play songs during workouts</string>
<!-- HOME -->
<key>NSHomeKitUsageDescription</key>
<string>Control your smart home devices directly from the app</string>
<!-- LOCAL NETWORK -->
<key>NSLocalNetworkUsageDescription</key>
<string>Discover and connect to devices on your local network for casting</string>
<!-- NEARBY INTERACTION -->
<key>NSNearbyInteractionUsageDescription</key>
<string>Find the precise location of your connected devices</string>Good vs Bad Purpose Strings
| Permission | ❌ BAD (Rejection Risk) | ✅ GOOD |
|---|---|---|
| Camera | "Camera access needed" | "Take photos to attach to your support tickets" |
| Camera | "For app functionality" | "Scan QR codes to quickly add friends" |
| Photo Library | "Access photos" | "Choose photos from your library to create a collage" |
| Location | "Location required" | "Find coffee shops within 2 miles of your current location" |
| Location | "We need your location" | "Show your position on the delivery tracking map" |
| Microphone | "Microphone permission" | "Record voice memos to attach to your notes" |
| Contacts | "To access contacts" | "Find friends who are already using the app" |
| Bluetooth | "Bluetooth needed" | "Connect to your heart rate monitor during workouts" |
| Health | "Health data access" | "Read your daily step count to track progress toward your goal" |
| Tracking | "For a better experience" | "This identifier will be used to deliver personalized ads" |
Common Rejection Reasons for Permissions
Location Permission Rejections:
<!-- ❌ REJECTION: Vague description -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app requires location access</string>
<!-- ❌ REJECTION: No explanation of benefit to user -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location is used by this app</string>
<!-- ❌ REJECTION: Over-requesting (Always when WhenInUse is sufficient) -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>For location features</string>
<!-- ✅ GOOD: Specific, user-beneficial -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location is used to show nearby restaurants and calculate delivery times to your address</string>Camera/Photo Library Rejections:
<!-- ❌ REJECTION: Missing description entirely when using camera -->
<!-- If you use react-native-image-picker or expo-image-picker, you NEED these! -->
<!-- ❌ REJECTION: Generic description -->
<key>NSCameraUsageDescription</key>
<string>Camera access is required</string>
<!-- ✅ GOOD: Explains exactly what user does with camera -->
<key>NSCameraUsageDescription</key>
<string>Take photos of receipts to submit expense reports</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Select existing photos of receipts from your library</string>React Native - Edit ios/[AppName]/Info.plist:
// React Native apps must configure purpose strings in:
// ios/[AppName]/Info.plist
// Common packages requiring purpose strings:
// react-native-camera → NSCameraUsageDescription
// react-native-vision-camera → NSCameraUsageDescription, NSMicrophoneUsageDescription
// react-native-image-picker → NSPhotoLibraryUsageDescription, NSCameraUsageDescription
// @react-native-community/geolocation → NSLocationWhenInUseUsageDescription
// react-native-geolocation-service → NSLocationWhenInUseUsageDescription
// expo-location → NSLocationWhenInUseUsageDescription
// react-native-contacts → NSContactsUsageDescription
// react-native-permissions → Various depending on permissions requested
// react-native-health → NSHealthShareUsageDescription, NSHealthUpdateUsageDescription
// react-native-ble-plx → NSBluetoothAlwaysUsageDescription
// react-native-touch-id → NSFaceIDUsageDescription
// ✅ GOOD: Specific, clear purpose strings
const INFO_PLIST_STRINGS = {
NSCameraUsageDescription:
"Camera is used to scan QR codes for quick login",
NSPhotoLibraryUsageDescription:
"Photo library access lets you choose a profile picture",
NSLocationWhenInUseUsageDescription:
"Location is used to show restaurants within 5 miles of you",
};
// ❌ BAD: Vague purpose strings
const BAD_STRINGS = {
NSCameraUsageDescription: "Camera access needed", // REJECTION
NSPhotoLibraryUsageDescription: "For app features", // REJECTION
NSLocationWhenInUseUsageDescription: "Location required", // REJECTION
};Swift - Verify Info.plist permissions match code usage:
// IMPORTANT: Only request permissions you actually use
// Apple WILL reject if you request permissions without corresponding features
import AVFoundation
import Photos
import CoreLocation
import Contacts
class PermissionManager {
// ✅ GOOD: Only request what you need, when you need it
func requestCameraIfNeeded() {
// Only call this when user taps "Take Photo"
AVCaptureDevice.requestAccess(for: .video) { granted in
// Handle permission result
}
}
// ❌ BAD: Requesting all permissions at app launch
func requestAllPermissionsAtLaunch() {
// Don't do this - Apple will reject
// Request permissions contextually when the feature is used
}
}Expo - app.json/app.config.js permissions:
// expo app.json - Configure iOS permissions
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "Take photos to share in your posts",
"NSPhotoLibraryUsageDescription": "Choose photos from your library to share",
"NSLocationWhenInUseUsageDescription": "Show your location on the event map",
"NSMicrophoneUsageDescription": "Record audio for video messages",
"NSContactsUsageDescription": "Find friends who use the app",
"NSFaceIDUsageDescription": "Use Face ID to securely log in"
}
}
}
}
// Expo plugins that automatically add permission keys:
// expo-camera → NSCameraUsageDescription, NSMicrophoneUsageDescription
// expo-image-picker → NSCameraUsageDescription, NSPhotoLibraryUsageDescription
// expo-location → NSLocationWhenInUseUsageDescription
// expo-contacts → NSContactsUsageDescription
// expo-calendar → NSCalendarsUsageDescription
// expo-local-authentication → NSFaceIDUsageDescription(iii) Data Minimization
- [ ] Only request access to data relevant to core functionality
- [ ] Only collect data required for the task
- [ ] Use out-of-process picker/share sheet when possible instead of full access
(iv) Access
- [ ] Respect user permission settings
- [ ] Do NOT manipulate, trick, or force consent
- [ ] Provide alternatives for users who don't consent
(v) Account Sign-In
- [ ] Let users use app without login if no significant account-based features
- [ ] If app supports account creation, MUST offer account deletion within app
- [ ] Do NOT require personal info unless directly relevant to core functionality
- [ ] If not related to social network, provide access without social login
- [ ] Must include mechanism to revoke social network credentials
- [ ] May not store social credentials off device
Swift implementation:
// REQUIRED: Account deletion
func deleteAccount() async throws {
// Must actually delete user data, not just deactivate
try await api.deleteUserData(userId: currentUser.id)
try await api.deleteUserAccount(userId: currentUser.id)
clearLocalData()
signOut()
}
// REQUIRED: Accessible from within app
class SettingsViewController {
@IBAction func deleteAccountTapped() {
presentDeleteAccountConfirmation()
}
}React Native implementation:
// REQUIRED: Account deletion must be accessible in-app
// Cannot just link to website - must be actionable within app
const deleteAccount = async () => {
// Show confirmation
Alert.alert(
'Delete Account',
'This will permanently delete your account and all data. This cannot be undone.',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
style: 'destructive',
onPress: async () => {
try {
// Must actually delete, not just deactivate!
await api.delete('/user/account');
// Clear local data
await AsyncStorage.clear();
await Keychain.resetGenericPassword();
// Sign out and redirect
signOut();
navigation.reset({ routes: [{ name: 'Login' }] });
} catch (error) {
Alert.alert('Error', 'Failed to delete account');
}
},
},
]
);
};
// REQUIRED: Accessible from Settings screen
const SettingsScreen = () => (
<ScrollView>
{/* Other settings */}
<Button
title="Delete Account"
color="red"
onPress={deleteAccount}
/>
</ScrollView>
);
// ❌ BAD: Linking to website for deletion
const badDeleteAccount = () => {
Linking.openURL('https://example.com/delete-account'); // NOT SUFFICIENT
};(vi) Surreptitious Data Collection
- [ ] Apps surreptitiously discovering passwords or private data will be REMOVED from Developer Program
(vii) SafariViewController
- [ ] Must be used to visibly present information
- [ ] May NOT be hidden or obscured
- [ ] May NOT track users without knowledge and consent
(viii) Unauthorized Personal Information Compilation
- [ ] Apps compiling personal info from sources not directly from user (including public databases) without explicit consent NOT permitted
(ix) Highly Regulated Fields
- [ ] Apps in banking, financial services, healthcare, gambling, legal cannabis, air travel, crypto exchanges should be from legal entity, not individual developer
- [ ] Cannabis apps must be geo-restricted to legal jurisdictions
(x) Basic Contact Information
- [ ] Request for name/email must be optional
- [ ] Features/services must NOT be conditional on providing this info
5.1.2 Data Use and Sharing
(i) Explicit Permission
- [ ] Cannot use, transmit, or share personal data without permission
- [ ] Must provide access to info about how/where data is used
- [ ] Must clearly disclose third-party sharing (including third-party AI)
- [ ] Must obtain explicit permission before sharing
- [ ] Data may only be shared to improve app or serve advertising
- [ ] Must use App Tracking Transparency APIs for cross-app tracking
- [ ] May NOT require system functionalities (push, location, tracking) for functionality or compensation
- [ ] Unauthorized data sharing may result in removal from sale and Developer Program
Swift implementation:
// REQUIRED: App Tracking Transparency
import AppTrackingTransparency
func requestTrackingAuthorization() {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// May track
enableTracking()
case .denied, .restricted, .notDetermined:
// May NOT track
disableTracking()
@unknown default:
disableTracking()
}
}
}React Native implementation (react-native-tracking-transparency):
// REQUIRED: App Tracking Transparency for iOS 14.5+
import {
requestTrackingPermission,
getTrackingStatus,
} from 'react-native-tracking-transparency';
// Request on app startup or before showing ads
const requestTracking = async () => {
const status = await requestTrackingPermission();
switch (status) {
case 'authorized':
// User allowed tracking
enableTracking();
initializeAdSDKs();
break;
case 'denied':
case 'restricted':
case 'not-determined':
// User denied or hasn't decided - NO TRACKING
disableTracking();
initializeAdSDKsWithoutTracking();
break;
}
};
// Check status before any tracking
const checkTrackingStatus = async () => {
const status = await getTrackingStatus();
return status === 'authorized';
};
// ❌ BAD: Tracking without ATT prompt
import analytics from '@react-native-firebase/analytics';
analytics().setUserId(userId); // Without ATT permission = REJECTION
// ✅ GOOD: Check ATT before tracking
const trackUser = async (userId: string) => {
const canTrack = await checkTrackingStatus();
if (canTrack) {
analytics().setUserId(userId);
}
};
// Also required: Add to Info.plist
// <key>NSUserTrackingUsageDescription</key>
// <string>This identifier will be used to deliver personalized ads</string>(ii) Purpose Limitation
- [ ] Data collected for one purpose may NOT be repurposed without consent
(iii) User Profile Building Restrictions
- [ ] May NOT surreptitiously build user profiles
- [ ] May NOT identify anonymous users or reconstruct profiles from "anonymized" data
(iv) Contact and Installation Data
- [ ] Do NOT use Contacts, Photos, or other APIs to build contact database for own use or sale
- [ ] Do NOT collect info about other installed apps for analytics or marketing
(v) Contact Communications
- [ ] Only contact people at explicit individual initiative (no Select All)
- [ ] Must show clear description of how message will appear
(vi) Sensitive Health and Fitness Data
Data from these sources may NOT be used for marketing/advertising:
- [ ] HomeKit API
- [ ] HealthKit
- [ ] Clinical Health Records API
- [ ] MovementDisorder APIs
- [ ] ClassKit
- [ ] ARKit depth/facial mapping
- [ ] Camera/Photo APIs (depth/facial mapping)
(vii) Apple Pay Data Sharing
- [ ] Apple Pay data only for facilitating/improving delivery of goods/services
5.1.3 Health and Health Research
(i) Health Data Use Restrictions
- [ ] Health research data NOT for advertising, marketing, or data mining
- [ ] Only for improving health management or health research (with permission)
- [ ] May use data to provide direct benefit to user (e.g., reduced insurance premium) if:
- Submitted by entity providing benefit
- Data not shared with third party
- [ ] Must disclose specific health data collected
(ii) Data Integrity
- [ ] Must NOT write false/inaccurate data to HealthKit or health apps
- [ ] May NOT store personal health info in iCloud
(iii) Human Subject Research Consent
Consent must include:
- [ ] Nature, purpose, and duration of research
- [ ] Procedures, risks, and benefits
- [ ] Confidentiality and data handling info
- [ ] Point of contact for questions
- [ ] Withdrawal process
(iv) Ethics Review Board Approval
- [ ] Must secure approval from independent ethics review board
- [ ] Proof must be provided upon request
5.1.4 Kids
(a) Children's Privacy Laws and Data Collection
- [ ] Comply with COPPA, GDPR, and all applicable children's privacy laws
- [ ] May ask for birthdate/parental contact ONLY for statutory compliance
- [ ] Must include useful functionality regardless of age
- [ ] Should NOT include third-party analytics (safer experience)
- [ ] Should NOT include third-party advertising (safer experience)
(b) Third-Party Services in Kids Apps
- [ ] Third-party analytics/advertising permitted only if adhering to Guideline 1.3
- [ ] Apps collecting/transmitting/capable of sharing kids' personal data MUST:
- Include privacy policy
- Comply with all applicable children's privacy statutes
- [ ] Terms "For Kids" and "For Children" reserved for Kids Category
- [ ] Non-Kids Category apps cannot imply main audience is children
5.1.5 Location Services
- [ ] Use ONLY when directly relevant to features
- [ ] NOT for emergency services or autonomous control (except small devices)
- [ ] Notify and obtain consent before collecting location
- [ ] Explain purpose in app
// REQUIRED: Location purpose string
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location is used to find nearby coffee shops and show them on the map</string>
// FLAG: Over-requesting location permission
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
// Only if truly needed for background location
// GOOD: Request minimum needed
locationManager.requestWhenInUseAuthorization() // Preferred
// FLAG: Request more than needed
locationManager.requestAlwaysAuthorization() // Only if background truly needed---
5.2 Intellectual Property
5.2.1 Generally
- [ ] Only use content you created or have license for
- [ ] No protected third-party material without permission
- [ ] No misleading, false, or copycat representations
- [ ] Apps should be submitted by owner or licensee
5.2.2 Third-Party Sites/Services
- [ ] Ensure permission under service's terms of use
- [ ] Authorization must be provided upon request
5.2.3 Audio/Video Downloading
- [ ] Should NOT facilitate illegal file sharing
- [ ] No ability to save/convert/download from third-party sources without authorization
- [ ] Verify Terms of Use for streaming
5.2.4 Apple Endorsements
(a) No False Apple Association
- [ ] Don't suggest Apple is source/supplier of app
- [ ] Don't suggest Apple endorses quality or functionality
(b) Editor's Choice Badge
- [ ] Applied automatically by Apple if selected
5.2.5 Apple Products
- [ ] Don't create apps confusingly similar to Apple products/interfaces
- [ ] Apps, extensions, keyboards, Sticker packs may NOT include Apple emoji
- [ ] Music previews only with link to iTunes/Apple Music
- [ ] Activity rings should not mimic Activity control
- [ ] Apple Weather data requires proper attribution
---
5.3 Gaming, Gambling, and Lotteries
5.3.1 Sweepstakes and Contests
- [ ] Must be sponsored by developer of app
5.3.2 Official Rules
- [ ] Must be presented in app
- [ ] Must make clear Apple is NOT involved
5.3.3 In-App Purchase and Real Money Gaming
- [ ] May NOT use IAP for real money gaming credit
5.3.4 Real Money Gaming and Lotteries
- [ ] Must have necessary licensing/permissions
- [ ] Must be geo-restricted to licensed locations
- [ ] Must be free on App Store
- [ ] No illegal gambling aids (card counters)
- [ ] Lottery apps must have consideration, chance, and prize
// REQUIRED: Geo-restriction for gambling apps
func checkGamblingEligibility() -> Bool {
guard let region = Locale.current.region?.identifier else {
return false
}
let licensedRegions = ["US-NJ", "US-NV", "GB"] // Example
return licensedRegions.contains(region)
}---
5.4 VPN Apps
- [ ] Must use NEVPNManager API
- [ ] Must be from developer enrolled as organization
- [ ] Must declare data collection clearly before any user action
- [ ] May NOT sell, use, or disclose data to third parties
- [ ] Must commit to this in privacy policy
- [ ] Must NOT violate local laws
- [ ] If requiring VPN license in territory, provide in App Review Notes
// REQUIRED: NEVPNManager API
import NetworkExtension
let manager = NEVPNManager.shared()
// Use official API, not custom VPN implementation---
5.5 Mobile Device Management
May only be offered by:
- Commercial enterprises
- Educational institutions
- Government agencies
- Companies using MDM for parental control or device security (limited cases)
Requirements:
- [ ] Request MDM capability from Apple
- [ ] Declare data collection clearly before any user action
- [ ] May NOT sell, use, or disclose data except for improving services to organization
- [ ] Must commit to restrictions in privacy policy
- [ ] Must NOT violate applicable laws
---
React Native Packages Reference
| Guideline | Expo Package | Bare RN Package |
|---|---|---|
| App Tracking Transparency | expo-tracking-transparency | react-native-tracking-transparency |
| Permissions | Built-in with each expo-* package | react-native-permissions |
| Secure Storage | expo-secure-store | react-native-keychain |
| Analytics | Use @react-native-firebase/analytics (config plugin) | @react-native-firebase/analytics |
| Location | expo-location | react-native-geolocation-service |
| Contacts | expo-contacts | react-native-contacts |
| Camera | expo-camera | react-native-vision-camera |
| Image Picker | expo-image-picker | react-native-image-picker |
React Native Privacy Checklist
Permission Usage Descriptions (Critical - Common Rejection Reason)
- [ ] Verified ALL required Info.plist permission keys are present
- [ ] Each permission description explains the SPECIFIC feature using it
- [ ] No vague descriptions ("for app functionality", "required", "needed")
- [ ] Descriptions are written from user's perspective (benefit to them)
- [ ] Only requesting permissions the app actually uses
- [ ] Permissions requested contextually (not all at app launch)
Tracking & Privacy
- [ ] ATT prompt shown before any tracking (iOS 14.5+)
- [ ] NSUserTrackingUsageDescription in Info.plist
- [ ] No tracking if user denies ATT
- [ ] Privacy policy link in app settings
Account & Data
- [ ] Account deletion accessible within app (not just website link)
- [ ] Sensitive data stored in Keychain, not AsyncStorage
- [ ] HTTPS for all API calls
- [ ] No hardcoded secrets in JavaScript bundle
React Native Privacy Implementation
// Complete privacy setup for React Native
// 1. ATT on startup (before any tracking)
// Expo:
import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
// Or bare RN:
import { requestTrackingPermission } from 'react-native-tracking-transparency';
useEffect(() => {
const initPrivacy = async () => {
// Request ATT first
const trackingStatus = await requestTrackingPermission();
// Only initialize tracking SDKs if authorized
if (trackingStatus === 'authorized') {
await initializeAnalytics();
await initializeAdSDKs();
}
};
initPrivacy();
}, []);
// 2. Secure storage for sensitive data
import * as Keychain from 'react-native-keychain';
// ✅ GOOD: Store tokens in Keychain
await Keychain.setGenericPassword('auth', token);
// ❌ BAD: Store tokens in AsyncStorage
await AsyncStorage.setItem('token', token); // Not secure!
// 3. Privacy policy in settings
const SettingsScreen = () => (
<View>
<Button
title="Privacy Policy"
onPress={() => Linking.openURL('https://example.com/privacy')}
/>
<Button
title="Delete Account"
onPress={deleteAccount}
color="red"
/>
</View>
);Related skills
FAQ
Which platforms does app-store-review support?
app-store-review evaluates code for iOS, macOS, tvOS, watchOS, and visionOS apps written in Swift, Objective-C, React Native, or Expo. The skill maps findings to Apple's App Store Review Guidelines for pre-submission compliance checking.
When should developers run app-store-review?
app-store-review should run before App Store Connect upload when preparing submission, after Apple guideline updates, or when debugging review rejections. The skill triggers on compliance checking and submission readiness tasks.
Is App Store Review safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.