
Flutter 3.50 changes how your Android app treats the Intent that opens it. In the 3.50 beta, FlutterActivity and FlutterFragmentActivity stop trusting the route, Dart entrypoint and cached engine extras on an Intent unless your own app sent it. If your app opens Flutter screens from notifications, shortcuts, home screen widgets, plugins or adb scripts, some of those launches will quietly land on / in release builds. This post covers what changed, why, how to check whether you are affected, and the manifest and Kotlin changes that keep each launch path working.
What changed in Flutter 3.50
The breaking-change page says the Android embedder now verifies the sender before it reads five Intent extras: route (EXTRA_INITIAL_ROUTE), dart_entrypoint, dart_entrypoint_args, cached_engine_id and cached_engine_group_id. In release mode, an Intent that can't be verified as coming from your app has those extras ignored.
The change landed in 3.50.0-0.1.pre on 14 October 2026. The current beta is 3.50.0-0.2.pre (24 October, Colombo time), which also ships Dart 3.14.0-315.3.beta. Stable is still 3.47.8. The release schedule targets Flutter 3.50 stable for November 2026 but gives no exact day. The code was merged on 26 September, before the 6 October branch cutoff, so I expect it to ship in 3.50 stable unchanged. Both breaking-change pages still say "In stable release: Not yet".
Why the Flutter team did this
Two Android security bugs explain it. In issue #190450, the default MainActivity is exported (it has to be, for the launcher), and the embedder parsed the data URI of any explicit Intent without checking the action, the caller or your intent filters. A co-resident app with zero permissions could send an explicit Intent straight to MainActivity, skip Android App Links domain verification, and push your app to a sensitive route.
Issue #190452 is worse. Any app could pass dart_entrypoint and dart_entrypoint_args to make your app boot a different @pragma('vm:entry-point') function, with any arguments, inside your app's own sandbox. The docs add a third risk: reusing a cached, already signed-in FlutterEngine through the cached engine extras.
Both issues were fixed by PR #190249. I couldn't find a CVE or GitHub security advisory for either one. They are tracked only as internal Google bugs.
The Flutter 3.50 Android Intent verification rules

