NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Flutter router. Screens are types; the app is a graph. Deep links, web, and a visual route graph.
Last release 8 days ago
30 Sep 2026
Release timing varies
gaps range from 8 days to 3 months
Some releases are documented
notes for 15 of 43 stable releases
Nothing withdrawn
no release was ever pulled
10 months old
44 releases · first in 2025
One column per month.
chore: release ZenRouter 3.0.0 and prepare DartNative 0.1.0
chore: release ZenRouter 3.0.0 and prepare DartNative 0.1.0
Stable release of ZenRouter 3. The changes below are cumulative since 2.3.0, including the 3.0 beta APIs and the fixes added before stable.
Coordinator capability mixins live in zenrouter_core 3.0.0. Flutter
Coordinator still composes the full set (LayoutCore + Navigatable +
Mutatable + Recoverable + Flutter CoordinatorLayout). Apps extending Coordinator retain the complete capability set; custom
CoordinatorCore subclasses must select their own mixins.
Coordinator.pushOrMoveToTop returns Future<void> (was incorrectly
typed as void while being async). Call sites that ignored the return
value keep working; prefer await when sequencing.
PageCallback receives LocalKey instead of ValueKey<RouteTarget>.
Custom page builders normally require no change unless they explicitly typed
the callback parameter.
pop() completes onResult and runs onDidPop immediately. Do not call
completeOnResult again after pop / tryPop; the completer is already
done. onDidPop is idempotent if Flutter's page callback also fires.
Equatable.internalProps is removed. Equality and hashing use
runtimeType + props only. Mutable path/result state no longer contributes
to route hashes; RouteTarget.deepEquals compares lifecycle entry identity.
Delete leftover internalProps overrides.
CoordinatorModular.defineModules returns Iterable<RouteModule<T>>.
Its iteration order is snapshotted during initialization; duplicate module
runtime types are rejected.
Generated reverse-routing helpers move to location. Regenerate
file-based coordinators and replace {route}Location() calls with
coordinator.location.{route}.
defineLayout / defineConverter on Coordinator / RouteModule.
Bind layouts with bindLayout on the path
(NavigationPath.createWith(...)..bindLayout(ShopLayout.new)).
Register restorable converters in init() via defineRestorableConverter.pushReplacement on indexed/branched parents now activates the
destination instead of no-oping after a possible pop of the current path.BranchedStackPath: fixed branch layout
roots retain an independent child stack while branch selection and
restoration reuse indexed-path semantics.RouteManifest seam; generated coordinators bind matched IDs to concrete
RouteTarget instances without putting Flutter types in the manifest.CoordinatorModular now exposes one root
graph assembled from local and nested RouteModule manifests. Cross-module
layout relationships and URI conflicts are validated after composition.RouteModuleBinding connects a
validated RouteBindingRegistry to routeManifest, URI parsing, Flutter
Router resolution, and typed not-found handling. A coordinator implements
RouteModule, so the same mixin covers standalone coordinators and child
modules.coordinator.location.home: generated and handwritten
coordinators expose a location namespace (location.home,
location.profile(...)) instead of reversed {route}Location() helpers.zenrouter_core): mix only the
capabilities you need. CoordinatorView.initialUri asserts in debug when
the host lacks CoordinatorNavigatable.defineDeeplinkHandler: pluggable deep-link strategy handlers on
CoordinatorRecoverable.recoverUri: parses a URI and recovers it on CoordinatorRecoverable
(parseRouteFromUri → recover).none when the app URI already matches the engine, and
neglect when a guard (or other failed apply) must restore the current
entry. Unconditional none would push a new history entry on Flutter
stable/master.NavigationCommit revision.NavigationStack diffs
pages by identity; DeclarativeNavigationStack still diffs by ==.NavigationPath.reset discards route-owned resources.
Inside a navigation transaction it notifies synchronously so multi-path
replace publishes one commit; outside a transaction it still publishes in
a microtask (safe during Flutter builds and in headless use).CoordinatorRouterDelegate.popRoute() asks the
deepest active Navigator to maybePop() before falling back to the
coordinator stack. Nested navigators and PopScope can handle Back before
the application route is removed.PageCallback, pop() results, and equality.zenrouter_core: ^3.0.0.zenrouter_file_annotation and
zenrouter_file_generator 3.0.0; the generator requires Dart 3.11 or newer.Prerelease for early testers. APIs may still change before 3.0.0.
Prerelease for early testers. APIs may still change before 3.0.0.
Coordinator capability mixins live in zenrouter_core 3.0.0-beta.1. Flutter
Coordinator still composes the full set (LayoutCore + Navigatable +
Mutatable + Recoverable + Flutter CoordinatorLayout). Apps that only
extend Coordinator are unaffected.
Coordinator.pushOrMoveToTop returns Future<void> (was incorrectly
typed as void while being async). Call sites that ignored the return
value keep working; prefer await when sequencing.
PageCallback receives LocalKey instead of ValueKey<RouteTarget>.
Custom page builders normally require no change unless they explicitly typed
the callback parameter.
pop() completes onResult and runs onDidPop immediately. Do not call
completeOnResult again after pop / tryPop; the completer is already
done. onDidPop is idempotent if Flutter's page callback also fires.
Equatable.internalProps is removed. Equality and hashing use
runtimeType + props only. Delete leftover overrides.
defineLayout / defineConverter on Coordinator / RouteModule.
Bind layouts with bindLayout on the path
(NavigationPath.createWith(...)..bindLayout(ShopLayout.new)).
Register restorable converters in init() via defineRestorableConverter.pushReplacement on indexed/branched parents now activates the
destination instead of no-oping after a possible pop of the current path.BranchedStackPath: fixed branch layout
roots retain an independent child stack while branch selection and
restoration reuse indexed-path semantics.RouteManifest seam; generated coordinators bind matched IDs to concrete
RouteTarget instances without putting Flutter types in the manifest.CoordinatorModular now exposes one root
graph assembled from local and nested RouteModule manifests. Cross-module
layout relationships and URI conflicts are validated after composition.RouteModuleBinding connects a
validated RouteBindingRegistry to routeManifest, URI parsing, Flutter
Router resolution, and typed not-found handling. A coordinator implements
RouteModule, so the same mixin covers standalone coordinators and child
modules.coordinator.location.home: generated and handwritten
coordinators expose a location namespace (location.home,
location.profile(...)) instead of reversed {route}Location() helpers.zenrouter_core): mix only the
capabilities you need. CoordinatorView.initialUri asserts in debug when
the host lacks CoordinatorNavigatable.defineDeeplinkHandler: pluggable deep-link strategy handlers on
CoordinatorRecoverable.recoverUri: parses a URI and recovers it on CoordinatorRecoverable
(parseRouteFromUri → recover).none when the app URI already matches the engine, and
neglect when a guard (or other failed apply) must restore the current
entry. Unconditional none would push a new history entry on Flutter
stable/master.NavigationCommit revision.NavigationStack diffs
pages by identity; DeclarativeNavigationStack still diffs by ==.NavigationPath.reset discards route-owned resources.
Inside a navigation transaction it notifies synchronously so multi-path
replace publishes one commit; outside a transaction it still publishes in
a microtask (safe during Flutter builds and in headless use).PageCallback, pop() results, and equality.`GuardRule` contract renamed (via zenrouter_core 2.3.0). The 2.2.0 methods are removed:
GuardRule contract renamed (via zenrouter_core 2.3.0). The 2.2.0 methods are removed:
| Removed | Replacement |
|---|---|
canPop(route) |
canPopRule(route) / canPopRuleWith(coordinator, route) |
canPopListenable(route) |
canPopListenableRule(route) / canPopListenableRuleWith(coordinator, route) |
guard(coordinator, route) |
guardRule(route) / guardRuleWith(coordinator, route) |
Use route-only methods when no coordinator is needed; override *With for dialogs / app state. Each *With defaults to its non-With counterpart.
// Before
class UnsavedChangesRule extends GuardRule<AppRoute> {
@override
bool canPop(AppRoute route) => !route.hasUnsavedChanges;
@override
FutureOr<bool?> guard(Coordinator c, AppRoute route) async =>
showDiscardDialog(c.navigator.context);
}
// After
class UnsavedChangesRule extends GuardRule<AppRoute> {
@override
bool canPopRule(AppRoute route) => !route.hasUnsavedChanges;
@override
FutureOr<bool?> guardRuleWith(Coordinator c, AppRoute route) async =>
showDiscardDialog(c.navigator.context);
}
RouteGuard.canPopWith / canPopListenableWith: Coordinator-aware PopScope hints; NavigationStack prefers these when a coordinator is present.RouteGuardRule.popGuard: Runs the guardRule chain without a coordinator.guardRule / guardRuleWith API.New GuardRule / RouteGuardRule API (via zenrouter_core 2.1.0) for reusable leave-confirmation chains — first non-null bool wins (null = continue).
RouteGuardRule — composable pop guardsGuardRule / RouteGuardRule API (via zenrouter_core 2.1.0) for reusable leave-confirmation chains — first non-null bool wins (null = continue).RouteGuard.canPop / canPopListenable drive Flutter PopScope; programmatic pop still always consults popGuard / popGuardWith.toListenableMixin() / toFlutterListenable(); NavigationStack rebuilds PopScope via ListenableBuilder when a listenable is present.CoordinatorCore.pop: Pops only the nearest eligible path (no longer multi-path in one call). Bumped zenrouter_core to 2.1.0.RouteRedirect.resolve: Throws StateError on redirect type mismatch.example/lib/main_guard_rules.dartBump zenrouter_core version to 2.0.3
zenrouter_core version to 2.0.3New `CoordinatorView` widget renders a coordinator via layoutBuilder without Flutter's Router—for super apps, parallel panels, plugin surfaces, and ot
CoordinatorView — headless coordinator embedCoordinatorView widget renders a coordinator via layoutBuilder without Flutter's Router—for super apps, parallel panels, plugin surfaces, and other host-owned shells.initialUri seeds navigation once when coordinator.root.stack is empty (ignored after the embed has stack state or on remount with the same coordinator).parseRouteFromUri for the initial bootstrap.CoordinatorLayoutBuilder mixinlayoutBuilder(BuildContext) into CoordinatorLayoutBuilder; CoordinatorLayout implements it so embed hosts can depend on layout rendering without RouterConfig.layoutBuilder moved to CoordinatorLayout: Override layoutBuilder on your coordinator's CoordinatorLayout mixin (unchanged for typical extends Coordinator subclasses). It is no longer declared on the Coordinator class body.RouteLayoutBuilder signature: The first parameter is now CoordinatorCore instead of Coordinator. Update custom defineLayoutBuilder / kDefaultLayoutBuilderTable callbacks accordingly (cast to Coordinator when you need Flutter-specific APIs).RouteLayout.buildRoot: Now accepts CoordinatorLayout instead of Coordinator. Call sites that passed a bare CoordinatorCore must use a type that provides getLayoutBuilder / root.initialUri semantics, pitfalls vs MaterialApp.routerCoordinatorView section and dual quick-startFix: CoordinatorModular.getModule now correctly resolves the coordinator itself — runtimeType: this is registered in _allModules, enabling getModule (
CoordinatorModular.getModule now correctly resolves the coordinator itself — runtimeType: this is registered in _allModules, enabling getModule<MyCoordinator>() to work at any level of the hierarchy. (Bumped zenrouter_core to 2.0.2)Fix: Fix CoordinatorModular edgecase cascading dispose and prevent duplicate definitions. (Bumped zenrouter_core to 2.0.1)
CoordinatorModular edgecase cascading dispose and prevent duplicate definitions. (Bumped zenrouter_core to 2.0.1)Fix: Revert hasEmptyPath back to pathSegments.isEmpty in resolveInitialUri for correct path empty checks.
hasEmptyPath back to pathSegments.isEmpty in resolveInitialUri for correct path empty checks.initialRouteInformation parameter from CoordinatorRouteInformationProvider since fallback defaults and resolution logic is robust now.RouteLayout.definePath Deprecated: Deprecated RouteLayout.definePath in favor of coordinator.defineLayoutBuilder.
🎉 Major Release - Core Architecture & Layouts
zenrouter_core Package: Extracted all platform-independent core routing types (RouteTarget, CoordinatorCore, StackPath, and route mixins) into a new dedicated package.RouteRedirectRule as RouteRedirect to allow composable, testable redirect logic via multiple rules (e.g., StopRedirect, ContinueRedirect, RedirectTo).RouteUri abstract class (and RouteUnique) to centralize URI-based identity for coordinator-managed routes.RouteLayout.defineLayout has been removed. You must now bind layouts to paths using the StackPath.bindLayout() cascade syntax (e.g., NavigationPath.createWith(...)..bindLayout(HomeLayout.new)), or use defineLayoutParent() / defineLayoutBuilder() inside the coordinator.zenrouter_core. The main package still exports them, but any explicit deep imports to old paths must be updated.RouteLayout.definePath in favor of coordinator.defineLayoutBuilder.Fix: Regression error when using RouteRedirectRule inside IndexedStackPath (Thanks to @obenkucuk)
RouteRedirectRule inside IndexedStackPath (Thanks to @obenkucuk)Coordinator now implements RouteModule<T>, enabling any coordinator to be nested inside a CoordinatorModular by overriding the coordinator getter.isRouteModule flag controls root path creation vs parent inheritance.example/lib/main_coordinator_module.dartCoordinator.parseRouteFromUri signature changed from FutureOr<T> to FutureOr<T?>. Child coordinators return null for unrecognized URIs; standalone coordinators are guarded by assertions.CoordinatorModular.parseRouteFromUri returns null instead of notFoundRoute when the coordinator is itself a nested module.BREAKING CHANGE: Remove coordinator from defineModules, use this getter instead.
coordinator from defineModules, use this getter instead.getModule method return exact type.🎉 Major Release - Production Ready
🎉 Major Release - Production Ready
CoordinatorModular mixin + RouteModule base classexample/lib/main_modular.dartclass AppCoordinator extends Coordinator<AppRoute>
with CoordinatorModular<AppRoute> {
@override
Set<RouteModule<AppRoute>> defineModules() => {
AuthModule(this),
ShopModule(this),
};
}
RedirectResult sealed class with Stop/Continue/RedirectTo variantsclass ProtectedRoute extends AppRoute
with RouteRedirect, RouteRedirectRule {
@override
List<RedirectRule> get redirectRules => [
AuthenticationRule(),
PermissionRule(permission: 'admin'),
];
}
Removed deprecated APIs:
RouteLayout.buildPrimitivePath → Use RouteLayout.buildPathRouteLayout.layoutBuilderTable → Use RouteLayout.buildPathRouteLayout.navigationPath/indexedStackPath → Use NavigationPath.key/IndexedStackPath.keyrouterDelegateWithInitialRoute → Use RouteRedirect in IndexRouteSee Migration Guide for details.
Fix: back gesture failed in android
Fix: Blank screen when using Coordinator as routerConfig (due to unset routerInformationProvider).
Coordinator as routerConfig (due to unset routerInformationProvider).initialRoutePath property to Coordinator.NavigatorObserverListGetter typedef for passing external observers. (View Guide)Breaking Change: Don't allow redirect to return null anymore since it doesn't do anything.
pushReplacement method in StackMutatable.Coordinator now implements RouterConfig so you can use it with MaterialApp.router more easily.
MaterialApp.router(
// New way
routerConfig: coordinator,
// Old way
routerDelegate: coordinator.routerDelegate,
routeInformationParser: coordinator.routeInformationParser,
);
routerDelegateWithInitialRoute is deprecated, you can simulate the same behavior by using RouteRedirect in IndexRoute.pushOrMoveToTop.onUpdate method to RouteTarget for handling in-place route updates when navigating to the same route with different state.bindLayout method to StackPath as a convenient alternative for layout registration. (See RouteLayout Guide)redirect to return null anymore since it doesn't do anything.mustCallSuper to paths getter (Thanks @mrgnhnt96)discard parameter to remove method for controlling discarding behavior.RouteQueryParameters in IndexedStackPath.RouteRedirect.navigate method is compatible with RouteRedirect.StackNavigatable mixin for StackPath to handle custom logic when receiving a navigate command. (Back/Forward button on the browser)navigate clear all history that occurred when pushing a custom layout.stackPath in RouteTarget and expose protected method for developer create custom stackPath.onDiscard to handle discarding phase in RouteTarget.serialize and deserialize for supported RouteTarget typeRouteRestoration mixin. Support state restoration by default if restorationScopeId is provided in MaterialApp.router and using Coordinator pattern.recover method where RouteRedirect was ignored.RouteQueryParameters mixin for targeted query parameter updates using ValueNotifier.path is set for RouteTarget when initial IndexedStackPath.layout is resolve correct if they under deeper stack.CoordinatorNavigatorObserver mixin to provide a list of observers for the coordinator's navigator.RouteLayout.buildPrimitivePath and use buildPath function instead.RouteLayout.definePath. You can create custom behavior path builder. (Eg: RecoverableHistoryStack like unrouter)transitionStrategy to Coordinator for default stack transition setupNavigationPath and IndexedStackPath. Use NavigationPath.create/createWith and IndexedStackPath.create/createWith instead.internalProps to RouteTarget for better deep equality and hash code generation.popGuardWith to RouteGuard and redirectWith to RouteRedirect for coordinator-aware mixin logic.createWith factories.routerDelegateWithInitalRoute to Coordinator.setInitialRoutePath to correctly handle initial routes vs deep links.navigate function: A smarter alternative to push that handles browser history restoration by popping to existing routes instead of duplicating them.parseRouteFromUri to return Future for implementing deferred import/async route parsingCoordinator.push() from Future<dynamic> to Future<T?>NavigationStack rerender page everytime path updated. Resolve #10.recover functionactivePathIndex to activeIndex in IndexedStackPathpushOrMoveToTopRouteRedirect.resolve chaindefinePrimitivePathbuildPrimitivePathactiveHostPaths to activeLayoutPaths to reflect correct concept.RouteGuard, RouteRedirect, RouteDeepLink.RouteUnique.Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →