Home / Docs / App Store Publishing

App Store Publishing Guide

Complete Guide

A practical, no-fluff reference for shipping your Aamlaa-generated React Native app to the Apple App Store — from first build to approval.


Before You Start

Make sure you have everything in place before touching a single config file. Missing any of these will block you mid-process.

Requirements checklist

  • Apple Developer Program — $99/year membership at developer.apple.com. Required to submit to the App Store.
  • App Store Connect account — Sign in at appstoreconnect.apple.com and accept the latest agreements.
  • Node.js 18+ — Check with node -v. Use Volta or nvm to manage versions.
  • EAS CLI installed npm install -g eas-cli
  • Expo account (free) — Sign up at expo.dev. Required for EAS Build and EAS Submit.

01

Configure app.json

Every field in app.json has consequences — wrong bundle IDs, missing splash configs, and absent privacy manifests are common causes of build failures and rejections.

{
  "expo": {
    // Human-readable app name shown under the icon on the home screen
    "name": "PlantPal",

    // URL-safe identifier used in Expo services — no spaces, lowercase
    "slug": "plantpal",

    // Semantic version string. Bump before EVERY App Store submission.
    "version": "1.2.0",

    "orientation": "portrait",

    // 1024×1024 PNG, no alpha, no rounded corners (Apple adds them)
    "icon": "./assets/icon.png",

    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#09090f"
    },

    "assetBundlePatterns": ["**/*"],

    "ios": {
      // Must match EXACTLY the Bundle ID registered in App Store Connect
      "bundleIdentifier": "com.acme.plantpal",

      // String — increment for every TestFlight / App Store build
      "buildNumber": "42",

      // supportsTablet: true = iPad compatible (increases discoverability)
      "supportsTablet": true,

      // infoPlist — only include keys for permissions your app actually uses
      "infoPlist": {
        "NSCameraUsageDescription": "Scan a plant tag to auto-fill species info.",
        "NSPhotoLibraryUsageDescription": "Pick a photo to identify a plant species.",
        "NSLocationWhenInUseUsageDescription": "Show nearby garden centres and local weather.",

        // Required by Apple even if empty — submit without it and the build fails
        "NSPrivacyAccessedAPITypes": []
      },

      // Required for iOS 17+ — list all privacy-sensitive APIs your app calls
      "privacyManifests": {
        "NSPrivacyAccessedAPITypes": []
      }
    },

    "android": {
      "package": "com.acme.plantpal",
      "versionCode": 42,
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#09090f"
      },
      "permissions": [
        "android.permission.CAMERA",
        "android.permission.READ_MEDIA_IMAGES"
      ]
    },

    "web": {
      "favicon": "./assets/favicon.png"
    },

    // Required when using EAS Build / EAS Submit
    "extra": {
      "eas": {
        "projectId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Aamlaa generates a complete app.json for every project. Use the app.json generator to customise it visually before downloading.


02

Permission Strings (NSUsageDescription)

This is one of the most common reasons for rejection. Apple reviewers read these strings — vague descriptions fail Guideline 5.1.1. Each string must include a concrete, specific use case.

CameraNSCameraUsageDescription
BAD

"App needs camera"

GOOD

"Used to scan QR codes on product labels to instantly pull up nutritional info"

MicrophoneNSMicrophoneUsageDescription
BAD

"Microphone access required"

GOOD

"Record voice memos like 'watered this morning, leaves look droopy' saved alongside your plant's care log"

LocationNSLocationWhenInUseUsageDescription
BAD

"We use your location"

GOOD

"Used to show nearby garden centres and local weather conditions affecting your plants"

Photo LibraryNSPhotoLibraryUsageDescription
BAD

"Photo library access"

GOOD

"Select a photo of a houseplant to identify its species and receive a full watering and light schedule"

ContactsNSContactsUsageDescription
BAD

"Contacts permission"

GOOD

"Lets you invite friends to collaborate on shared garden plans directly from your contacts"


03

App Icons

A single 1024×1024 source icon is all Expo needs — it generates all required sizes automatically during the build.

  • 1024×1024 PNG — no alpha channel (transparency causes an App Store upload error).
  • No rounded corners — Apple applies the iOS squircle mask automatically. Adding your own corners creates a double-rounded artifact.
  • No text smaller than ~20 px — at 60×60 (iPhone home screen) fine text becomes illegible.
  • Useful tools: appicon.co or npx expo-asset-icons to preview your icon at every required size before building.

Required assets/ structure:

assets/
├── icon.png            ← 1024×1024, no alpha (iOS + Android)
├── adaptive-icon.png   ← 1024×1024 Android foreground layer
├── splash.png          ← 1284×2778 recommended (iPhone 14 Pro Max)
└── favicon.png         ← 48×48 for web

04

EAS Build Setup

EAS Build handles certificates and provisioning profiles in the cloud — no Xcode required on your machine.

1. Create eas.json in your project root:

{
  "cli": {
    "version": ">= 7.0.0"
  },
  "build": {
    "preview": {
      "distribution": "internal",
      "ios": {
        "simulator": false
      }
    },
    "production": {
      "ios": {
        "credentialsSource": "remote"
      },
      "android": {
        "credentialsSource": "remote",
        "buildType": "app-bundle"
      }
    }
  },
  "submit": {
    "production": {}
  }
}

2. Authenticate and initialise your project:

# Log in to your Expo account
eas login

# Initialise EAS for this project (writes projectId to app.json)
eas build:configure

3. Build and submit:

# Build a production iOS .ipa (queued on Expo's build servers)
eas build --platform ios --profile production

# Submit the latest successful build directly to App Store Connect
eas submit --platform ios --latest

05

App Store Connect Setup

Before submitting a build for review, every item below must be complete. Incomplete records are rejected before a human even sees your app.

  • Create App record — the Bundle ID you enter here must match ios.bundleIdentifier in app.json exactly (case-sensitive).
  • Set age rating — answer all content questions honestly. Inaccurate ratings are a rejection reason.
  • Add screenshots — all required device sizes. At minimum: 6.7″ (iPhone 15 Pro Max) and 12.9″ iPad Pro if you marked supportsTablet: true.
  • Privacy Policy URL (required) — must be a live, publicly accessible URL. Apple will reject any app without one.
  • Set pricing — free apps can be set to free immediately. Paid apps require a banking agreement.
  • App Review Information — if your app requires login, add a reviewer demo account with full credentials in the notes field. Missing login details = instant rejection under Guideline 2.1.

Common Rejection Reasons

These are the six rejections that hit Aamlaa-generated apps most often. Read each one before submitting — fixing them proactively saves days of back-and-forth.

Guideline 4.3(a)Spam

What triggers this rejection

Your app shares a similar binary, metadata, or concept to other apps on the store with only minor differences. Apple reviewers look at your first screen, your icon, and your description — if it reads like a template clone, it gets flagged.

How to fix it

The first screen a reviewer sees should immediately communicate what makes your app DIFFERENT. Use your onboarding copy to lead with a concrete differentiator: "Not Another Plant App — Built for Serious Growers". Back it up with a distinct icon colour palette and unique feature callouts in your App Store description. Copy-pasting metadata from a competitor is detected.

// Example: onboarding slide that leads with the differentiator
const OnboardingSlide1 = () => (
  <View style={styles.container}>
    <Text style={styles.eyebrow}>Not Another Plant App</Text>
    <Text style={styles.headline}>Built for Serious Growers</Text>
    <Text style={styles.body}>
      PlantPal tracks 200+ species with species-specific watering
      schedules, disease detection, and seasonal care reminders —
      not just a generic timer.
    </Text>
  </View>
);
Guideline 5.1.1Privacy — Data Collection and Storage

What triggers this rejection

Vague NSUsageDescription strings that don't explain why the permission is needed or how the data will be used. Apple reviewers read these strings and reject apps that say 'App needs camera access' or 'We use your location'.

How to fix it

Every NSUsageDescription must include a specific, user-facing sentence explaining the concrete feature that requires this permission. See Step 02 of this guide for BAD vs GOOD examples for every permission type.

// In app.json — GOOD permission strings
"infoPlist": {
  "NSCameraUsageDescription":
    "Scan a plant tag to auto-fill the species name, watering schedule, and toxicity info.",
  "NSPhotoLibraryUsageDescription":
    "Select a photo of a houseplant to identify its species and get a full care plan.",
  "NSLocationWhenInUseUsageDescription":
    "Show garden centres within 10 km and pull local frost-date data for your planting calendar."
}
Guideline 2.1App Completeness

What triggers this rejection

Placeholder content, broken navigation flows, lorem ipsum text, or a login screen with no way to create or demo an account. Reviewers cannot test your app if they can't get past the login screen.

How to fix it

Create a dedicated demo reviewer account before submitting. Put the email and password in the App Review Information notes field in App Store Connect. Never ship lorem ipsum text — search for it in every screen before building. Broken taps and empty states should be handled gracefully.

Guideline 3.1.1Payments — In-App Purchase

What triggers this rejection

Using PayPal, Stripe, or any third-party payment processor to sell digital goods, premium features, or subscriptions inside the app.

How to fix it

All digital goods and subscriptions sold inside an iOS app must go through Apple's In-App Purchase (StoreKit). Physical goods and real-world services (e.g., booking a plumber) are exempt. Use expo-in-app-purchases or react-native-iap to implement StoreKit.

Guideline 4.0Design — Minimum Functionality

What triggers this rejection

An app that is a thin wrapper around a website rendered in WKWebView with no native functionality. Apple wants apps to feel native, not like a pinned browser tab.

How to fix it

Add at least one native capability that meaningfully enhances the experience: push notifications, camera or photo library access, biometric authentication (Face ID), offline mode with local storage, a home screen widget, or Siri shortcuts. Any one of these is usually sufficient.

Guideline 2.3.3Accurate Metadata

What triggers this rejection

App Store screenshots that show features, UI states, or content that doesn't exist in the version being reviewed. Marketing mockups with 'floating phone' frames or features marked 'coming soon' are a common cause.

How to fix it

Use Xcode Simulator screenshots or real device screenshots taken from the exact build you are submitting. Screenshots must match the current UI exactly. Remove any phone frame overlays or marketing copy that implies features not present in the build.


After a Rejection

Getting rejected does not mean your app is bad — most apps are rejected on the first submission. Here is the right process.

  • Don't panic — most apps are rejected at least once. It is a normal part of the process, not a permanent ban.
  • Read the letter carefully — the specific guideline number (e.g. 4.3a vs 4.0) tells you exactly what to fix. One number, one fix.
  • Use the Resolution Center — you can reply directly to the reviewer to ask for clarification on an ambiguous rejection. Keep it professional and specific.
  • Fix ALL mentioned issues before resubmitting. Reviewers check previous rejection notes — resubmitting with only some issues fixed often results in a faster second rejection.
  • Appeal via the App Review Board — if you genuinely believe the rejection was wrong, file an appeal. It escalates to a different team and overturns approximately 20-30% of contested cases.
  • Resubmission timeline — resubmissions typically receive a decision within 24-48 hours. First submissions average 24 hours as of 2025.

Subscription Screen Requirements

If your app uses in-app subscriptions, your paywall screen must meet specific requirements or it will be rejected under Guideline 3.1.1 and 3.1.2.

  • Must clearly display the price, billing period, and any free trial length before the user taps subscribe.
  • Must include visible links to your Privacy Policy and Terms of Service.
  • Must include a "Restore Purchases" button — required for all apps with purchasable content so users can reclaim purchases on a new device.
  • No manipulative design patterns — fake countdown timers, pre-checked auto-renew toggles, or hidden cancel buttons will result in rejection and potential removal from the store.

Final Submission Checklist

Run through every item before hitting "Submit for Review". Each unchecked box is a potential rejection.

  1. 01version in app.json has been bumped from the previous submission (e.g. 1.1.0 → 1.2.0).
  2. 02bundleIdentifier matches the Bundle ID registered in App Store Connect exactly.
  3. 03All NSUsageDescription strings are descriptive with a concrete use-case example (not just "App needs camera access").
  4. 04No placeholder or lorem ipsum text visible anywhere in the app — search your codebase for lorem before building.
  5. 05Every tappable element navigates to a real screen — no dead ends or "coming soon" modals.
  6. 06Demo reviewer login credentials are entered in App Review Information notes if your app requires authentication.
  7. 07Privacy Policy URL is live and publicly accessible — test it in an incognito browser window.
  8. 08Screenshots in App Store Connect match the current UI exactly — no features shown that are not in this build.
  9. 09Age rating questionnaire is completed accurately.
  10. 10App description and keywords are unique to your app — not copied from a competitor or template.

Ready to build your app?

Aamlaa generates a production-ready app.json with correct permission strings, icon assets, and EAS configuration — saving you the setup time so you can focus on shipping.

No credit card required to start.