How Did We Encounter the Expo MediaLibrary Permission Error?
During a recent project for a logistics field inspection platform, we encountered a situation where a core mobile application feature abruptly stopped working for thousands of field workers. The mobile app, built using React Native and Expo SDK 53, allows inspectors to capture photos of cargo using the device camera. These photos are stored within the app’s isolated sandbox for immediate syncing and are also exported to a specific album in the user’s local Android photo gallery for their personal records.
The application had been running flawlessly for months. The last production release was deployed in early March. Yet, without any new code pushes or updates to the Google Play Store, we suddenly saw a spike in error logs. Users were unable to save images to their gallery, and the application was throwing a Missing MEDIA_LIBRARY permissions error right at the point of saving.
In the mobile ecosystem, an application breaking without a codebase change usually points to one of two things: an expired backend token or an underlying operating system update altering API behaviors. For enterprises looking to hire software developer teams, having engineers who understand how silent OS-level updates impact compiled applications is critical. This challenge inspired this article so other engineering teams can understand the complex intersection of Expo plugins, Google Play policies, and Android’s evolving scoped storage rules.
What Was the Business Context and Where Did the Issue Appear in the Architecture?
To understand the problem, we must first look at the business requirement. The field workers needed the ability to easily access the photos they took during inspections directly from their native Android Gallery apps, separated into specific albums (e.g., “CargoInspections”).
To achieve this, we utilized expo-camera to capture the image, expo-file-system to handle the temporary file, and expo-media-library to export it to the gallery. The architecture relied on the following standard Expo implementation:
// Note: The user has already granted permissions via usePermissions()
// from expo-media-library earlier in the flow.
let photo = new File(imageUri);
const asset = await MediaLibrary.createAssetAsync(photo.uri);
// albumName is a dynamically generated string based on inspection ID
const album = await MediaLibrary.getAlbumAsync(albumName);
if (album) {
await MediaLibrary.addAssetsToAlbumAsync([asset], album, false);
} else {
await MediaLibrary.createAlbumAsync(albumName, asset, false);
}In order to comply with strict Google Play Store data privacy policies—and to ensure the app passed the automated policy review process—we explicitly blocked broad storage permissions in our app.json file. Google Play actively rejects applications that request broad storage access unless it is an absolute core functionality (like a file manager app).
Here is how our app.json was configured to block these permissions from being injected by Expo’s auto-linking plugins:
"blockedPermissions": [
"android.permission.MANAGE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_MEDIA_IMAGES",
"android.permission.READ_MEDIA_VIDEO"
]The error was originating specifically at the MediaLibrary.getAlbumAsync(albumName) call.
What Went Wrong With the Expo MediaLibrary Execution?
The primary symptom was an unhandled promise rejection: Error: Missing MEDIA_LIBRARY permissions. Because our blockedPermissions array explicitly prevented the application manifest from declaring READ_MEDIA_IMAGES and READ_EXTERNAL_STORAGE, the app lacked the authorization to query the device’s MediaStore.
But why did it work for months and suddenly fail?
The root cause was traced back to Android OS updates rolling out to the fleet of user devices. Specifically, devices updating to newer security patches of Android 13 and Android 14 began strictly enforcing Scoped Storage and MediaStore query rules via Google Play Services silent updates.
When you call MediaLibrary.createAssetAsync, newer Android versions allow applications to contribute (write) to the MediaStore without needing explicit WRITE_EXTERNAL_STORAGE permissions. However, when you call MediaLibrary.getAlbumAsync, the operating system must scan the user’s entire gallery to find an album matching that name. This is considered a read operation. By stripping READ_MEDIA_IMAGES from the manifest to appease Google Play, the app was technically operating in a grey area that older Android builds temporarily tolerated, but newer enforcing updates completely blocked.
How Did We Approach Resolving the Android Media Storage Issue?
We needed a solution that would restore functionality immediately without triggering a policy violation rejection from the Google Play Console. When companies hire react native developers for cross-platform stability, navigating these native-layer nuances is a top priority. We evaluated several architectural approaches.
Did We Consider Using the Storage Access Framework (SAF)?
We considered bypassing expo-media-library entirely and using the native Android Storage Access Framework via Expo File System’s SAF bindings. SAF allows an app to request a specific directory from the user via a native system prompt. While this bypasses the need for manifest permissions, it introduces significant friction into the UX. Field workers would have to manually select or confirm a folder on every inspection, slowing down their workflow.
Did We Consider Removing the Album Requirement entirely?
If we only used MediaLibrary.createAssetAsync and did not attempt to group the photos into a specific named album, we wouldn’t need to call getAlbumAsync, thereby bypassing the read query. However, business stakeholders rejected this approach because dumping thousands of inspection photos loosely into the main camera roll would create chaos for the users’ personal device organization.
Did We Consider Reinstating Granular Read Permissions?
Our final consideration was to selectively remove the block on READ_MEDIA_IMAGES (introduced in Android 13/API 33) while keeping READ_EXTERNAL_STORAGE and MANAGE_EXTERNAL_STORAGE blocked. The challenge here was proving to Google Play that our app genuinely required reading images. Since we were creating an album and checking for its existence, this technically justified the read permission under the latest policies, provided we filled out the Google Play Console Data Safety declaration correctly.
How Was the Final Implementation Executed?
We proceeded with a hybrid approach: modifying the Expo configuration to allow the specific, granular Android 13+ permission while handling legacy devices gracefully.
First, we updated the app.json to unblock READ_MEDIA_IMAGES. We retained the blocks on broader permissions to ensure Play Store compliance.
"blockedPermissions": [
"android.permission.MANAGE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_MEDIA_VIDEO"
]Next, we updated our React Native service layer to gracefully handle environments where permissions might still be restricted. We implemented a try-catch block specifically around the album query to fallback to a basic asset save if the album query failed, preventing a hard crash of the inspection workflow.
let photo = new File(imageUri);
let asset;
try {
asset = await MediaLibrary.createAssetAsync(photo.uri);
} catch (error) {
console.error("Failed to create asset in gallery", error);
return;
}
try {
// Attempt to query the album. Requires READ_MEDIA_IMAGES on API 33+
const album = await MediaLibrary.getAlbumAsync(albumName);
if (album) {
await MediaLibrary.addAssetsToAlbumAsync([asset], album, false);
} else {
await MediaLibrary.createAlbumAsync(albumName, asset, false);
}
} catch (albumError) {
// Fallback: If getting/creating the album fails due to permission blocks,
// the asset is at least saved to the main gallery via createAssetAsync.
console.warn("Album assignment failed, likely due to missing read permissions. Asset saved loosely.", albumError);
}Finally, we updated our Google Play Console Data Safety form to explicitly declare that we request READ_MEDIA_IMAGES solely for the purpose of managing the app-created photo album, which resulted in a successful, unblocked app update.
What Actionable Lessons Can Engineering Teams Learn From This?
- OS Updates Behave Like Silent Code Changes: An app doesn’t need to be updated to break. When the underlying OS enforces new security policies via silent updates (like Android Play Services), previously functioning API calls can throw fatal exceptions.
- Write Does Not Implies Read in Modern Android: Scoped Storage allows apps to insert media into the MediaStore without permissions, but querying the MediaStore (even to find an album you just created) requires strict Read permissions.
- Play Store Policies Demand Granularity: Blocking all permissions in
app.jsonis a blunt instrument. Engineering teams must understand the exact minimum permissions required (e.g.,READ_MEDIA_IMAGESvsREAD_EXTERNAL_STORAGE) and block only the broad, outdated ones. - Always Wrap Native Module Calls: Native module calls like
getAlbumAsyncshould always be wrapped in atry-catch. A failed gallery organization should never block the primary business logic of saving the data. - Align Engineering with Compliance: If you hire app developer to create a mobile app, ensure they are comfortable navigating the Google Play Console policy center. Technical correctness in code does not matter if the app is rejected by automated policy bots.
How Can You Prevent Android Storage Permission Failures in the Future?
Mobile platforms are rapidly tightening user privacy and storage access constraints. What works today might be categorized as a security violation tomorrow. By staying ahead of Android API level changes and understanding the granular nature of Expo’s native modules, engineering teams can build resilient applications that survive OS-level shifts. If your enterprise is struggling with mobile architecture, cross-platform stability, or native module integration, we can help you scale your delivery. contact us to explore how we can support your technical initiatives.
Social Hashtags
#ReactNative #Expo #Android13 #Android14 #ExpoSDK53 #MediaLibrary #ScopedStorage #MobileDevelopment #GooglePlay #JavaScript #TypeScript #AppDevelopment #AndroidDev #SoftwareEngineering #ReactJS
Frequently Asked Questions
Older Android versions and previous Android 13 security patches had more lenient enforcement of Scoped Storage. A silent Google Play Services or OS patch likely rolled out to devices, enforcing the rule that querying the MediaStore requires explicit read permissions.
Yes. You can use MediaLibrary.createAssetAsync() to save an image to the general camera roll. The OS allows write-only contributions without broad permissions on modern Android versions. The error only occurs when you attempt to read or query existing albums.
You must restrict your request to granular permissions (READ_MEDIA_IMAGES instead of READ_EXTERNAL_STORAGE). Furthermore, you must accurately fill out the Data Safety declaration in the Play Console, explaining exactly why your app needs to read media to fulfill its core functionality.
Blocking permissions via the blockedPermissions array prevents Expo from auto-injecting them into the AndroidManifest.xml. While this helps pass automated Play Store checks by removing restricted permissions, it will cause runtime crashes if your JavaScript code still attempts to invoke APIs that rely on those native permissions.
Success Stories That Inspire
See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.

California-based SMB Hired Dedicated Developers to Build a Photography SaaS Platform

Swedish Agency Built a Laravel-Based Staffing System by Hiring a Dedicated Remote Team

















