Skip to main content
Route guards allow you to control access to pages based on conditions like authentication status, user permissions, or custom logic. NavigationUtils provides multiple ways to implement route guards.

Authentication guard

The most common route guard is authentication. NavigationUtils provides built-in support through the authenticationRequired property.

Setup

1

Annotate DeeplinkDestination

Mark deeplinks that require authentication:
DeeplinkDestination(
  deeplinkUrl: '/deeplink/profile',
  destinationUrl: '/profile',
  authenticationRequired: true,
)
2

Pass authentication state

Provide the current authentication status when opening deeplinks:
NavigationUtils.openDeeplinkDestination(
  uri: uri,
  deeplinkDestinations: deeplinkDestinations,
  routerDelegate: NavigationManager.instance.routerDelegate,
  authenticated: AuthService.instance.isAuthenticated,
);
When authenticationRequired is true and authenticated is false, navigation is automatically blocked.

Route-level authentication guard

Implement authentication guards for all routes using the setMainRoutes callback: From example_auth/lib/main.dart:126-151:
List<DefaultRoute> setMainRoutes(List<DefaultRoute> routes) {
  List<DefaultRoute> routesHolder = routes;
  // Authenticated route guard.
  if (AuthService.instance.isAuthenticated.value == false) {
    routesHolder.removeWhere((element) => element.metadata?['auth'] == true);
    if (routesHolder.isEmpty) {
      routesHolder.add(DefaultRoute(label: SignUpForm.name, path: '/signup'));
      NavigationManager.instance.resumeNavigation();
    }
  }
  // Remove login and signup page guard.
  if (AuthService.instance.isAuthenticated.value) {
    routesHolder
        .removeWhere((element) => element.metadata?['type'] == 'auth');
    if (routesHolder.isEmpty) {
      routesHolder.add(NavigationUtils.buildDefaultRouteFromName(
          navigation_routes.routes, '/'));
    }
  }
  return routesHolder;
}
Attach this callback in your app initialization: From example_auth/lib/main.dart:65-66:
NavigationManager.instance.setMainRoutes =
    (routes) => setMainRoutes(routes);

Marking routes as authenticated

Tag routes that require authentication using metadata: From example_auth/lib/navigation_routes.dart:8-12:
NavigationData(
    label: HomePage.name,
    url: '/',
    builder: (context, routeData, globalData) => const HomePage(),
    metadata: {'auth': true}),
The metadata field is a flexible Map<String, dynamic> that can hold any custom data for your route guards.
Prevent deeplinks from navigating when users are on certain pages like onboarding or tutorials.

Global exclusion list

Define pages that should never be interrupted by deeplinks:
List<String> doNotNavigateDeeplinkPages = [
  OnboardingPage.name,
  TutorialPage.name,
  SetupWizard.name,
];

NavigationUtils.openDeeplinkDestination(
  uri: uri,
  deeplinkDestinations: deeplinkDestinations,
  routerDelegate: NavigationManager.instance.routerDelegate,
  currentRoute: NavigationManager.instance.currentRoute,
  excludeDeeplinkNavigationPages: doNotNavigateDeeplinkPages,
);
Exclude specific pages for individual deeplinks:
DeeplinkDestination(
  deeplinkUrl: '/deeplink/settings',
  destinationUrl: '/settings',
  excludeDeeplinkNavigationPages: [PaymentPage.name, CheckoutPage.name],
)
Both the global excludeDeeplinkNavigationPages parameter and the per-deeplink excludeDeeplinkNavigationPages property accept both named routes and path routes.

Custom route guards with metadata

Create custom guards by tagging routes with metadata and checking them before navigation.

Setup

1

Tag routes with metadata

Add custom metadata to your routes:
NavigationData(
  label: PremiumMemberPage.name,
  url: '/premium_page',
  builder: (context, routeData, globalData) => const PremiumPage(),
  metadata: {'userStatus': 'PREMIUM'},
)
2

Check metadata before navigation

Implement custom guard logic:
if (NavigationManager.instance.routerDelegate
    .currentConfiguration?.metadata?['userStatus'] == 'PREMIUM') {
  DefaultRouteParser.openDeeplink(uri);
} else {
  // Redirect to upgrade page
  NavigationManager.instance.push(UpgradePage.name);
}