The logic lives in a new class, IntentUtils.java. I read it on main to confirm the order of the checks. An Intent counts as self-sent if any of these is true:
| Situation | Rule in release builds | What you need to do |
|---|---|---|
Activity has android:exported="false" | Always trusted | Nothing |
| Android 14 and later (API 34+) | Trusted if getLaunchedFromUid() equals your app's UID | Launch from your own code |
| Android 13 and earlier (API 33 and lower) | Trusted if getCallingPackage() equals your package | Launch with startActivityForResult() or startActivityIfNeeded() |
| Debug or profile build | Check bypassed, warning logged | Read the warnings before you ship |
Deep links follow a separate path. If the Intent isn't self-sent, the embedder still uses its data URI as the route, but only when the action is ACTION_VIEW and the URI matches an <intent-filter> that resolves to that activity. An explicit Intent with a URI but no ACTION_VIEW gets ignored, which closes the App Links bypass.
What falls back to what
When verification fails in release, the docs list these fallbacks:
- Route: the command-line route or
io.flutter.InitialRoutemeta-data, otherwise"/". - Dart entrypoint:
io.flutter.Entrypointmeta-data, otherwise"main". - Entrypoint arguments:
null. - Cached engine ID and group ID:
null, so a fresh engine starts instead of reusing a cached one.
Who is affected
You don't need to change anything if you never pass those five extras, or if the Flutter activity that receives them is not exported. Go through this list if you're not sure:
- Notifications or shortcuts built in native code that put
routeinto aPendingIntentaimed at an exported activity. - Home screen widgets or add-to-app hosts that call
FlutterActivity.withNewEngine().initialRoute(...)orwithCachedEngine(...)and then use plainstartActivity(). On Android 13 and earlier, those launches now fail the check. - A second exported Flutter activity that uses
dart_entrypointto run a differentmain. - QA or CI scripts that run
adb shell am start ... --es route /somethingagainst a release APK. - Explicit Intents that carry a deep link URI without
ACTION_VIEW, or URIs your manifest doesn't declare. - Release-build CI that passes engine flags (more on that below).
The PR author checked several popular plugins and reported they won't break: all first-party plugins, firebase_messaging 16.5.0, flutter_local_notifications 22.3.0, awesome_notifications 0.12.1, android_alarm_manager_plus 5.1.1, receive_sharing_intent 1.9.0, onesignal_flutter, workmanager 0.10.7, flutter_background_service 5.1.0, app_links 7.2.1 and uni_links 0.5.1. Older versions of these weren't part of that check. The same PR notes that on Android 13 and earlier the sender can't always be verified, so unmigrated plugins can still break on those devices.
go_router has published nothing specific about 3.50. It gets deep links from the engine, so it follows the deep link rule above: a correct ACTION_VIEW link that matches your filter still routes.
Migrating your app
These follow the migration options in the official docs. I've written them in Kotlin, since new Flutter projects use Kotlin for the Android side.
1. Make internal Flutter activities non-exported
If only your own app opens an activity, take it off the exported list. Your launcher MainActivity has to stay exported.
<activity
android:name=".SettingsFlutterActivity"
android:exported="false"
android:theme="@style/LaunchTheme" />2. Use real deep links (ACTION_VIEW and an intent filter) for notifications and shortcuts
Replace route extras with an ACTION_VIEW deep link and a matching filter. Flutter's built-in deep link handling has to be on. It defaults to on, but check that you haven't set flutter_deeplinking_enabled to false.
<activity android:name=".MainActivity" android:exported="true">
<!-- launcher intent-filter stays as it is -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="orders" />
</intent-filter>
</activity>val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse("myapp://orders/42"),
context,
MainActivity::class.java,
)
val pendingIntent = PendingIntent.getActivity(
context,
42,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)3. Declare static entrypoints and routes in the manifest
If an activity always opens the same entrypoint or route, put that in meta-data, so it no longer depends on the Intent.
<activity android:name=".WidgetFlutterActivity" android:exported="true">
<meta-data
android:name="io.flutter.Entrypoint"
android:value="widgetMain" />
<meta-data
android:name="io.flutter.InitialRoute"
android:value="/widget" />
</activity>4. Use startActivityForResult for internal launches on Android 13 and earlier
On API 33 and lower, getCallingPackage() is only set when the caller expects a result. You don't need to handle the result.
private const val FLUTTER_LAUNCH = 1001
fun openFlutterSettings(activity: Activity) {
val intent = FlutterActivity
.withNewEngine()
.initialRoute("/settings")
.build(activity)
// Populates getCallingPackage() on API 33 and lower.
activity.startActivityForResult(intent, FLUTTER_LAUNCH)
}startActivityForResult() is deprecated on ComponentActivity in favour of the Activity Result APIs, but it's what the Flutter docs recommend for this case, and it still works.
5. Subclass FlutterActivity for dynamic entrypoints
If the entrypoint or arguments depend on runtime state, override the configuration methods instead of using extras:
class WidgetFlutterActivity : FlutterActivity() {
override fun getDartEntrypointFunctionName(): String =
"widgetMain"
override fun getDartEntrypointArgs(): List<String>? =
listOf(WidgetConfig.currentWidgetId(this))
}Engine flags are now ignored in release builds
A related change, Restrict Android engine flags in release mode, also landed in 3.50.0-0.1.pre. Release builds now ignore engine flags such as --dart-flags, --route and tracing flags when they come in through Intent extras, and log a warning. Release builds read configuration only from the compiled manifest or from code that runs before the engine starts. Debug and profile builds still accept these flags. The plan is in issue #190461.
The CLI change matters most for CI. flutter run --release --use-application-binary with engine flags is now a fatal error, because a prebuilt APK's manifest can't be changed. Normal flutter build apk --release and flutter build appbundle work as before, since the CLI writes the flags into the manifest at build time.
If you need flags in a release binary, declare them under <application>:
<application android:label="my_app" android:name="${applicationName}">
<meta-data
android:name="io.flutter.embedding.android.DartFlags"
android:value="--some-dart-flag" />
</application>For benchmarks and integration tests that change flags at launch, switch to --profile. In native code, override getFlutterEngineFlags() in your FlutterActivity subclass (getFlutterShellArgs is deprecated), use FlutterFragment.withNewEngine().flutterEngineFlags(...), or pass arguments to the FlutterEngine(context, args) constructor.
How to test on the 3.50 beta
I haven't run a device test for this post. These are the commands I'd use, based on the docs and the source.
# Option A: switch channel
flutter channel beta && flutter upgrade
# Option B: FVM, pinned to the current beta
fvm install 3.50.0-0.2.pre
fvm use 3.50.0-0.2.pre
flutter build apk --release
adb install -r build/app/outputs/flutter-apk/app-release.apkadb shell runs as the shell user, not as your app, so these launches count as external:
# Route extra on an explicit Intent: expected to open "/" in release
adb shell am start -n com.example.app/.MainActivity --es route /admin
# URI without ACTION_VIEW: expected to be ignored in release
adb shell am start -n com.example.app/.MainActivity -d "myapp://orders/42"
# Proper deep link that matches your filter: should still open the order
adb shell am start -a android.intent.action.VIEW -d "myapp://orders/42" com.example.appThen install a debug build and run the same commands while watching adb logcat | grep IntentUtils. Each launch your app should be making internally shows up as a warning to fix. Test on one Android 13 device and one Android 14 or later device, because the rule differs between them.
The fix doesn't cover everything yet. A few extras, such as background mode and state restoration, are still read directly, and issue #192076 tracks moving them to the safe accessors.
Dart 3.14 in the same release
Flutter 3.50 beta bundles Dart 3.14. The Dart SDK changelog still marks 3.14.0 as "Unreleased", so the details could change. The parts that matter most for app teams:
- A new
dart migratecommand raises a package's SDK lower bound one minor version at a time and applies the matching fixes. Use--dry-runto preview and--applyto write.--target-sdkand--step(prepare,bump,cleanup,all) control how far it goes. It needs packages already on Dart 3.0 or later, and it isn't the old null-safety tool. dart:mirrorsis now deprecated.dart pub upgradeacceptspackage@latest,package@constraintandpackage@resolvable.- There's a new
future_sync_valuelint, anddart run -eevaluates inline code. - Breaking: the sync isolate APIs accidentally exposed in 3.13 (
Isolate.runSyncand related) are removed, and SIMDtoString()now returns'V128'.
If you maintain an older app and plan to jump several versions at once, the checklist in taking over a live Flutter app is a good place to start. Test the beta first and leave the move to stable until 3.50 is actually released.
Frequently asked questions
What changed with Android Intents in Flutter 3.50?
FlutterActivity and FlutterFragmentActivity now check who sent an Intent before using the route, dart_entrypoint, dart_entrypoint_args, cached_engine_id and cached_engine_group_id extras. In release builds those extras are ignored unless the Intent came from your own app or the activity is not exported.
Does Flutter 3.50 block explicit Intents from other Android apps?
No, other apps can still start your exported Flutter activity with an explicit Intent. In release builds Flutter 3.50 just ignores the route, entrypoint and cached engine extras on it unless the sender is your own app, and a URI is only routed when it arrives as ACTION_VIEW and matches an intent filter in your manifest.
Why does my Flutter notification open the home screen instead of the right page on 3.50?
The notification probably passes a route extra to an exported activity, and in release builds Flutter 3.50 ignores that extra when it cannot verify the sender. Switch the notification to an ACTION_VIEW deep link that matches an intent filter in your manifest.
Do Flutter deep links still work in 3.50?
Yes. A deep link still opens its route if the Intent uses ACTION_VIEW and the URI matches an intent filter declared for that activity. Explicit Intents that carry a URI without ACTION_VIEW, or with a URI the manifest does not declare, are ignored in release builds.
Why does adb shell am start with a route extra no longer work on my release build?
adb runs as the shell user, so Flutter 3.50 treats the launch as external and ignores the route extra in release mode. Debug and profile builds still accept it and log a warning, so use a profile build for scripted tests.
Is there a CVE for the Flutter Android Intent vulnerability?
No CVE or GitHub security advisory had been published as of 27 October 2026. The issues are tracked as flutter/flutter issues 190450 and 190452 and internal Google bugs, and were fixed by pull request 190249.
When is Flutter 3.50 stable released?
The Flutter release schedule targets November 2026 for 3.50 stable without an exact day. As of 27 October 2026 the latest beta is 3.50.0-0.2.pre and the latest stable is 3.47.8.
Sources
- Stricter Android Intent verification for app entrypoints (Flutter docs, 2026-10-14)
- Restrict Android engine flags in release mode (Flutter docs, 2026-10-14)
- Breaking changes and migration guides (Flutter docs, 2026-10-26)
- Flutter SDK releases feed (Linux) (Flutter)
- Flutter SDK archive and 2026 release schedule (Flutter docs)
- [Android] Refuse external setters of engine entrypoint and cached engine arguments via Intents (PR #190249) (flutter/flutter on GitHub, 2026-09-26)
- [Android] Arbitrary route injection via unvalidated explicit Intents (#190450) (flutter/flutter on GitHub, 2026-08-03)
- [Android] Unauthorized executing of arbitrary Dart entrypoints via Intent extras (#190452) (flutter/flutter on GitHub, 2026-08-03)
- [Android] Restrict engine flags via Intent extras to non-release modes (#190461) (flutter/flutter on GitHub, 2026-08-03)
- [Android] Migrate remaining internal Intent.get*Extra calls to IntentUtils (#192076) (flutter/flutter on GitHub, 2026-08-31)
- IntentUtils.java (Flutter Android embedder source) (flutter/flutter on GitHub)
- Dart SDK CHANGELOG (3.14.0, unreleased) (Dart)
- Deep linking (Flutter docs)
Keep reading
- Flutter 3.47.8: Fixing the eMudhra TLS VulnerabilityFlutter 3.47.8 fixes a TLS man-in-the-middle risk from the eMudhra CA compromise. Here is who is affected and how to upgrade, rebuild and ship.
- Flutter UIScene Migration: Fix iOS 27 Launch CrashesFlutter apps built with Xcode 27 quit at launch without the UIScene lifecycle. Here's how to migrate a custom AppDelegate, Info.plist and plugins.
- Taking Over an Existing Flutter App and PHP Backend: My PlaybookHow I take over a live Flutter app with a legacy PHP backend: a first-week checklist, reproducing bugs, PHP security fixes and a safe move to token auth.
Comments
Share a question or note about this article.
Loading comments…