Expo iOS permission descriptions that pass App Review
iOS will not let your app touch the camera, location, photos, contacts, or the microphone without a purpose string — a short sentence, shown in the system permission prompt, explaining why. Ship a missing, empty, or vague one and App Review rejects the build under Guideline 5.1.1. This is one of the most common and most avoidable rejections for an Expo app.
What a purpose string is
Every protected resource has a matching Info.plist key ending in UsageDescription — for example NSCameraUsageDescription or NSLocationWhenInUseUsageDescription. iOS shows the string verbatim when it first asks the user for access. Apple documents the full list of keys and the rule that a missing string causes a crash or a rejection in Protecting the user's privacy, and the review rule itself lives in App Store Review Guideline 5.1.1.
How to set them in an Expo app
In a managed Expo project you set purpose strings under ios.infoPlist in app.json / app.config.js, or let a config plugin add them. The keys and the infoPlist block are documented in the Expo app config reference, and the permissions workflow in the Expo permissions guide. Most Expo SDK modules — for example expo-camera — also expose a config-plugin prop that writes the string for you, so you configure it in one place instead of hand-editing the plist.
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription":
"Trailmark uses the camera to attach photos to the hike you are recording."
}
}
}
}The wording App Review rejects
- Missing. The module is installed and used, but no matching key is in the final build. iOS shows a blank prompt or the app crashes on request.
- Placeholder. Left as
TODO, a template string, or an empty value. - Generic. "This app needs access to your camera." restates the permission without a reason. Apple wants the specific user benefit.
- Unused. A key is declared for a capability the app never uses. An orphaned permission is flagged as an inaccurate description under Guideline 2.3.
A string that passes names the app, the capability, and what the user gets: "<App> uses <capability> to <specific user benefit>."
What a repository check can and cannot prove
A static check reads your config and packages and can catch the common cases: a UsageDescription that is empty, still a placeholder, generic, or declared with no package that needs it. What it cannot prove from source alone is that the string actually survived your config plugins into the built binary — so a thorough preflight also reads the final build's Info.plist and compares it to your config. It does not, and cannot, know how a reviewer will judge a borderline sentence; that stays a human call.
The App Store Preflight Kit checks every purpose string in your Expo config, flags placeholders, generic wording, and permissions with no matching package, and verifies the strings made it into your final iOS build. Checkout is closed during cross-agent validation — see exactly what the skill pack checks and the planned offer.