From 0948b32adb74ce5d8618f94618a41b06116d1b8e Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Wed, 4 Mar 2026 16:54:20 +0100
Subject: [PATCH 01/11] ADDRESS_COUNTRY with query parameter support,
 refactoring related components, and updating navigation dynamic routes logic.

---
 src/ROUTES.ts                                 | 12 ++--
 src/SCREENS.ts                                |  2 +-
 src/components/CountrySelector.tsx            |  6 +-
 .../ModalStackNavigators/index.tsx            |  2 +-
 .../Navigation/helpers/createDynamicRoute.ts  | 55 +++++++++++++------
 .../helpers/getPathWithoutDynamicSuffix.ts    | 44 +++++++++++++--
 .../helpers/getStateForDynamicRoute.ts        | 18 ++++++
 src/libs/Navigation/helpers/linkTo/index.ts   |  5 +-
 .../RELATIONS/SETTINGS_TO_RHP.ts              |  1 -
 src/libs/Navigation/linkingConfig/config.ts   |  5 +-
 src/libs/Navigation/types.ts                  |  6 +-
 ...ge.tsx => DynamicCountrySelectionPage.tsx} | 30 ++++------
 tests/navigation/createDynamicRouteTests.ts   | 43 +++++++++++++++
 .../findMatchingDynamicSuffixTests.ts         |  4 ++
 .../getPathWithoutDynamicSuffixTests.ts       | 12 ++++
 tests/navigation/useDynamicBackPathTests.ts   |  1 +
 16 files changed, 183 insertions(+), 63 deletions(-)
 rename src/pages/settings/Profile/PersonalDetails/{CountrySelectionPage.tsx => DynamicCountrySelectionPage.tsx} (71%)

diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index c315ce527183..a196b59c3a4b 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -56,6 +56,7 @@ const VERIFY_ACCOUNT = 'verify-account';
 type DynamicRouteConfig = {
     path: string;
     entryScreens: Screen[];
+    getRoute?: (...args: never[]) => string;
 };
 
 type DynamicRoutes = Record<string, DynamicRouteConfig>;
@@ -97,6 +98,11 @@ const DYNAMIC_ROUTES = {
         path: 'owner-selector',
         entryScreens: [],
     },
+    ADDRESS_COUNTRY: {
+        path: 'country',
+        entryScreens: [SCREENS.SETTINGS.PROFILE.ADDRESS],
+        getRoute: (country: string) => `country?country=${country}`,
+    },
 } as const satisfies DynamicRoutes;
 
 const ROUTES = {
@@ -516,12 +522,6 @@ const ROUTES = {
     SETTINGS_DATE_OF_BIRTH: 'settings/profile/date-of-birth',
     SETTINGS_PHONE_NUMBER: 'settings/profile/phone',
     SETTINGS_ADDRESS: 'settings/profile/address',
-    SETTINGS_ADDRESS_COUNTRY: {
-        route: 'settings/profile/address/country',
-
-        // eslint-disable-next-line no-restricted-syntax -- Legacy route generation
-        getRoute: (country: string, backTo?: string) => getUrlWithBackToParam(`settings/profile/address/country?country=${country}`, backTo),
-    },
     SETTINGS_ADDRESS_STATE: {
         route: 'settings/profile/address/state',
 
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index ee0d293f8664..2cafa9460336 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -142,7 +142,7 @@ const SCREENS = {
             PHONE_NUMBER: 'Settings_PhoneNumber',
             ADDRESS: 'Settings_Address',
             AVATAR: 'Settings_Avatar',
-            ADDRESS_COUNTRY: 'Settings_Address_Country',
+            DYNAMIC_ADDRESS_COUNTRY: 'Dynamic_Address_Country',
             ADDRESS_STATE: 'Settings_Address_State',
         },
 
diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx
index 9c64311a1aea..813779c1895a 100644
--- a/src/components/CountrySelector.tsx
+++ b/src/components/CountrySelector.tsx
@@ -5,10 +5,11 @@ import type {View} from 'react-native';
 import useGeographicalStateAndCountryFromRoute from '@hooks/useGeographicalStateAndCountryFromRoute';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
+import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import CONST from '@src/CONST';
 import type {Country} from '@src/CONST';
-import ROUTES from '@src/ROUTES';
+import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import MenuItemWithTopDescription from './MenuItemWithTopDescription';
 
 type CountrySelectorProps = {
@@ -78,9 +79,8 @@ function CountrySelector({errorText = '', value: countryCode, onInputChange = ()
             description={translate('common.country')}
             errorText={errorText}
             onPress={() => {
-                const activeRoute = Navigation.getActiveRoute();
                 didOpenCountrySelector.current = true;
-                Navigation.navigate(ROUTES.SETTINGS_ADDRESS_COUNTRY.getRoute(countryCode ?? '', activeRoute));
+                Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.ADDRESS_COUNTRY.getRoute(countryCode ?? '')));
             }}
         />
     );
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 04da596d1c4c..28dc0514f6d1 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -387,7 +387,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
     [SCREENS.SETTINGS.PROFILE.DATE_OF_BIRTH]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default,
     [SCREENS.SETTINGS.PROFILE.PHONE_NUMBER]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/PhoneNumberPage').default,
     [SCREENS.SETTINGS.PROFILE.ADDRESS]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default,
-    [SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default,
+    [SCREENS.SETTINGS.PROFILE.DYNAMIC_ADDRESS_COUNTRY]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage').default,
     [SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default,
     [SCREENS.SETTINGS.PROFILE.AVATAR]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Avatar/AvatarPage').default,
     [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/ContactMethodsPage').default,
diff --git a/src/libs/Navigation/helpers/createDynamicRoute.ts b/src/libs/Navigation/helpers/createDynamicRoute.ts
index 51b62ac3bb71..460618b34490 100644
--- a/src/libs/Navigation/helpers/createDynamicRoute.ts
+++ b/src/libs/Navigation/helpers/createDynamicRoute.ts
@@ -1,35 +1,54 @@
 import Log from '@libs/Log';
 import Navigation from '@libs/Navigation/Navigation';
-import type {DynamicRouteSuffix, Route} from '@src/ROUTES';
+import type {Route} from '@src/ROUTES';
 import isDynamicRouteSuffix from './isDynamicRouteSuffix';
 import splitPathAndQuery from './splitPathAndQuery';
 
-const combinePathAndSuffix = (path: string, suffix: string): Route => {
-    const [normalizedPath, query] = splitPathAndQuery(path);
-
-    // This should never happen as the path should always be defined
-    if (!normalizedPath) {
-        Log.warn('[createDynamicRoute.ts] Path is undefined or empty, returning suffix only', {path, suffix});
-        return suffix as Route;
+/**
+ * Merges two query strings into one. If both contain the same key,
+ * the suffix value wins and a warning is logged.
+ */
+const mergeQueryStrings = (baseQuery?: string, suffixQuery?: string): string => {
+    if (!baseQuery && !suffixQuery) {
+        return '';
+    }
+    const params = new URLSearchParams(baseQuery ?? '');
+    const suffixParams = new URLSearchParams(suffixQuery ?? '');
+    for (const [key, value] of suffixParams.entries()) {
+        if (params.has(key)) {
+            Log.warn(`[createDynamicRoute] Query param "${key}" exists in both base path and dynamic suffix. Suffix value will override.`);
+        }
+        params.set(key, value);
     }
+    const result = params.toString();
+    return result ? `?${result}` : '';
+};
 
-    let newPath = normalizedPath === '/' ? `/${suffix}` : `${normalizedPath}/${suffix}`;
+const combinePathAndSuffix = (basePath: string, suffixWithQuery: string): Route => {
+    const [normalizedBasePath, baseQuery] = splitPathAndQuery(basePath);
+    const [suffixPath, suffixQuery] = splitPathAndQuery(suffixWithQuery);
 
-    if (query) {
-        newPath += `?${query}`;
+    if (!normalizedBasePath) {
+        Log.warn('[createDynamicRoute.ts] Path is undefined or empty, returning suffix only', {basePath, suffixWithQuery});
+        return suffixWithQuery as Route;
     }
-    return newPath as Route;
+
+    const combinedPath = normalizedBasePath === '/' ? `/${suffixPath}` : `${normalizedBasePath}/${suffixPath}`;
+    const mergedQuery = mergeQueryStrings(baseQuery, suffixQuery);
+
+    return `${combinedPath}${mergedQuery}` as Route;
 };
 
-/** Adds dynamic route name to the current URL and returns it */
-const createDynamicRoute = (dynamicRouteSuffix: DynamicRouteSuffix): Route => {
-    if (!isDynamicRouteSuffix(dynamicRouteSuffix)) {
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        throw new Error(`The route name ${dynamicRouteSuffix} is not supported in createDynamicRoute`);
+/** Adds dynamic route name (with optional query params) to the current URL and returns it */
+const createDynamicRoute = (dynamicRouteSuffixWithParams: string): Route => {
+    const [suffixPath] = splitPathAndQuery(dynamicRouteSuffixWithParams);
+
+    if (!suffixPath || !isDynamicRouteSuffix(suffixPath)) {
+        throw new Error(`The route name ${suffixPath} is not supported in createDynamicRoute`);
     }
 
     const activeRoute = Navigation.getActiveRoute();
-    return combinePathAndSuffix(activeRoute, dynamicRouteSuffix);
+    return combinePathAndSuffix(activeRoute, dynamicRouteSuffixWithParams);
 };
 
 export default createDynamicRoute;
diff --git a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts b/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
index 935372ab5475..f7da4381ed39 100644
--- a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
+++ b/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
@@ -1,17 +1,51 @@
 import type {Route} from '@src/ROUTES';
+import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import splitPathAndQuery from './splitPathAndQuery';
 
+function getQueryParamsToStrip(dynamicSuffix: string): string[] | undefined {
+    const keys = Object.keys(DYNAMIC_ROUTES) as Array<keyof typeof DYNAMIC_ROUTES>;
+    const match = keys.find((key) => DYNAMIC_ROUTES[key].path === dynamicSuffix);
+    if (!match) {
+        return undefined;
+    }
+    const config = DYNAMIC_ROUTES[match];
+    if (!('getRoute' in config) || !config.getRoute) {
+        return undefined;
+    }
+    const getRouteFn = config.getRoute as unknown as (...args: string[]) => string;
+    const placeholderArgs: string[] = Array(getRouteFn.length).fill('') as string[];
+    const routeOutput = getRouteFn(...placeholderArgs);
+    const [, query] = splitPathAndQuery(routeOutput);
+    if (!query) {
+        return undefined;
+    }
+    return Array.from(new URLSearchParams(query).keys());
+}
+
 /**
- * Returns the path without a dynamic route suffix while preserving query parameters.
+ * Returns the path without a dynamic route suffix, stripping suffix-specific query parameters
+ * (derived from the matching DYNAMIC_ROUTES.getRoute output) and preserving any remaining ones.
  *
- * @param fullPath - The full URL path possibly containing query params (e.g., '/settings/wallet/verify-account?param=value')
- * @param dynamicSuffix - The dynamic suffix to strip (e.g., 'verify-account')
- * @returns The path without the suffix and with query params re-appended
+ * @param fullPath - The full URL path possibly containing query params (e.g., '/settings/profile/address/country?country=US')
+ * @param dynamicSuffix - The dynamic suffix to strip (e.g., 'country')
+ * @returns The path without the suffix and with only base-path query params preserved
  */
 function getPathWithoutDynamicSuffix(fullPath: string, dynamicSuffix: string): Route {
     const [pathWithoutQuery, query] = splitPathAndQuery(fullPath);
     const pathWithoutDynamicSuffix = pathWithoutQuery?.slice(0, -(dynamicSuffix.length + 1)) ?? '';
-    return `${pathWithoutDynamicSuffix}${query ? `?${query}` : ''}` as Route;
+
+    const paramsToStrip = getQueryParamsToStrip(dynamicSuffix);
+    let filteredQuery = query;
+    if (paramsToStrip?.length && query) {
+        const params = new URLSearchParams(query);
+        for (const key of paramsToStrip) {
+            params.delete(key);
+        }
+        const result = params.toString();
+        filteredQuery = result || undefined;
+    }
+
+    return `${pathWithoutDynamicSuffix}${filteredQuery ? `?${filteredQuery}` : ''}` as Route;
 }
 
 export default getPathWithoutDynamicSuffix;
diff --git a/src/libs/Navigation/helpers/getStateForDynamicRoute.ts b/src/libs/Navigation/helpers/getStateForDynamicRoute.ts
index fc9fb1a378b1..c7b8127eb739 100644
--- a/src/libs/Navigation/helpers/getStateForDynamicRoute.ts
+++ b/src/libs/Navigation/helpers/getStateForDynamicRoute.ts
@@ -1,10 +1,12 @@
 import {normalizedConfigs} from '@libs/Navigation/linkingConfig/config';
 import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import type {DynamicRouteSuffix} from '@src/ROUTES';
+import splitPathAndQuery from './splitPathAndQuery';
 
 type LeafRoute = {
     name: string;
     path: string;
+    params?: Record<string, string>;
 };
 
 type NestedRoute = {
@@ -19,6 +21,19 @@ type RouteNode = LeafRoute | NestedRoute;
 
 const configEntries = Object.entries(normalizedConfigs);
 
+function getParamsFromQuery(query: string | undefined): Record<string, string> | undefined {
+    if (!query) {
+        return undefined;
+    }
+
+    const entries = Array.from(new URLSearchParams(query).entries());
+    if (entries.length === 0) {
+        return undefined;
+    }
+
+    return Object.fromEntries(entries);
+}
+
 function getRouteNamesForDynamicRoute(dynamicRouteName: DynamicRouteSuffix): string[] | null {
     // Search through normalized configs to find matching path and extract navigation hierarchy
     // routeNames contains the sequence of screen/navigator names that should be present in the navigation state
@@ -33,6 +48,8 @@ function getRouteNamesForDynamicRoute(dynamicRouteName: DynamicRouteSuffix): str
 
 function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DYNAMIC_ROUTES) {
     const routeConfig = getRouteNamesForDynamicRoute(DYNAMIC_ROUTES[dynamicRouteName].path);
+    const [, query] = splitPathAndQuery(path);
+    const params = getParamsFromQuery(query);
 
     if (!routeConfig) {
         throw new Error(`No route configuration found for dynamic route '${dynamicRouteName}'`);
@@ -47,6 +64,7 @@ function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DY
             return {
                 name: currentRoute ?? '',
                 path,
+                params,
             };
         }
 
diff --git a/src/libs/Navigation/helpers/linkTo/index.ts b/src/libs/Navigation/helpers/linkTo/index.ts
index 63126c40e1c7..c17495466a03 100644
--- a/src/libs/Navigation/helpers/linkTo/index.ts
+++ b/src/libs/Navigation/helpers/linkTo/index.ts
@@ -1,6 +1,7 @@
 import {getActionFromState} from '@react-navigation/core';
 import type {NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native';
 import {findFocusedRoute, StackActions} from '@react-navigation/native';
+import findMatchingDynamicSuffix from '@libs/Navigation/helpers/findMatchingDynamicSuffix';
 import {getMatchingFullScreenRoute, isFullScreenName} from '@libs/Navigation/helpers/getAdaptedStateFromPath';
 import getStateFromPath from '@libs/Navigation/helpers/getStateFromPath';
 import normalizePath from '@libs/Navigation/helpers/normalizePath';
@@ -151,10 +152,12 @@ export default function linkTo(navigation: NavigationContainerRef<RootNavigatorP
     // If not, it will be replaced. This way, navigating between one attachment screen and another won't be added to the browser history.
     // Report screen - Also a special case. If we are navigating to the report with same reportID we want to replace it (navigate will do that).
     // This covers the case when we open a specific message in report (reportActionID).
+    // Dynamic routes - Keep NAVIGATE so that StackRouter preserves `path` on the route (PUSH explicitly sets path to undefined).
     else if (
         action.type === CONST.NAVIGATION.ACTION_TYPE.NAVIGATE &&
         !isNavigatingToAttachmentScreen(focusedRouteFromPath?.name) &&
-        !isNavigatingToReportWithSameReportID(currentFocusedRoute, focusedRouteFromPath)
+        !isNavigatingToReportWithSameReportID(currentFocusedRoute, focusedRouteFromPath) &&
+        !findMatchingDynamicSuffix(normalizedPath)
     ) {
         // We want to PUSH by default to add entries to the browser history.
         action.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
index f5969c3ee4b1..8dc412afdd43 100755
--- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
+++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
@@ -22,7 +22,6 @@ const SETTINGS_TO_RHP: Partial<Record<keyof SettingsSplitNavigatorParamList, str
         SCREENS.SETTINGS.PROFILE.DATE_OF_BIRTH,
         SCREENS.SETTINGS.PROFILE.PHONE_NUMBER,
         SCREENS.SETTINGS.PROFILE.ADDRESS,
-        SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY,
         SCREENS.SETTINGS.SHARE_CODE,
     ],
     [SCREENS.SETTINGS.PREFERENCES.ROOT]: [
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index bf06644a527d..1c092493a204 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -442,10 +442,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
                             path: ROUTES.SETTINGS_ADDRESS,
                             exact: true,
                         },
-                        [SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: {
-                            path: ROUTES.SETTINGS_ADDRESS_COUNTRY.route,
-                            exact: true,
-                        },
+                        [SCREENS.SETTINGS.PROFILE.DYNAMIC_ADDRESS_COUNTRY]: DYNAMIC_ROUTES.ADDRESS_COUNTRY.path,
                         [SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: {
                             path: ROUTES.SETTINGS_ADDRESS_STATE.route,
                             exact: true,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index da27c0037bae..5f37b88682b6 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -98,10 +98,8 @@ type SettingsNavigatorParamList = {
     [SCREENS.SETTINGS.PROFILE.ADDRESS]: {
         country?: Country | '';
     };
-    [SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY]: {
-        // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
-        backTo?: Routes;
-        country: string;
+    [SCREENS.SETTINGS.PROFILE.DYNAMIC_ADDRESS_COUNTRY]: {
+        country?: string;
     };
     [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: {
         // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
diff --git a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
similarity index 71%
rename from src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx
rename to src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
index 34c9197f158b..df253c624ab8 100644
--- a/src/pages/settings/Profile/PersonalDetails/CountrySelectionPage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
@@ -3,6 +3,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
 import ScreenWrapper from '@components/ScreenWrapper';
 import SelectionList from '@components/SelectionList';
 import RadioListItem from '@components/SelectionList/ListItem/RadioListItem';
+import useDynamicBackPath from '@hooks/useDynamicBackPath';
 import useLocalize from '@hooks/useLocalize';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -13,15 +14,16 @@ import StringUtils from '@libs/StringUtils';
 import {appendParam} from '@libs/Url';
 import CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
-import type {Route} from '@src/ROUTES';
+import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import type SCREENS from '@src/SCREENS';
 
-type CountrySelectionPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY>;
+type DynamicCountrySelectionPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.PROFILE.DYNAMIC_ADDRESS_COUNTRY>;
 
-function CountrySelectionPage({route}: CountrySelectionPageProps) {
+function DynamicCountrySelectionPage({route}: DynamicCountrySelectionPageProps) {
     const [searchValue, setSearchValue] = useState('');
     const {translate} = useLocalize();
-    const currentCountry = route.params.country;
+    const currentCountry = route.params?.country ?? '';
+    const backPath = useDynamicBackPath(DYNAMIC_ROUTES.ADDRESS_COUNTRY.path);
 
     const countries = useMemo(
         () =>
@@ -42,17 +44,9 @@ function CountrySelectionPage({route}: CountrySelectionPageProps) {
 
     const selectCountry = useCallback(
         (option: Option) => {
-            const backTo = route.params.backTo ?? '';
-
-            // Check the "backTo" parameter to decide navigation behavior
-            if (!backTo) {
-                Navigation.goBack();
-            } else {
-                // Set compareParams to false because we want to go back to this particular screen and update params (country).
-                Navigation.goBack(appendParam(backTo, 'country', option.value), {compareParams: false});
-            }
+            Navigation.goBack(appendParam(backPath, 'country', option.value), {compareParams: false});
         },
-        [route.params.backTo],
+        [backPath],
     );
 
     const textInputOptions = useMemo(
@@ -67,16 +61,14 @@ function CountrySelectionPage({route}: CountrySelectionPageProps) {
 
     return (
         <ScreenWrapper
-            testID="CountrySelectionPage"
+            testID="DynamicCountrySelectionPage"
             enableEdgeToEdgeBottomSafeAreaPadding
         >
             <HeaderWithBackButton
                 title={translate('common.country')}
                 shouldShowBackButton
                 onBackButtonPress={() => {
-                    const backTo = route.params.backTo ?? '';
-                    const backToRoute = backTo ? `${backTo}?country=${currentCountry}` : '';
-                    Navigation.goBack(backToRoute as Route, {compareParams: false});
+                    Navigation.goBack(appendParam(backPath, 'country', currentCountry), {compareParams: false});
                 }}
             />
 
@@ -93,4 +85,4 @@ function CountrySelectionPage({route}: CountrySelectionPageProps) {
     );
 }
 
-export default CountrySelectionPage;
+export default DynamicCountrySelectionPage;
diff --git a/tests/navigation/createDynamicRouteTests.ts b/tests/navigation/createDynamicRouteTests.ts
index a8bc48ca63fe..0680d9225779 100644
--- a/tests/navigation/createDynamicRouteTests.ts
+++ b/tests/navigation/createDynamicRouteTests.ts
@@ -1,3 +1,4 @@
+import Log from '@libs/Log';
 import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import type {DynamicRouteSuffix} from '@src/ROUTES';
@@ -6,6 +7,10 @@ jest.mock('@libs/Navigation/Navigation', () => ({
     getActiveRoute: jest.fn(),
 }));
 
+jest.mock('@libs/Log', () => ({
+    warn: jest.fn(),
+}));
+
 jest.mock('@src/ROUTES', () => ({
     DYNAMIC_ROUTES: {
         VERIFY_ACCOUNT: {path: 'verify-account'},
@@ -13,11 +18,13 @@ jest.mock('@src/ROUTES', () => ({
         DETAILS: {path: 'details'},
         INVITE: {path: 'invite'},
         FILTERS: {path: 'filters'},
+        ADDRESS_COUNTRY: {path: 'country', getRoute: (country: string) => `country?country=${country}`},
     },
 }));
 
 describe('createDynamicRoute', () => {
     const mockGetActiveRoute = Navigation.getActiveRoute as jest.Mock;
+    const mockLogWarn = jest.spyOn(Log, 'warn');
 
     beforeEach(() => {
         jest.clearAllMocks();
@@ -78,4 +85,40 @@ describe('createDynamicRoute', () => {
 
         expect(result).toBe(expectedPath);
     });
+
+    it('should append suffix with its own query params to a simple path', () => {
+        const activeRoute = 'settings/profile/address';
+        const suffixWithQuery = 'country?country=US';
+        const expectedPath = 'settings/profile/address/country?country=US';
+
+        mockGetActiveRoute.mockReturnValue(activeRoute);
+
+        const result = createDynamicRoute(suffixWithQuery);
+
+        expect(result).toBe(expectedPath);
+    });
+
+    it('should merge suffix query params with base path query params', () => {
+        const activeRoute = 'settings/profile/address?existingParam=1';
+        const suffixWithQuery = 'country?country=US';
+        const expectedPath = 'settings/profile/address/country?existingParam=1&country=US';
+
+        mockGetActiveRoute.mockReturnValue(activeRoute);
+
+        const result = createDynamicRoute(suffixWithQuery);
+
+        expect(result).toBe(expectedPath);
+    });
+
+    it('should log warning when suffix query param collides with base path query param', () => {
+        const activeRoute = 'settings/profile/address?country=GB';
+        const suffixWithQuery = 'country?country=US';
+
+        mockGetActiveRoute.mockReturnValue(activeRoute);
+
+        const result = createDynamicRoute(suffixWithQuery);
+
+        expect(result).toBe('settings/profile/address/country?country=US');
+        expect(mockLogWarn).toHaveBeenCalledWith(expect.stringContaining('Query param "country" exists in both base path and dynamic suffix'));
+    });
 });
diff --git a/tests/navigation/findMatchingDynamicSuffixTests.ts b/tests/navigation/findMatchingDynamicSuffixTests.ts
index 46e3fcefa20b..a3f42be0c2b9 100644
--- a/tests/navigation/findMatchingDynamicSuffixTests.ts
+++ b/tests/navigation/findMatchingDynamicSuffixTests.ts
@@ -32,4 +32,8 @@ describe('findMatchingDynamicSuffix', () => {
     it('should not match a suffix that appears in the middle of the path', () => {
         expect(findMatchingDynamicSuffix('/verify-account/settings/wallet')).toBeUndefined();
     });
+
+    it('should match a suffix when path has suffix-specific query params', () => {
+        expect(findMatchingDynamicSuffix('settings/profile/address/country?country=US')).toBe('country');
+    });
 });
diff --git a/tests/navigation/getPathWithoutDynamicSuffixTests.ts b/tests/navigation/getPathWithoutDynamicSuffixTests.ts
index 72bed418726f..b195474a6d2f 100644
--- a/tests/navigation/getPathWithoutDynamicSuffixTests.ts
+++ b/tests/navigation/getPathWithoutDynamicSuffixTests.ts
@@ -36,4 +36,16 @@ describe('getPathWithoutDynamicSuffix', () => {
 
         expect(result).toBe('/settings/wallet');
     });
+
+    it('should strip suffix-specific query params derived from DYNAMIC_ROUTES.getRoute', () => {
+        const result = getPathWithoutDynamicSuffix('/settings/profile/address/country?country=US', 'country');
+
+        expect(result).toBe('/settings/profile/address');
+    });
+
+    it('should strip only suffix-specific params and preserve base path params', () => {
+        const result = getPathWithoutDynamicSuffix('/settings/profile/address/country?baseParam=1&country=US', 'country');
+
+        expect(result).toBe('/settings/profile/address?baseParam=1');
+    });
 });
diff --git a/tests/navigation/useDynamicBackPathTests.ts b/tests/navigation/useDynamicBackPathTests.ts
index 325a12508fd3..99dacded768f 100644
--- a/tests/navigation/useDynamicBackPathTests.ts
+++ b/tests/navigation/useDynamicBackPathTests.ts
@@ -11,6 +11,7 @@ jest.mock('@src/ROUTES', () => ({
     DYNAMIC_ROUTES: {
         VERIFY_ACCOUNT: {path: 'verify-account'},
         CUSTOM_TEST_ROUTE: {path: 'custom-test-route'},
+        ADDRESS_COUNTRY: {path: 'country'},
     },
 }));
 

From 71fc19300d83d3e4d9f3420800a0f10d5d9962b1 Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Fri, 6 Mar 2026 13:03:39 +0100
Subject: [PATCH 02/11] cleanup

---
 contributingGuides/NAVIGATION.md                      |  1 -
 src/libs/Navigation/helpers/createDynamicRoute.ts     | 11 ++++++-----
 .../Navigation/helpers/getPathWithoutDynamicSuffix.ts |  2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/contributingGuides/NAVIGATION.md b/contributingGuides/NAVIGATION.md
index fb76ad9dd99a..35330ed35271 100644
--- a/contributingGuides/NAVIGATION.md
+++ b/contributingGuides/NAVIGATION.md
@@ -700,7 +700,6 @@ A dynamic route is a URL suffix (e.g. `verify-account`) that can be appended to
 Do not use dynamic routes when:
 - Your use case falls under the [current limitations](#current-limitations-work-in-progress):
   - You need to stack multiple dynamic route suffixes (e.g. `/a/verify-account/another-flow`).
-  - Your suffix includes path or query parameters (e.g. `verify-account/:id` or `verify-account?tab=details`).
 - The screen has a single, fixed entry and a fixed back destination. In this case, use a normal static route instead.
 
 ### Dynamic routes configuration
diff --git a/src/libs/Navigation/helpers/createDynamicRoute.ts b/src/libs/Navigation/helpers/createDynamicRoute.ts
index 460618b34490..11cb771fb6ee 100644
--- a/src/libs/Navigation/helpers/createDynamicRoute.ts
+++ b/src/libs/Navigation/helpers/createDynamicRoute.ts
@@ -8,15 +8,16 @@ import splitPathAndQuery from './splitPathAndQuery';
  * Merges two query strings into one. If both contain the same key,
  * the suffix value wins and a warning is logged.
  */
-const mergeQueryStrings = (baseQuery?: string, suffixQuery?: string): string => {
+const mergeQueryStrings = (baseQuery = '', suffixQuery = ''): string => {
     if (!baseQuery && !suffixQuery) {
         return '';
     }
-    const params = new URLSearchParams(baseQuery ?? '');
-    const suffixParams = new URLSearchParams(suffixQuery ?? '');
-    for (const [key, value] of suffixParams.entries()) {
+    const params = new URLSearchParams(baseQuery);
+    const suffixParams = new URLSearchParams(suffixQuery);
+    const suffixParamsEntries = suffixParams.entries();
+    for (const [key, value] of suffixParamsEntries) {
         if (params.has(key)) {
-            Log.warn(`[createDynamicRoute] Query param "${key}" exists in both base path and dynamic suffix. Suffix value will override.`);
+            throw new Error(`[createDynamicRoute] Query param "${key}" exists in both base path and dynamic suffix. This is not allowed.`);
         }
         params.set(key, value);
     }
diff --git a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts b/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
index f7da4381ed39..d474d1d91e8a 100644
--- a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
+++ b/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
@@ -12,7 +12,7 @@ function getQueryParamsToStrip(dynamicSuffix: string): string[] | undefined {
     if (!('getRoute' in config) || !config.getRoute) {
         return undefined;
     }
-    const getRouteFn = config.getRoute as unknown as (...args: string[]) => string;
+    const getRouteFn = config.getRoute as (...args: string[]) => string;
     const placeholderArgs: string[] = Array(getRouteFn.length).fill('') as string[];
     const routeOutput = getRouteFn(...placeholderArgs);
     const [, query] = splitPathAndQuery(routeOutput);

From a55b12591f330bfec2ce933a038c8a2c7ec2f28b Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Fri, 6 Mar 2026 13:24:43 +0100
Subject: [PATCH 03/11] fix tests

---
 tests/navigation/createDynamicRouteTests.ts | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/tests/navigation/createDynamicRouteTests.ts b/tests/navigation/createDynamicRouteTests.ts
index 0680d9225779..272b692c3b77 100644
--- a/tests/navigation/createDynamicRouteTests.ts
+++ b/tests/navigation/createDynamicRouteTests.ts
@@ -1,4 +1,3 @@
-import Log from '@libs/Log';
 import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import type {DynamicRouteSuffix} from '@src/ROUTES';
@@ -24,7 +23,6 @@ jest.mock('@src/ROUTES', () => ({
 
 describe('createDynamicRoute', () => {
     const mockGetActiveRoute = Navigation.getActiveRoute as jest.Mock;
-    const mockLogWarn = jest.spyOn(Log, 'warn');
 
     beforeEach(() => {
         jest.clearAllMocks();
@@ -110,15 +108,12 @@ describe('createDynamicRoute', () => {
         expect(result).toBe(expectedPath);
     });
 
-    it('should log warning when suffix query param collides with base path query param', () => {
+    it('should throw an error when suffix query param collides with base path query param', () => {
         const activeRoute = 'settings/profile/address?country=GB';
         const suffixWithQuery = 'country?country=US';
 
         mockGetActiveRoute.mockReturnValue(activeRoute);
 
-        const result = createDynamicRoute(suffixWithQuery);
-
-        expect(result).toBe('settings/profile/address/country?country=US');
-        expect(mockLogWarn).toHaveBeenCalledWith(expect.stringContaining('Query param "country" exists in both base path and dynamic suffix'));
+        expect(() => createDynamicRoute(suffixWithQuery)).toThrow('[createDynamicRoute] Query param "country" exists in both base path and dynamic suffix. This is not allowed.');
     });
 });

From 37871d1ebe033ec52bc2c6ddc03b2928b45f300e Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Fri, 6 Mar 2026 16:46:42 +0100
Subject: [PATCH 04/11] add other entryScreens to address country dynamic page

---
 src/ROUTES.ts | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 670ce3108288..be29f685f5aa 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -100,8 +100,15 @@ const DYNAMIC_ROUTES = {
     },
     ADDRESS_COUNTRY: {
         path: 'country',
-        entryScreens: [SCREENS.SETTINGS.PROFILE.ADDRESS],
-        getRoute: (country: string) => `country?country=${country}`,
+        entryScreens: [
+            SCREENS.SETTINGS.PROFILE.ADDRESS,
+            SCREENS.WORKSPACE.ADDRESS,
+            SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS,
+            SCREENS.DOMAIN_CARD.DOMAIN_CARD_UPDATE_ADDRESS,
+            SCREENS.TRAVEL.WORKSPACE_ADDRESS,
+            SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT,
+        ],
+        getRoute: (country: string) => `country${country ? `?country=${country}` : ''}`,
     },
 } as const satisfies DynamicRoutes;
 

From ad315d28f3e2093178d9e0281d160119153bd58a Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Fri, 6 Mar 2026 17:11:04 +0100
Subject: [PATCH 05/11] change to additional arg in config

---
 src/ROUTES.ts                                       |  4 +++-
 .../helpers/getPathWithoutDynamicSuffix.ts          | 13 +++----------
 2 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index be29f685f5aa..1d21a4cb0b4a 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -57,6 +57,7 @@ type DynamicRouteConfig = {
     path: string;
     entryScreens: Screen[];
     getRoute?: (...args: never[]) => string;
+    queryParams?: readonly string[];
 };
 
 type DynamicRoutes = Record<string, DynamicRouteConfig>;
@@ -108,7 +109,8 @@ const DYNAMIC_ROUTES = {
             SCREENS.TRAVEL.WORKSPACE_ADDRESS,
             SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT,
         ],
-        getRoute: (country: string) => `country${country ? `?country=${country}` : ''}`,
+        getRoute: (country = '') => `country${country ? `?country=${country}` : ''}`,
+        queryParams: ['country'],
     },
 } as const satisfies DynamicRoutes;
 
diff --git a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts b/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
index 208128eb051d..5fe23cc3b00f 100644
--- a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
+++ b/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
@@ -2,24 +2,17 @@ import type {Route} from '@src/ROUTES';
 import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import splitPathAndQuery from './splitPathAndQuery';
 
-function getQueryParamsToStrip(dynamicSuffix: string): string[] | undefined {
+function getQueryParamsToStrip(dynamicSuffix: string): readonly string[] | undefined {
     const keys = Object.keys(DYNAMIC_ROUTES) as Array<keyof typeof DYNAMIC_ROUTES>;
     const match = keys.find((key) => DYNAMIC_ROUTES[key].path === dynamicSuffix);
     if (!match) {
         return undefined;
     }
     const config = DYNAMIC_ROUTES[match];
-    if (!('getRoute' in config) || !config.getRoute) {
+    if (!('queryParams' in config)) {
         return undefined;
     }
-    const getRouteFn = config.getRoute as (...args: string[]) => string;
-    const placeholderArgs: string[] = Array(getRouteFn.length).fill('') as string[];
-    const routeOutput = getRouteFn(...placeholderArgs);
-    const [, query] = splitPathAndQuery(routeOutput);
-    if (!query) {
-        return undefined;
-    }
-    return Array.from(new URLSearchParams(query).keys());
+    return config.queryParams;
 }
 
 /**

From 0938c45952bf3c86d6830abf2dcfc0660c46911d Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Mon, 9 Mar 2026 16:17:18 +0100
Subject: [PATCH 06/11] Refactor dynamic route imports to a dedicated utils
 directory for better organization and maintainability

---
 src/components/CountrySelector.tsx                       | 2 +-
 src/components/WorkspaceConfirmationForm.tsx             | 2 +-
 src/hooks/useDynamicBackPath.ts                          | 4 ++--
 .../{ => dynamicRoutesUtils}/createDynamicRoute.ts       | 9 ++++++++-
 .../findMatchingDynamicSuffix.ts                         | 0
 .../getPathWithoutDynamicSuffix.ts                       | 8 ++++++++
 .../{ => dynamicRoutesUtils}/getStateForDynamicRoute.ts  | 0
 .../{ => dynamicRoutesUtils}/isDynamicRouteSuffix.ts     | 0
 .../{ => dynamicRoutesUtils}/splitPathAndQuery.ts        | 0
 src/libs/Navigation/helpers/getAdaptedStateFromPath.ts   | 4 ++--
 src/libs/Navigation/helpers/getPathFromState.ts          | 2 +-
 src/libs/Navigation/helpers/getStateFromPath.ts          | 6 +++---
 src/libs/Navigation/helpers/linkTo/index.ts              | 2 +-
 src/libs/actions/BankAccounts.ts                         | 2 +-
 src/pages/settings/Wallet/PaymentMethodList.tsx          | 2 +-
 src/pages/settings/Wallet/WalletPage/index.tsx           | 2 +-
 16 files changed, 30 insertions(+), 15 deletions(-)
 rename src/libs/Navigation/helpers/{ => dynamicRoutesUtils}/createDynamicRoute.ts (86%)
 rename src/libs/Navigation/helpers/{ => dynamicRoutesUtils}/findMatchingDynamicSuffix.ts (100%)
 rename src/libs/Navigation/helpers/{ => dynamicRoutesUtils}/getPathWithoutDynamicSuffix.ts (82%)
 rename src/libs/Navigation/helpers/{ => dynamicRoutesUtils}/getStateForDynamicRoute.ts (100%)
 rename src/libs/Navigation/helpers/{ => dynamicRoutesUtils}/isDynamicRouteSuffix.ts (100%)
 rename src/libs/Navigation/helpers/{ => dynamicRoutesUtils}/splitPathAndQuery.ts (100%)

diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx
index 813779c1895a..20e495670d13 100644
--- a/src/components/CountrySelector.tsx
+++ b/src/components/CountrySelector.tsx
@@ -5,7 +5,7 @@ import type {View} from 'react-native';
 import useGeographicalStateAndCountryFromRoute from '@hooks/useGeographicalStateAndCountryFromRoute';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
-import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import CONST from '@src/CONST';
 import type {Country} from '@src/CONST';
diff --git a/src/components/WorkspaceConfirmationForm.tsx b/src/components/WorkspaceConfirmationForm.tsx
index b2158325084e..10d5194797b1 100644
--- a/src/components/WorkspaceConfirmationForm.tsx
+++ b/src/components/WorkspaceConfirmationForm.tsx
@@ -14,7 +14,7 @@ import {generateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Poli
 import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
 import {addErrorMessage} from '@libs/ErrorUtils';
 import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter';
-import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
 import {isRequiredFulfilled} from '@libs/ValidationUtils';
diff --git a/src/hooks/useDynamicBackPath.ts b/src/hooks/useDynamicBackPath.ts
index 1bebe7f6e814..b2a33637cf7e 100644
--- a/src/hooks/useDynamicBackPath.ts
+++ b/src/hooks/useDynamicBackPath.ts
@@ -1,6 +1,6 @@
+import getPathWithoutDynamicSuffix from '@libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix';
+import splitPathAndQuery from '@libs/Navigation/helpers/dynamicRoutesUtils/splitPathAndQuery';
 import getPathFromState from '@libs/Navigation/helpers/getPathFromState';
-import getPathWithoutDynamicSuffix from '@libs/Navigation/helpers/getPathWithoutDynamicSuffix';
-import splitPathAndQuery from '@libs/Navigation/helpers/splitPathAndQuery';
 import type {State} from '@libs/Navigation/types';
 import type {DynamicRouteSuffix, Route} from '@src/ROUTES';
 import ROUTES from '@src/ROUTES';
diff --git a/src/libs/Navigation/helpers/createDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts
similarity index 86%
rename from src/libs/Navigation/helpers/createDynamicRoute.ts
rename to src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts
index 11cb771fb6ee..17b8a280f177 100644
--- a/src/libs/Navigation/helpers/createDynamicRoute.ts
+++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts
@@ -6,7 +6,14 @@ import splitPathAndQuery from './splitPathAndQuery';
 
 /**
  * Merges two query strings into one. If both contain the same key,
- * the suffix value wins and a warning is logged.
+ * the error is thrown.
+ * @param baseQuery - The query string of the base path
+ * @param suffixQuery - The query string of the suffix
+ * @returns The merged query string or an empty string if both are empty
+ *
+ * @example
+ * mergeQueryStrings('foo=bar', 'foo=baz') => '?foo=bar&baz=qux'
+ * mergeQueryStrings('foo=bar', 'foo=baz') => throws an error
  */
 const mergeQueryStrings = (baseQuery = '', suffixQuery = ''): string => {
     if (!baseQuery && !suffixQuery) {
diff --git a/src/libs/Navigation/helpers/findMatchingDynamicSuffix.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/findMatchingDynamicSuffix.ts
similarity index 100%
rename from src/libs/Navigation/helpers/findMatchingDynamicSuffix.ts
rename to src/libs/Navigation/helpers/dynamicRoutesUtils/findMatchingDynamicSuffix.ts
diff --git a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts
similarity index 82%
rename from src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
rename to src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts
index 5fe23cc3b00f..7bc779408947 100644
--- a/src/libs/Navigation/helpers/getPathWithoutDynamicSuffix.ts
+++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts
@@ -2,6 +2,14 @@ import type {Route} from '@src/ROUTES';
 import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import splitPathAndQuery from './splitPathAndQuery';
 
+/**
+ * Returns the query parameter names that belong to a dynamic route suffix and should be
+ * stripped when removing that suffix from the path. Looks up the matching DYNAMIC_ROUTES
+ * config and returns its `queryParams` array if defined.
+ *
+ * @param dynamicSuffix - The dynamic route path segment (e.g., 'country')
+ * @returns The list of query param keys to strip, or undefined if no match or no queryParams config
+ */
 function getQueryParamsToStrip(dynamicSuffix: string): readonly string[] | undefined {
     const keys = Object.keys(DYNAMIC_ROUTES) as Array<keyof typeof DYNAMIC_ROUTES>;
     const match = keys.find((key) => DYNAMIC_ROUTES[key].path === dynamicSuffix);
diff --git a/src/libs/Navigation/helpers/getStateForDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts
similarity index 100%
rename from src/libs/Navigation/helpers/getStateForDynamicRoute.ts
rename to src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts
diff --git a/src/libs/Navigation/helpers/isDynamicRouteSuffix.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/isDynamicRouteSuffix.ts
similarity index 100%
rename from src/libs/Navigation/helpers/isDynamicRouteSuffix.ts
rename to src/libs/Navigation/helpers/dynamicRoutesUtils/isDynamicRouteSuffix.ts
diff --git a/src/libs/Navigation/helpers/splitPathAndQuery.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/splitPathAndQuery.ts
similarity index 100%
rename from src/libs/Navigation/helpers/splitPathAndQuery.ts
rename to src/libs/Navigation/helpers/dynamicRoutesUtils/splitPathAndQuery.ts
diff --git a/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts b/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts
index 33f7996fb8f4..930862e77ce5 100644
--- a/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts
+++ b/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts
@@ -11,10 +11,10 @@ import NAVIGATORS from '@src/NAVIGATORS';
 import type {Route as RoutePath} from '@src/ROUTES';
 import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
-import findMatchingDynamicSuffix from './findMatchingDynamicSuffix';
+import findMatchingDynamicSuffix from './dynamicRoutesUtils/findMatchingDynamicSuffix';
+import getPathWithoutDynamicSuffix from './dynamicRoutesUtils/getPathWithoutDynamicSuffix';
 import getMatchingNewRoute from './getMatchingNewRoute';
 import getParamsFromRoute from './getParamsFromRoute';
-import getPathWithoutDynamicSuffix from './getPathWithoutDynamicSuffix';
 import getRedirectedPath from './getRedirectedPath';
 import getStateFromPath from './getStateFromPath';
 import {isFullScreenName} from './isNavigatorName';
diff --git a/src/libs/Navigation/helpers/getPathFromState.ts b/src/libs/Navigation/helpers/getPathFromState.ts
index fa48471b6f8d..0362ce72a148 100644
--- a/src/libs/Navigation/helpers/getPathFromState.ts
+++ b/src/libs/Navigation/helpers/getPathFromState.ts
@@ -4,7 +4,7 @@ import {linkingConfig} from '@libs/Navigation/linkingConfig';
 import {normalizedConfigs} from '@libs/Navigation/linkingConfig/config';
 import type {DynamicRouteSuffix} from '@src/ROUTES';
 import type {Screen} from '@src/SCREENS';
-import {dynamicRoutePaths} from './isDynamicRouteSuffix';
+import {dynamicRoutePaths} from './dynamicRoutesUtils/isDynamicRouteSuffix';
 
 type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
 
diff --git a/src/libs/Navigation/helpers/getStateFromPath.ts b/src/libs/Navigation/helpers/getStateFromPath.ts
index ca01d5a6b5fc..92119fdbf228 100644
--- a/src/libs/Navigation/helpers/getStateFromPath.ts
+++ b/src/libs/Navigation/helpers/getStateFromPath.ts
@@ -6,11 +6,11 @@ import type {Route} from '@src/ROUTES';
 import {DYNAMIC_ROUTES} from '@src/ROUTES';
 import type {Screen} from '@src/SCREENS';
 import SCREENS from '@src/SCREENS';
-import findMatchingDynamicSuffix from './findMatchingDynamicSuffix';
+import findMatchingDynamicSuffix from './dynamicRoutesUtils/findMatchingDynamicSuffix';
+import getPathWithoutDynamicSuffix from './dynamicRoutesUtils/getPathWithoutDynamicSuffix';
+import getStateForDynamicRoute from './dynamicRoutesUtils/getStateForDynamicRoute';
 import getMatchingNewRoute from './getMatchingNewRoute';
-import getPathWithoutDynamicSuffix from './getPathWithoutDynamicSuffix';
 import getRedirectedPath from './getRedirectedPath';
-import getStateForDynamicRoute from './getStateForDynamicRoute';
 
 /**
  * @param path - The path to parse
diff --git a/src/libs/Navigation/helpers/linkTo/index.ts b/src/libs/Navigation/helpers/linkTo/index.ts
index c17495466a03..88fedac311ee 100644
--- a/src/libs/Navigation/helpers/linkTo/index.ts
+++ b/src/libs/Navigation/helpers/linkTo/index.ts
@@ -1,7 +1,7 @@
 import {getActionFromState} from '@react-navigation/core';
 import type {NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native';
 import {findFocusedRoute, StackActions} from '@react-navigation/native';
-import findMatchingDynamicSuffix from '@libs/Navigation/helpers/findMatchingDynamicSuffix';
+import findMatchingDynamicSuffix from '@libs/Navigation/helpers/dynamicRoutesUtils/findMatchingDynamicSuffix';
 import {getMatchingFullScreenRoute, isFullScreenName} from '@libs/Navigation/helpers/getAdaptedStateFromPath';
 import getStateFromPath from '@libs/Navigation/helpers/getStateFromPath';
 import normalizePath from '@libs/Navigation/helpers/normalizePath';
diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts
index df9a305b9cab..6ac536e761e0 100644
--- a/src/libs/actions/BankAccounts.ts
+++ b/src/libs/actions/BankAccounts.ts
@@ -24,7 +24,7 @@ import type {SaveCorpayOnboardingCompanyDetails} from '@libs/API/parameters/Save
 import type SaveCorpayOnboardingDirectorInformationParams from '@libs/API/parameters/SaveCorpayOnboardingDirectorInformationParams';
 import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
 import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
-import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import type {MemberForList} from '@libs/OptionsListUtils';
 import {getFormattedStreet} from '@libs/PersonalDetailsUtils';
diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx
index 0b458f0778f1..1cee08078c3d 100644
--- a/src/pages/settings/Wallet/PaymentMethodList.tsx
+++ b/src/pages/settings/Wallet/PaymentMethodList.tsx
@@ -34,7 +34,7 @@ import {
     lastFourNumbersFromCardName,
     maskCardNumber,
 } from '@libs/CardUtils';
-import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import {formatPaymentMethods} from '@libs/PaymentUtils';
 import {getDescriptionForPolicyDomainCard} from '@libs/PolicyUtils';
diff --git a/src/pages/settings/Wallet/WalletPage/index.tsx b/src/pages/settings/Wallet/WalletPage/index.tsx
index 3195f3bb4a73..0048af9b6fbf 100644
--- a/src/pages/settings/Wallet/WalletPage/index.tsx
+++ b/src/pages/settings/Wallet/WalletPage/index.tsx
@@ -36,7 +36,7 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {hasDisplayableAssignedCards, isDirectFeed, maskCardNumber} from '@libs/CardUtils';
 import {convertToDisplayString} from '@libs/CurrencyUtils';
-import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import {formatPaymentMethods, getPaymentMethodDescription} from '@libs/PaymentUtils';
 import {getActiveAdminWorkspaces, getDescriptionForPolicyDomainCard, hasActiveAdminWorkspaces, hasEligibleActiveAdminFromWorkspaces, isPaidGroupPolicy} from '@libs/PolicyUtils';

From 8171ab1e30a1036ca896d293ecbec8a7355f028e Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Mon, 9 Mar 2026 16:52:30 +0100
Subject: [PATCH 07/11] fix errors

---
 tests/navigation/createDynamicRouteTests.ts          | 2 +-
 tests/navigation/findMatchingDynamicSuffixTests.ts   | 2 +-
 tests/navigation/getPathWithoutDynamicSuffixTests.ts | 2 +-
 tests/navigation/getStateForDynamicRouteTests.ts     | 2 +-
 tests/navigation/getStateFromPathTests.ts            | 4 ++--
 tests/navigation/splitPathAndQueryTests.ts           | 2 +-
 6 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/tests/navigation/createDynamicRouteTests.ts b/tests/navigation/createDynamicRouteTests.ts
index 272b692c3b77..9acbfff65972 100644
--- a/tests/navigation/createDynamicRouteTests.ts
+++ b/tests/navigation/createDynamicRouteTests.ts
@@ -1,4 +1,4 @@
-import createDynamicRoute from '@libs/Navigation/helpers/createDynamicRoute';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import Navigation from '@libs/Navigation/Navigation';
 import type {DynamicRouteSuffix} from '@src/ROUTES';
 
diff --git a/tests/navigation/findMatchingDynamicSuffixTests.ts b/tests/navigation/findMatchingDynamicSuffixTests.ts
index a3f42be0c2b9..6baaaf187d4c 100644
--- a/tests/navigation/findMatchingDynamicSuffixTests.ts
+++ b/tests/navigation/findMatchingDynamicSuffixTests.ts
@@ -1,4 +1,4 @@
-import findMatchingDynamicSuffix from '@libs/Navigation/helpers/findMatchingDynamicSuffix';
+import findMatchingDynamicSuffix from '@libs/Navigation/helpers/dynamicRoutesUtils/findMatchingDynamicSuffix';
 
 describe('findMatchingDynamicSuffix', () => {
     it('should match a single-segment dynamic suffix', () => {
diff --git a/tests/navigation/getPathWithoutDynamicSuffixTests.ts b/tests/navigation/getPathWithoutDynamicSuffixTests.ts
index b195474a6d2f..f0b22e6a0ed4 100644
--- a/tests/navigation/getPathWithoutDynamicSuffixTests.ts
+++ b/tests/navigation/getPathWithoutDynamicSuffixTests.ts
@@ -1,4 +1,4 @@
-import getPathWithoutDynamicSuffix from '@libs/Navigation/helpers/getPathWithoutDynamicSuffix';
+import getPathWithoutDynamicSuffix from '@libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix';
 
 describe('getPathWithoutDynamicSuffix', () => {
     it('should remove a single-segment suffix from a simple path', () => {
diff --git a/tests/navigation/getStateForDynamicRouteTests.ts b/tests/navigation/getStateForDynamicRouteTests.ts
index 0c2b36581595..cf3dc7c588f0 100644
--- a/tests/navigation/getStateForDynamicRouteTests.ts
+++ b/tests/navigation/getStateForDynamicRouteTests.ts
@@ -1,4 +1,4 @@
-import getStateForDynamicRoute from '@libs/Navigation/helpers/getStateForDynamicRoute';
+import getStateForDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute';
 import type {DYNAMIC_ROUTES} from '@src/ROUTES';
 
 jest.mock('@libs/Navigation/linkingConfig/config', () => ({
diff --git a/tests/navigation/getStateFromPathTests.ts b/tests/navigation/getStateFromPathTests.ts
index c6bac121f472..ffa5cb2ace84 100644
--- a/tests/navigation/getStateFromPathTests.ts
+++ b/tests/navigation/getStateFromPathTests.ts
@@ -1,6 +1,6 @@
 import {findFocusedRoute, getStateFromPath as RNGetStateFromPath} from '@react-navigation/native';
 import Log from '@libs/Log';
-import getStateForDynamicRoute from '@libs/Navigation/helpers/getStateForDynamicRoute';
+import getStateForDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute';
 import getStateFromPath from '@libs/Navigation/helpers/getStateFromPath';
 import type {Route} from '@src/ROUTES';
 
@@ -30,7 +30,7 @@ jest.mock('@src/ROUTES', () => ({
 
 jest.mock('@libs/Navigation/helpers/getMatchingNewRoute', () => jest.fn());
 jest.mock('@libs/Navigation/helpers/getRedirectedPath', () => jest.fn((path: string) => path));
-jest.mock('@libs/Navigation/helpers/getStateForDynamicRoute', () => jest.fn());
+jest.mock('@libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute', () => jest.fn());
 
 describe('getStateFromPath', () => {
     const mockFindFocusedRoute = findFocusedRoute as jest.Mock;
diff --git a/tests/navigation/splitPathAndQueryTests.ts b/tests/navigation/splitPathAndQueryTests.ts
index a0dba6475e6f..baa585bc695d 100644
--- a/tests/navigation/splitPathAndQueryTests.ts
+++ b/tests/navigation/splitPathAndQueryTests.ts
@@ -1,4 +1,4 @@
-import splitPathAndQuery from '@libs/Navigation/helpers/splitPathAndQuery';
+import splitPathAndQuery from '@libs/Navigation/helpers/dynamicRoutesUtils/splitPathAndQuery';
 
 describe('splitPathAndQuery', () => {
     it('should split path and query', () => {

From 5229db48b03d3dbcabbb1caeedd44a8ababe742c Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Tue, 10 Mar 2026 12:26:54 +0100
Subject: [PATCH 08/11] Add private annotations to internal helper functions in
 dynamic routes utils

---
 .../helpers/dynamicRoutesUtils/createDynamicRoute.ts |  7 +++++++
 .../getPathWithoutDynamicSuffix.ts                   |  2 ++
 .../dynamicRoutesUtils/getStateForDynamicRoute.ts    | 12 ++++++++++--
 3 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts
index 17b8a280f177..5ab1511a9957 100644
--- a/src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts
+++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts
@@ -11,6 +11,8 @@ import splitPathAndQuery from './splitPathAndQuery';
  * @param suffixQuery - The query string of the suffix
  * @returns The merged query string or an empty string if both are empty
  *
+ * @private - Internal helper. Do not export or use outside this file.
+ *
  * @example
  * mergeQueryStrings('foo=bar', 'foo=baz') => '?foo=bar&baz=qux'
  * mergeQueryStrings('foo=bar', 'foo=baz') => throws an error
@@ -32,6 +34,11 @@ const mergeQueryStrings = (baseQuery = '', suffixQuery = ''): string => {
     return result ? `?${result}` : '';
 };
 
+/**
+ * Combines a base path with a dynamic route suffix, merging their query parameters.
+ *
+ * @private - Internal helper. Do not export or use outside this file.
+ */
 const combinePathAndSuffix = (basePath: string, suffixWithQuery: string): Route => {
     const [normalizedBasePath, baseQuery] = splitPathAndQuery(basePath);
     const [suffixPath, suffixQuery] = splitPathAndQuery(suffixWithQuery);
diff --git a/src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts
index 7bc779408947..2382b5cec186 100644
--- a/src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts
+++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts
@@ -9,6 +9,8 @@ import splitPathAndQuery from './splitPathAndQuery';
  *
  * @param dynamicSuffix - The dynamic route path segment (e.g., 'country')
  * @returns The list of query param keys to strip, or undefined if no match or no queryParams config
+ *
+ * @private - Internal helper. Do not export or use outside this file.
  */
 function getQueryParamsToStrip(dynamicSuffix: string): readonly string[] | undefined {
     const keys = Object.keys(DYNAMIC_ROUTES) as Array<keyof typeof DYNAMIC_ROUTES>;
diff --git a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts
index c7b8127eb739..ce35dc567c20 100644
--- a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts
+++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts
@@ -21,6 +21,11 @@ type RouteNode = LeafRoute | NestedRoute;
 
 const configEntries = Object.entries(normalizedConfigs);
 
+/**
+ * Parses a query string into a key-value record.
+ *
+ * @private - Internal helper. Do not export or use outside this file.
+ */
 function getParamsFromQuery(query: string | undefined): Record<string, string> | undefined {
     if (!query) {
         return undefined;
@@ -34,9 +39,12 @@ function getParamsFromQuery(query: string | undefined): Record<string, string> |
     return Object.fromEntries(entries);
 }
 
+/**
+ * Looks up the navigation screen hierarchy (routeNames) for a given dynamic route suffix.
+ *
+ * @private - Internal helper. Do not export or use outside this file.
+ */
 function getRouteNamesForDynamicRoute(dynamicRouteName: DynamicRouteSuffix): string[] | null {
-    // Search through normalized configs to find matching path and extract navigation hierarchy
-    // routeNames contains the sequence of screen/navigator names that should be present in the navigation state
     for (const [, config] of configEntries) {
         if (config.path === dynamicRouteName) {
             return config.routeNames;

From 2f9cd2f6a2268805f0f7f3fe861f632dc8bdb8ba Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Tue, 10 Mar 2026 13:06:41 +0100
Subject: [PATCH 09/11] Enhance navigation documentation by adding sections on
 multi-segment dynamic routes and dynamic routes with query parameters

---
 contributingGuides/NAVIGATION.md | 120 ++++++++++++++++++++++++++++---
 1 file changed, 110 insertions(+), 10 deletions(-)

diff --git a/contributingGuides/NAVIGATION.md b/contributingGuides/NAVIGATION.md
index 35330ed35271..9cee8c3b9f0a 100644
--- a/contributingGuides/NAVIGATION.md
+++ b/contributingGuides/NAVIGATION.md
@@ -26,6 +26,8 @@ The navigation in the app is built on top of the `react-navigation` library. To
     - [Dynamic routes configuration](#dynamic-routes-configuration)
     - [Entry screens (access control)](#entry-screens-access-control)
     - [Current limitations (work in progress)](#current-limitations-work-in-progress)
+    - [Multi-segment dynamic routes](#multi-segment-dynamic-routes)
+    - [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters)
     - [How to add a new dynamic route](#how-to-add-a-new-dynamic-route)
     - [Migrating from backTo to dynamic routes](#migrating-from-backto-to-dynamic-routes)
   - [How to remove backTo from URL (Legacy)](#how-to-remove-backto-from-url)
@@ -731,21 +733,119 @@ When adding or extending a dynamic route, list every screen that should be able
 ### Current limitations (work in progress)
 
 - **Stacking:** Multiple dynamic route suffixes on top of each other (e.g. `/a/verify-account/another-flow`) are not supported. Only one dynamic suffix per path is allowed.
-- **Suffix shape:** Suffixes must be a single path segment. Compound suffixes with extra path segments (e.g. `a/b`) are not supported.
-- **Parameters:** Suffixes must not include path params (e.g. `a/:reportID`) or query params (e.g. `a?foo=bar`). Use a single literal segment like `verify-account` only.
+- **Path parameters:** Suffixes must not include path params (e.g. `a/:reportID`). Query parameters are supported - see [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters).
 
 If you try to use dynamic routes for these cases now, you will either fail to navigate to the page at all or end up on a non-existent page, and the navigation will be broken.
 
-### How to add a new dynamic route
+### Multi-segment dynamic routes
+
+Dynamic route suffixes are not limited to a single path segment -
+they can span multiple segments separated by `/`.
+For example, the suffix `add-bank-account/verify-account` is a valid
+multi-segment suffix that combines two segments into one dynamic route.
+
+When the URL is parsed, the matching algorithm
+iterates from the longest candidate suffix to the shortest,
+so overlapping registrations are resolved deterministically.
+For instance, if both `verify-account` and `add-bank-account/verify-account`
+are registered, a path ending with `/add-bank-account/verify-account`
+will always match the longer, more specific suffix.
+
+### Dynamic routes with query parameters
+
+Dynamic route suffixes can carry query parameters
+(e.g. `country?country=US`).
+This is useful when a dynamic screen needs initial data passed
+through the URL - for example, a country selector that
+pre-selects the current country.
+
+#### How to add query parameters to a dynamic route
+
+1. In `DYNAMIC_ROUTES` (in [`src/ROUTES.ts`](../src/ROUTES.ts)), add a `getRoute` function
+that returns the suffix with query parameters and a `queryParams`
+array listing every parameter key that the suffix may add:
+
+```ts
+ADDRESS_COUNTRY: {
+    path: 'country',
+    entryScreens: [SCREENS.SETTINGS.PROFILE.ADDRESS, /* ... */],
+    getRoute: (country = '') => `country${country ? `?country=${country}` : ''}`,
+    queryParams: ['country'],
+},
+```
 
-1. Add to `DYNAMIC_ROUTES` in `src/ROUTES.ts`: define `path` and `entryScreens` (screen names that may open this route).
-2. Add a screen constant in `src/SCREENS.ts`. The name must start with the `DYNAMIC_` prefix (e.g. `SETTINGS.DYNAMIC_VERIFY_ACCOUNT`) so dynamic screens can be distinguished from static ones.
-3. Register in linking config in `src/libs/Navigation/linkingConfig/config.ts`: map the new screen to `DYNAMIC_ROUTES.<KEY>.path`.
-4. Implement the page component: Use `createDynamicRoute(DYNAMIC_ROUTES.<KEY>.path)` to navigate to the flow and `useDynamicBackPath(DYNAMIC_ROUTES.<KEY>.path)` to get the back path. Pass these into your base UI (e.g. `VerifyAccountPageBase` with `navigateBackTo` / `navigateForwardTo`).
-5. Register the screen in the appropriate modal/stack navigator (e.g. `src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx`).
-6. Types: Add the screen to the navigator param list in `src/libs/Navigation/types.ts` (no params for the new screen).
+2. Navigate using `createDynamicRoute` with the output of `getRoute`:
+
+```ts
+Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.ADDRESS_COUNTRY.getRoute(countryCode)));
+// Produces e.g. /settings/profile/address/country?country=US
+```
+
+3. In the page component, read query params from `route.params` as usual:
+
+```ts
+const currentCountry = route.params?.country ?? '';
+```
+
+> [!CAUTION]
+> **`queryParams` array is mandatory.**
+> When you define a `getRoute` function that adds query parameters
+> after `?` in a dynamic route, you **must** also define a `queryParams`
+> array containing **every** parameter key that `getRoute` may produce.
+> The `queryParams` array is used to strip suffix-specific parameters when navigating back.
+> Without it, query parameters will leak into the parent path
+> and break back navigation.
+
+> [!CAUTION]
+> **Query parameter names must not collide with inherited entry screen parameters.**
+> Do not use a query parameter name in a dynamic suffix that
+> already exists as a parameter of any entry screen listed
+> in `entryScreens`.
+> For example, if an entry screen has a `country` query parameter,
+> do not add `country` as a query parameter in your dynamic route.
+> When both paths carry the same parameter key,
+> `createDynamicRoute` will throw an error:
+> `Query param "X" exists in both base path and dynamic suffix.`
+> `This is not allowed.`
+> This guard prevents non-deterministic parameter handling
+> and accidental overwriting of inherited parameters.
+
+The Address Country flow is the reference implementation for
+dynamic routes with query parameters:
+see [`src/components/CountrySelector.tsx`](../src/components/CountrySelector.tsx) (navigation call)
+and [`src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx`](../src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx)
+(page component).
+
+### How to add a new dynamic route
 
-The Verify Account flow (Wallet → Dynamic Verify Account) is the reference implementation: see `src/pages/settings/DynamicVerifyAccountPage.tsx` and the Wallet entry point in `src/pages/settings/Wallet/WalletPage/index.tsx`.
+1. Add to `DYNAMIC_ROUTES` in [`src/ROUTES.ts`](../src/ROUTES.ts): define `path` and
+`entryScreens` (screen names that may open this route).
+If the suffix needs query parameters, also define `getRoute`
+and `queryParams` - see
+[Dynamic routes with query parameters](#dynamic-routes-with-query-parameters).
+2. Add a screen constant in [`src/SCREENS.ts`](../src/SCREENS.ts).
+The name must start with the `DYNAMIC_` prefix
+(e.g. `SETTINGS.DYNAMIC_VERIFY_ACCOUNT`) so dynamic screens
+can be distinguished from static ones.
+3. Register in linking config in
+[`src/libs/Navigation/linkingConfig/config.ts`](../src/libs/Navigation/linkingConfig/config.ts):
+map the new screen to `DYNAMIC_ROUTES.<KEY>.path`.
+4. Implement the page component:
+Use `createDynamicRoute(DYNAMIC_ROUTES.<KEY>.path)` to navigate
+to the flow and `useDynamicBackPath(DYNAMIC_ROUTES.<KEY>.path)`
+to get the back path. Pass these into your base UI
+(e.g. `VerifyAccountPageBase` with `navigateBackTo` /
+`navigateForwardTo`).
+5. Register the screen in the appropriate modal/stack navigator
+(e.g. [`src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx`](../src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx)).
+6. Types: Add the screen to the navigator param list in
+[`src/libs/Navigation/types.ts`](../src/libs/Navigation/types.ts) (no params for the new screen).
+
+The Verify Account flow (Wallet → Dynamic Verify Account) is the
+reference implementation:
+see [`src/pages/settings/DynamicVerifyAccountPage.tsx`](../src/pages/settings/DynamicVerifyAccountPage.tsx) and the
+Wallet entry point in
+[`src/pages/settings/Wallet/WalletPage/index.tsx`](../src/pages/settings/Wallet/WalletPage/index.tsx).
 
 ### Migrating from backTo to dynamic routes
 

From afd42b6fad4307fba88c5f4c7d9a1855391aa1e6 Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Wed, 11 Mar 2026 15:39:42 +0100
Subject: [PATCH 10/11] fix error when country param is left in url without
 value

---
 .../Profile/PersonalDetails/DynamicCountrySelectionPage.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
index df253c624ab8..9b5bb119c60f 100644
--- a/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
@@ -68,7 +68,7 @@ function DynamicCountrySelectionPage({route}: DynamicCountrySelectionPageProps)
                 title={translate('common.country')}
                 shouldShowBackButton
                 onBackButtonPress={() => {
-                    Navigation.goBack(appendParam(backPath, 'country', currentCountry), {compareParams: false});
+                    Navigation.goBack(currentCountry ? appendParam(backPath, 'country', currentCountry) : (backPath as Route), {compareParams: false});
                 }}
             />
 

From 70807267fad3461b81fa023e752264872d0b0646 Mon Sep 17 00:00:00 2001
From: Yehor Kharchenko <yehorkharchenko4@gmail.com>
Date: Wed, 11 Mar 2026 16:28:23 +0100
Subject: [PATCH 11/11] Update dynamic route handling and navigation parameters
 for country selection to fix bugs

---
 src/ROUTES.ts                                                 | 2 +-
 .../Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts     | 1 +
 .../Profile/PersonalDetails/DynamicCountrySelectionPage.tsx   | 4 ++--
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 1d21a4cb0b4a..49466bce956e 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -109,7 +109,7 @@ const DYNAMIC_ROUTES = {
             SCREENS.TRAVEL.WORKSPACE_ADDRESS,
             SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT,
         ],
-        getRoute: (country = '') => `country${country ? `?country=${country}` : ''}`,
+        getRoute: (country = '') => `country?country=${country}`,
         queryParams: ['country'],
     },
 } as const satisfies DynamicRoutes;
diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
index 44b4a085a26c..36e20c9b14ad 100755
--- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
+++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
@@ -59,6 +59,7 @@ const SETTINGS_TO_RHP: Partial<Record<keyof SettingsSplitNavigatorParamList, str
         SCREENS.SETTINGS.REPORT_CARD_LOST_OR_DAMAGED_CONFIRM_MAGIC_CODE,
         SCREENS.SETTINGS.WALLET.PERSONAL_CARD_WARNING,
         SCREENS.SETTINGS.WALLET.PERSONAL_CARD_UPGRADE,
+        SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT,
     ],
     [SCREENS.SETTINGS.RULES.ROOT]: [
         SCREENS.SETTINGS.RULES.ADD,
diff --git a/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
index 9b5bb119c60f..e83eecb21dd9 100644
--- a/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx
@@ -22,7 +22,7 @@ type DynamicCountrySelectionPageProps = PlatformStackScreenProps<SettingsNavigat
 function DynamicCountrySelectionPage({route}: DynamicCountrySelectionPageProps) {
     const [searchValue, setSearchValue] = useState('');
     const {translate} = useLocalize();
-    const currentCountry = route.params?.country ?? '';
+    const currentCountry = route.params.country;
     const backPath = useDynamicBackPath(DYNAMIC_ROUTES.ADDRESS_COUNTRY.path);
 
     const countries = useMemo(
@@ -68,7 +68,7 @@ function DynamicCountrySelectionPage({route}: DynamicCountrySelectionPageProps)
                 title={translate('common.country')}
                 shouldShowBackButton
                 onBackButtonPress={() => {
-                    Navigation.goBack(currentCountry ? appendParam(backPath, 'country', currentCountry) : (backPath as Route), {compareParams: false});
+                    Navigation.goBack(currentCountry ? appendParam(backPath, 'country', currentCountry) : backPath, {compareParams: false});
                 }}
             />
 
