Fixing React Native Android Apps That Lose File Access After Targeting Scoped Storage on Android 11+
A React Native app that read and wrote files anywhere on external storage without issue suddenly can't find files it created, gets permission denials on paths that worked fine before, or has features silently stop working — not because anything in the app's own code changed, but because raising targetSdkVersion to 30 or higher switches the app's storage model to Scoped Storage, which changes what file paths actually mean.
The Problem
A React Native app that previously read and wrote files freely in locations like /storage/emulated/0/Download or a custom app-specific folder on external storage suddenly behaves inconsistently after an unrelated update — usually one that bumps targetSdkVersion to satisfy a Play Store requirement. File paths that used to resolve correctly now point to files that don't exist, write operations that used to succeed now throw permission errors, or a feature like exporting a PDF or saving a downloaded image to the gallery silently stops producing a usable result. None of the app's file-handling code changed; only the target SDK version did.
Why It Happens
Targeting Android 11 (API 30) or higher opts an app into Scoped Storage, which fundamentally restricts direct file-path access to shared storage
Scoped Storage, enforced for any app with targetSdkVersion of 29 or higher on Android 10, and mandatory with no opt-out starting at targetSdkVersion 30 on Android 11, confines an app to its own private app-specific directory and a small set of mediated APIs for everything else — it can no longer freely read or write arbitrary paths under shared external storage the way it could before, regardless of which storage permissions were granted at install time.
Legacy broad storage permissions like WRITE_EXTERNAL_STORAGE stop granting the access they used to under the new model
An app that previously relied on <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"> for unrestricted file access finds that permission effectively neutered once Scoped Storage applies — it may still be requestable and even grantable, but it no longer unlocks direct filesystem access to other apps' files or arbitrary shared-storage paths the way it did on earlier Android versions.
Files the app created before targeting Scoped Storage may still be exactly where they were, but the app's own hardcoded absolute paths to them no longer resolve the same way
Code that constructs a path like RNFS.ExternalStorageDirectoryPath + "/MyApp/export.pdf" and expects to read it back later assumes a stable, directly-addressable filesystem layout that Scoped Storage no longer universally guarantees for anything outside the app's own sandboxed directory — the underlying file may still physically exist, but the app's old way of referring to it is no longer a reliable way to reach it.
The Fix
1. Move anything the app only needs for itself into app-specific storage, which Scoped Storage leaves fully unrestricted
import RNFS from "react-native-fs";
// Fully accessible without any storage permission, under Scoped Storage,
// on every Android version — this is the first place to look for a fix.
const appPrivatePath = `${RNFS.ExternalDirectoryPath}/export.pdf`;
await RNFS.writeFile(appPrivatePath, pdfData, "base64");
ExternalDirectoryPath (or DocumentDirectoryPath for fully private storage) stays completely unaffected by Scoped Storage rules, because it's the app's own sandboxed directory rather than shared storage — for any file the app creates and only the app itself needs to read back later, this alone removes the problem entirely with no permission or API changes required.
2. For files the user needs to see or share outside the app — downloads, exports, media — use the MediaStore API or a Storage Access Framework picker instead of a raw file path
import { launchImageLibrary } from "react-native-image-picker";
// Or, for saving INTO shared storage (e.g. Downloads, Pictures):
import RNFS from "react-native-fs";
await RNFS.copyFile(appPrivatePath, RNFS.DownloadDirectoryPath + "/export.pdf");
// On API 29+, writes to well-known public directories like Downloads/
// go through MediaStore-backed access even via some simplified libraries,
// but a raw path write into an arbitrary shared folder still requires
// MediaStore.createDocument or ACTION_OPEN_DOCUMENT_TREE for full correctness.
Content genuinely meant to be visible to the user in Downloads, Pictures, or another shared collection has to go through the mediated APIs Scoped Storage actually permits — MediaStore for media-type content, or a Storage Access Framework document picker for arbitrary shared locations — rather than attempting a direct filesystem write to a shared path the way older code did.
3. Request MANAGE_EXTERNAL_STORAGE only if the app has a genuine, Play Store–justifiable need for broad file management, since it carries real approval risk
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
This special permission restores something close to pre-Scoped-Storage broad file access, but Google Play requires an explicit declaration and justification for it during app review, reserved for apps like file managers or backup tools whose core purpose requires it — using it as a quick workaround for a feature that could instead use app-specific storage or MediaStore risks app rejection and is the wrong fix for most cases.
4. Audit every hardcoded absolute shared-storage path in the codebase rather than fixing failures one at a time as they surface
grep -rn "ExternalStorageDirectoryPath|/storage/emulated/0" src/ --include="*.js" --include="*.ts"
Because Scoped Storage affects every direct path into shared storage at once, rather than one specific feature, searching the codebase for every place a shared-storage path is constructed finds the full scope of what needs to migrate to app-specific storage or a mediated API in one pass, instead of discovering each broken feature separately as users report it.
Why This Works
None of these failures are a storage permission being denied in the traditional sense — they're the direct filesystem access that permission used to grant simply no longer being how Android lets an app reach shared storage once Scoped Storage applies. Moving app-only files to the always-accessible app-specific directory, routing anything genuinely shared through MediaStore or a document picker, and reserving the broad-access permission for apps that truly need it addresses the actual model change rather than fighting it with a permission that no longer does what it used to.
Conclusion
File access breaking after raising targetSdkVersion is Scoped Storage changing what a file path means on Android 10+, not a permissions bug introduced by the app's own update. Moving private files to app-specific storage, using MediaStore or a Storage Access Framework picker for anything meant to be shared, and auditing the codebase for every hardcoded shared-storage path turns a scattered set of silent failures into a one-time, complete migration.
