
Flutter's Hidden Android Manifest: The Permissions and Components You Never Added
Your Flutter app's final `AndroidManifest.xml` contains dozens of declarations you never wrote. Every plugin dependency, from camera access to push notifications, contributes permissions, activities, and services through Android's manifest merging process. Most Flutter developers discover this the hard way—through unexpected permission requests, build conflicts, or app store rejections.
Here's what's actually happening behind the scenes and why you should care.
The Merge You Never See
Android's build system automatically combines multiple manifest files into a single declaration that ships with your app. Your innocent-looking pubspec.yaml with five Flutter plugins might generate a final manifest with 20+ permissions and a dozen background services.
<> The manifest merger processes files sequentially based on priority, with your app's main manifest taking precedence over library contributions, but libraries can still force their requirements into your final build./>
This happens in a specific order:
- Your app's
android/app/src/main/AndroidManifest.xml(highest priority) - Build variant and flavor manifests
- Flutter plugin manifests (embedded in their Android Archive files)
- Transitive dependencies from those plugins
When conflicts arise, the merger follows predefined rules—sometimes merging values, sometimes overriding them, and occasionally failing your build entirely with cryptic error messages.
What's Actually in Your Manifest
Let's look at a real example. A typical Flutter app with common plugins might start with a minimal manifest:
1<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2 <application
3 android:label="my_app"
4 android:name="${applicationName}"
5 android:icon="@mipmap/ic_launcher">
6 <activity android:name=".MainActivity"
7 android:exported="true">
8 <intent-filter>
9 <action android:name="android.intent.action.MAIN"/>
10 <category android:name="android.intent.category.LAUNCHER"/>
11 </intent-filter>
12 </activity>
13 </application>
14</manifest>But after adding plugins for camera, location, and push notifications, the merged result includes:
1<!-- Auto-generated permissions you never declared -->
2<uses-permission android:name="android.permission.CAMERA" />
3<uses-permission android:name="android.permission.RECORD_AUDIO" />
4<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
5<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
6<uses-permission android:name="android.permission.WAKE_LOCK" />
7<uses-permission android:name="android.permission.INTERNET" />
8<uses-permission android:name="android.permission.VIBRATE" />None of those declarations exist in your source code, yet they're all active in your production app.
The Permission Inheritance Problem
One particularly sneaky issue occurs when plugins target older Android SDK versions than your app. The manifest merger automatically grants permissions to ensure backward compatibility, even if you don't need them.
For example, if your app targets SDK 31 but includes a plugin targeting SDK 28, you might inherit deprecated permissions like WRITE_EXTERNAL_STORAGE—even if your app never writes to external storage.
Debugging Manifest Conflicts
When builds fail with manifest merger errors, the messages are notoriously unhelpful:
1Android resource linking failed
2ERROR: /path/to/AndroidManifest.xml:XX:X-XX:XX AAPT: error: attribute android:exported not specified...The real solution requires understanding which plugin introduced the conflicting element. You can resolve these explicitly using merger rule attributes:
1<activity
2 android:name=".MainActivity"
3 android:exported="true"
4 tools:replace="android:exported">
5 <!-- Your activity declaration -->
6</activity>This tells the merger to replace any lower-priority android:exported values with your explicit declaration.
Auditing Your Real Manifest
Most Flutter developers never look at their merged manifest. Here's how to inspect what you're actually shipping:
Option 1: Android Studio
1. Open your Flutter project in Android Studio
2. Navigate to android/app/src/main/AndroidManifest.xml
3. Click the "Merged Manifest" tab at the bottom
4. Expand the tree view to see exactly which plugins contributed each element
Option 2: Command Line
1cd android
2./gradlew app:processDebugManifest
3cat app/build/intermediates/merged_manifests/debug/AndroidManifest.xmlThis generates and displays your complete merged manifest, showing every permission, service, and component that will ship with your app.
Taking Control
Once you understand what's in your manifest, you can make informed decisions:
Remove unnecessary permissions:
1<uses-permission android:name="android.permission.CAMERA"
2 tools:node="remove" />Override plugin defaults:
1<service android:name="com.plugin.BackgroundService"
2 android:enabled="false"
3 tools:replace="android:enabled" />Document your dependencies:
Maintain a record of which plugins require which permissions, so you can explain permission requests to users and app store reviewers.
Why This Matters
User trust: Unexpected permissions damage user confidence. When your simple utility app requests camera and location access because of unused plugin features, users notice.
App store compliance: Both Google Play and Apple's App Store are increasingly strict about permission justification. You need to explain every permission in your manifest, even inherited ones.
Security surface area: Every permission and background service increases your app's attack surface. Unused capabilities from plugins create unnecessary risk.
Performance impact: Background services and receivers consume device resources even when dormant. Understanding what's running helps optimize your app's footprint.
The next time you add a Flutter plugin, don't just check its Dart API—inspect what it's adding to your Android manifest. Your users, app store reviewers, and security auditors will thank you.