Common custom guards

Permission-based guard

NavigationData(
  url: '/admin',
  builder: (context, routeData, globalData) => const AdminPage(),
  metadata: {'requiredPermission': 'admin'},
)
Check permission before navigating:
void navigateIfAuthorized(String route) {
  NavigationData? navData = NavigationUtils.getNavigationDataFromName(
    routes,
    route,
  );
  
  String? requiredPermission = navData?.metadata?['requiredPermission'];
  
  if (requiredPermission == null || 
      UserService.instance.hasPermission(requiredPermission)) {
    NavigationManager.instance.push(route);
  } else {
    // Show error or redirect
    showPermissionDeniedDialog();
  }
}

Subscription-based guard

NavigationData(
  url: '/pro-features',
  builder: (context, routeData, globalData) => const ProFeaturesPage(),
  metadata: {'requiresSubscription': true},
)

Role-based guard

NavigationData(
  url: '/moderator/dashboard',
  builder: (context, routeData, globalData) => const ModeratorDashboard(),
  metadata: {
    'requiredRoles': ['moderator', 'admin'],
  },
)

Conditional navigation with shouldNavigateDeeplinkFunction

Prevent navigation based on custom runtime conditions:
DeeplinkDestination(
  deeplinkUrl: '/deeplink/premium',
  destinationUrl: '/premium',
  shouldNavigateDeeplinkFunction: () {
    // Only navigate if user has premium
    if (UserService.instance.isPremium) return true;
    
    // Show upgrade dialog instead
    showUpgradeDialog();
    return false;
  },
)

Async navigation guards

Perform async checks before navigation:
DeeplinkDestination(
  deeplinkUrl: '/deeplink/secure',
  destinationUrl: '/secure',
  shouldNavigateDeeplinkFunction: () async {
    // Wait for biometric verification
    bool isVerified = await BiometricService.verify();
    
    if (!isVerified) {
      showAuthenticationFailedDialog();
      return false;
    }
    
    return true;
  },
)
Async navigation allows you to perform operations like:
  • Biometric authentication
  • Fetching user permissions from an API
  • Checking feature flags
  • Validating session tokens

Protecting routes during auth state changes

Handle navigation when authentication state changes: From example_auth/lib/main.dart:96-112:
Future<void> onUserAuthenticated(String uid) async {
  // Attempt to load the initial route URI.
  if (loadInitialRoute) {
    loadInitialRoute = false;
    initialized = true;
    String initialRoute =
        NavigationManager.instance.routeInformationParser.initialRoute;
    NavigationManager.instance.set([initialRoute]);
  } else {
    NavigationManager.instance.set([HomePage.name]);
  }

  NavigationManager.instance.resumeNavigation();
}

Future<void> onUserUnauthenticated() async {
  // Automatically navigate to auth screen when user is logged out.
  if (NavigationManager.instance.currentRoute?.metadata?['auth'] == true) {
    NavigationManager.instance.set([SignUpForm.name]);
  }
  NavigationManager.instance.resumeNavigation();
  SignoutHelper.signOut();
}
Use pauseNavigation() and resumeNavigation() to temporarily halt navigation during authentication state transitions.

Best practices

Use metadata consistentlyDefine constants for metadata keys:
class RouteMetadata {
  static const String auth = 'auth';
  static const String permission = 'permission';
  static const String subscription = 'subscription';
}

NavigationData(
  url: '/admin',
  metadata: {
    RouteMetadata.auth: true,
    RouteMetadata.permission: 'admin',
  },
)
Provide fallback routesAlways redirect to a valid route when blocking navigation:
if (AuthService.instance.isAuthenticated.value == false) {
  routesHolder.removeWhere((element) => element.metadata?['auth'] == true);
  if (routesHolder.isEmpty) {
    // Fallback to signup if no valid routes remain
    routesHolder.add(DefaultRoute(label: SignUpForm.name, path: '/signup'));
  }
}
Don’t rely solely on client-side guardsRoute guards in the app only protect the UI. Always validate permissions and authentication on your backend as well.

Next steps

Deeplinks

Learn about deeplink authentication and conditional navigation

Custom transitions

Customize page transitions and animations

Build docs developers (and LLMs) love