diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue
index 197eeb0a3b6c66ea94b6b70e632484ca69078490..4ba2bc3a8e3f3b85653c4f148818c3642ba39bf2 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -288,6 +288,7 @@ export default {
           icon: 'epic',
           token: EpicToken,
           unique: true,
+          idProperty: 'id',
           fetchEpics: this.fetchEpics,
         });
       }
@@ -320,13 +321,23 @@ export default {
       );
     },
     urlParams() {
+      const filterParams = {
+        ...this.urlFilterParams,
+      };
+
+      if (filterParams.epic_id) {
+        filterParams.epic_id = encodeURIComponent(filterParams.epic_id);
+      } else if (filterParams['not[epic_id]']) {
+        filterParams['not[epic_id]'] = encodeURIComponent(filterParams['not[epic_id]']);
+      }
+
       return {
         due_date: this.dueDateFilter,
         page: this.page,
         search: this.searchQuery,
         state: this.state,
         ...urlSortParams[this.sortKey],
-        ...this.urlFilterParams,
+        ...filterParams,
       };
     },
   },
@@ -358,7 +369,7 @@ export default {
     fetchEmojis(search) {
       return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search);
     },
-    async fetchEpics(search) {
+    async fetchEpics({ search }) {
       const epics = await this.fetchWithCache(this.groupEpicsPath, 'epics');
       if (!search) {
         return epics.slice(0, MAX_LIST_SIZE);
@@ -387,6 +398,16 @@ export default {
 
       this.isLoading = true;
 
+      const filterParams = {
+        ...this.apiFilterParams,
+      };
+
+      if (filterParams.epic_id) {
+        filterParams.epic_id = filterParams.epic_id.split('::&').pop();
+      } else if (filterParams['not[epic_id]']) {
+        filterParams['not[epic_id]'] = filterParams['not[epic_id]'].split('::&').pop();
+      }
+
       return axios
         .get(this.endpoint, {
           params: {
@@ -397,7 +418,7 @@ export default {
             state: this.state,
             with_labels_details: true,
             ...apiSortParams[this.sortKey],
-            ...this.apiFilterParams,
+            ...filterParams,
           },
         })
         .then(({ data, headers }) => {
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
index 1450807b11dc13189beea9e47334f59fb5fb5d29..d21fa9a344a3778c39773bacbfb0af3ee0c232df 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue
@@ -11,6 +11,7 @@ import { __ } from '~/locale';
 import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants';
 
 export default {
+  separator: '::&',
   components: {
     GlDropdownDivider,
     GlFilteredSearchToken,
@@ -34,17 +35,35 @@ export default {
     };
   },
   computed: {
+    idProperty() {
+      return this.config.idProperty || 'iid';
+    },
     currentValue() {
-      return Number(this.value.data);
+      const epicIid = Number(this.value.data);
+      if (epicIid) {
+        return epicIid;
+      }
+      return this.value.data;
     },
     defaultEpics() {
       return this.config.defaultEpics || DEFAULT_NONE_ANY;
     },
-    idProperty() {
-      return this.config.idProperty || 'id';
-    },
     activeEpic() {
-      return this.epics.find((epic) => epic[this.idProperty] === this.currentValue);
+      if (this.currentValue && this.epics.length) {
+        // Check if current value is an epic ID.
+        if (typeof this.currentValue === 'number') {
+          return this.epics.find((epic) => epic[this.idProperty] === this.currentValue);
+        }
+
+        // Current value is a string.
+        const [groupPath, idProperty] = this.currentValue?.split('::&');
+        return this.epics.find(
+          (epic) =>
+            epic.group_full_path === groupPath &&
+            epic[this.idProperty] === parseInt(idProperty, 10),
+        );
+      }
+      return null;
     },
   },
   watch: {
@@ -58,10 +77,10 @@ export default {
     },
   },
   methods: {
-    fetchEpicsBySearchTerm(searchTerm = '') {
+    fetchEpicsBySearchTerm({ epicPath = '', search = '' }) {
       this.loading = true;
       this.config
-        .fetchEpics(searchTerm)
+        .fetchEpics({ epicPath, search })
         .then((response) => {
           this.epics = Array.isArray(response) ? response : response.data;
         })
@@ -71,11 +90,21 @@ export default {
         });
     },
     searchEpics: debounce(function debouncedSearch({ data }) {
-      this.fetchEpicsBySearchTerm(data);
+      let epicPath = this.activeEpic?.web_url;
+
+      // When user visits the page with token value already included in filters
+      // We don't have any information about selected token except for its
+      // group path and iid joined by separator, so we need to manually
+      // compose epic path from it.
+      if (data.includes(this.$options.separator)) {
+        const [groupPath, epicIid] = data.split(this.$options.separator);
+        epicPath = `/groups/${groupPath}/-/epics/${epicIid}`;
+      }
+      this.fetchEpicsBySearchTerm({ epicPath, search: data });
     }, DEBOUNCE_DELAY),
 
     getEpicDisplayText(epic) {
-      return `${epic.title}::&${epic[this.idProperty]}`;
+      return `${epic.title}${this.$options.separator}${epic.iid}`;
     },
   },
 };
@@ -104,8 +133,8 @@ export default {
       <template v-else>
         <gl-filtered-search-suggestion
           v-for="epic in epics"
-          :key="epic[idProperty]"
-          :value="String(epic[idProperty])"
+          :key="epic.id"
+          :value="`${epic.group_full_path}::&${epic[idProperty]}`"
         >
           {{ epic.title }}
         </gl-filtered-search-suggestion>
diff --git a/ee/app/assets/javascripts/epics_list/components/epics_list_root.vue b/ee/app/assets/javascripts/epics_list/components/epics_list_root.vue
index 115dd48eaf180a0f28171c9da51df19b29d91dd9..6690f91fbe6c0e80ba644f7ad39b4574bba49d16 100644
--- a/ee/app/assets/javascripts/epics_list/components/epics_list_root.vue
+++ b/ee/app/assets/javascripts/epics_list/components/epics_list_root.vue
@@ -207,7 +207,7 @@ export default {
     :current-tab="currentState"
     :tab-counts="epicsCount"
     :search-input-placeholder="__('Search or filter results...')"
-    :search-tokens="getFilteredSearchTokens()"
+    :search-tokens="getFilteredSearchTokens({ supportsEpic: false })"
     :sort-options="$options.EpicsSortOptions"
     :initial-filter-value="getFilteredSearchValue()"
     :initial-sort-by="sortedBy"
diff --git a/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js b/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js
index 278525b6b01e5d561fa39fb4bfd2a35754cda605..83922a069e513bc114b8b0ebfed575f5f1d0d35a 100644
--- a/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js
+++ b/ee/app/assets/javascripts/roadmap/mixins/filtered_search_mixin.js
@@ -36,13 +36,13 @@ export default {
         milestone_title: milestoneTitle,
         confidential,
         my_reaction_emoji: myReactionEmoji,
-        epic_iid: epicIid && Number(epicIid),
+        epic_iid: epicIid,
         search,
       };
     },
   },
   methods: {
-    getFilteredSearchTokens() {
+    getFilteredSearchTokens({ supportsEpic = true } = {}) {
       const tokens = [
         {
           type: 'author_username',
@@ -113,7 +113,10 @@ export default {
             { icon: 'eye', value: false, title: __('No') },
           ],
         },
-        {
+      ];
+
+      if (supportsEpic) {
+        tokens.push({
           type: 'epic_iid',
           icon: 'epic',
           title: __('Epic'),
@@ -121,16 +124,28 @@ export default {
           symbol: '&',
           token: EpicToken,
           operators: OPERATOR_IS_ONLY,
-          idProperty: 'iid',
           defaultEpics: [],
-          fetchEpics: (search = '') => {
-            const number = Number(search);
-            return !search || Number.isNaN(number)
-              ? axios.get(this.listEpicsPath, { params: { search } })
-              : axios.get(joinPaths(this.listEpicsPath, search)).then(({ data }) => [data]);
+          fetchEpics: ({ epicPath = '', search = '' }) => {
+            const epicId = Number(search) || null;
+
+            // No search criteria or path has been provided, fetch all epics.
+            if (!epicPath && !search) {
+              return axios.get(this.listEpicsPath);
+            } else if (epicPath) {
+              // Just epicPath has been provided, fetch a specific epic.
+              return axios.get(epicPath).then(({ data }) => [data]);
+            } else if (!epicPath && epicId) {
+              // Exact epic ID provided, fetch the epic.
+              return axios
+                .get(joinPaths(this.listEpicsPath, String(epicId)))
+                .then(({ data }) => [data]);
+            }
+
+            // Search for an epic.
+            return axios.get(this.listEpicsPath, { params: { search } });
           },
-        },
-      ];
+        });
+      }
 
       if (gon.current_user_id) {
         // Appending to tokens only when logged-in
diff --git a/ee/app/assets/javascripts/roadmap/roadmap_bundle.js b/ee/app/assets/javascripts/roadmap/roadmap_bundle.js
index 2e4a02d42402eb2cc3506e97fab3f0d55f418db5..eef74233f7ab42e58850dec5bf4f11bc5ff99745 100644
--- a/ee/app/assets/javascripts/roadmap/roadmap_bundle.js
+++ b/ee/app/assets/javascripts/roadmap/roadmap_bundle.js
@@ -79,7 +79,7 @@ export default () => {
         }),
 
         ...(rawFilterParams.epicIid && {
-          epicIid: parseInt(rawFilterParams.epicIid, 10),
+          epicIid: rawFilterParams.epicIid,
         }),
       };
       const timeframe = getTimeframeForPreset(
diff --git a/ee/app/assets/javascripts/roadmap/store/actions.js b/ee/app/assets/javascripts/roadmap/store/actions.js
index ef76d93ca2ca34d413689cf56dbbb214f36ef76c..2b6d380ec2dad8715065147b79ec973e793c6369 100644
--- a/ee/app/assets/javascripts/roadmap/store/actions.js
+++ b/ee/app/assets/javascripts/roadmap/store/actions.js
@@ -47,7 +47,7 @@ const fetchGroupEpics = (
     };
 
     if (filterParams?.epicIid) {
-      variables.iid = filterParams.epicIid;
+      variables.iid = filterParams.epicIid.split('::&').pop();
     }
   }
 
diff --git a/ee/app/serializers/epic_entity.rb b/ee/app/serializers/epic_entity.rb
index 1b8b0201a957d21e08837af792e60398c4afdaa2..5947f74a7044b7efd5c92996224469334f15c802 100644
--- a/ee/app/serializers/epic_entity.rb
+++ b/ee/app/serializers/epic_entity.rb
@@ -9,6 +9,9 @@ class EpicEntity < IssuableEntity
   expose :group_full_name do |epic|
     epic.group.full_name
   end
+  expose :group_full_path do |epic|
+    epic.group.full_path
+  end
 
   expose :start_date
   expose :start_date_is_fixed?, as: :start_date_is_fixed
diff --git a/ee/spec/fixtures/api/schemas/entities/epic.json b/ee/spec/fixtures/api/schemas/entities/epic.json
index b5be8af20d1f8336245b3ba5265a3ea6dc77d50e..3d076dd692a2da43002f5ef158433c69a31f0868 100644
--- a/ee/spec/fixtures/api/schemas/entities/epic.json
+++ b/ee/spec/fixtures/api/schemas/entities/epic.json
@@ -22,6 +22,7 @@
     "labels": { "type": ["array", "null"] },
     "group_name": { "type": "string" },
     "group_full_name": { "type": "string" },
+    "group_full_path": { "type": "string" },
     "current_user": {
       "can_create_note": { "type": "boolean" }
     },
@@ -49,6 +50,7 @@
     "labels",
     "group_name",
     "group_full_name",
+    "group_full_path",
     "current_user",
     "create_note_path",
     "preview_note_path"
diff --git a/ee/spec/frontend/epics_list/components/epics_list_root_spec.js b/ee/spec/frontend/epics_list/components/epics_list_root_spec.js
index 974673d0bacae8167b4cedb8e2d0699e7835e209..b1f7a9a86cc97a9c5c645d39ab42c06255b1a2d6 100644
--- a/ee/spec/frontend/epics_list/components/epics_list_root_spec.js
+++ b/ee/spec/frontend/epics_list/components/epics_list_root_spec.js
@@ -170,6 +170,7 @@ describe('EpicsListRoot', () => {
     const getIssuableList = () => wrapper.find(IssuableList);
 
     it('renders issuable-list component', async () => {
+      jest.spyOn(wrapper.vm, 'getFilteredSearchTokens');
       wrapper.setData({
         filterParams: {
           search: 'foo',
@@ -192,6 +193,10 @@ describe('EpicsListRoot', () => {
         issuableSymbol: '&',
         recentSearchesStorageKey: 'epics',
       });
+
+      expect(wrapper.vm.getFilteredSearchTokens).toHaveBeenCalledWith({
+        supportsEpic: false,
+      });
     });
 
     it.each`
diff --git a/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js b/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js
index 86815972eec79847bafe8b7f86826f5550545602..642a5d42023c6b43a4a471599bc246c90a868ff7 100644
--- a/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js
+++ b/ee/spec/frontend/roadmap/components/roadmap_filters_spec.js
@@ -218,7 +218,6 @@ describe('RoadmapFilters', () => {
           symbol: '&',
           token: EpicToken,
           operators,
-          idProperty: 'iid',
           defaultEpics: [],
           fetchEpics: expect.any(Function),
         },
diff --git a/ee/spec/serializers/epic_entity_spec.rb b/ee/spec/serializers/epic_entity_spec.rb
index 5f17c0570ef4552826c488d7963fb69759a48f8b..048be8586a0dce6c07eee66fb112ece67e8c9892 100644
--- a/ee/spec/serializers/epic_entity_spec.rb
+++ b/ee/spec/serializers/epic_entity_spec.rb
@@ -16,6 +16,6 @@
   end
 
   it 'has epic specific attributes' do
-    expect(subject).to include(:start_date, :end_date, :group_id, :group_name, :group_full_name, :web_url)
+    expect(subject).to include(:start_date, :end_date, :group_id, :group_name, :group_full_name, :group_full_path, :web_url)
   end
 end
diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js
index ce2880d177a46466314a144cc731c456679982f1..99267fb6e3102429784d1762326b47f63fdaf536 100644
--- a/spec/frontend/issues_list/mock_data.js
+++ b/spec/frontend/issues_list/mock_data.js
@@ -21,8 +21,8 @@ export const locationSearch = [
   'confidential=no',
   'iteration_title=season:+%234',
   'not[iteration_title]=season:+%2320',
-  'epic_id=12',
-  'not[epic_id]=34',
+  'epic_id=gitlab-org%3A%3A%2612',
+  'not[epic_id]=gitlab-org%3A%3A%2634',
   'weight=1',
   'not[weight]=3',
 ].join('&');
@@ -53,8 +53,8 @@ export const filteredTokens = [
   { type: 'confidential', value: { data: 'no', operator: OPERATOR_IS } },
   { type: 'iteration', value: { data: 'season: #4', operator: OPERATOR_IS } },
   { type: 'iteration', value: { data: 'season: #20', operator: OPERATOR_IS_NOT } },
-  { type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } },
-  { type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } },
+  { type: 'epic_id', value: { data: 'gitlab-org::&12', operator: OPERATOR_IS } },
+  { type: 'epic_id', value: { data: 'gitlab-org::&34', operator: OPERATOR_IS_NOT } },
   { type: 'weight', value: { data: '1', operator: OPERATOR_IS } },
   { type: 'weight', value: { data: '3', operator: OPERATOR_IS_NOT } },
   { type: 'filtered-search-term', value: { data: 'find' } },
@@ -84,7 +84,7 @@ export const apiParams = {
   iteration_title: 'season: #4',
   'not[iteration_title]': 'season: #20',
   epic_id: '12',
-  'not[epic_id]': '34',
+  'not[epic_id]': 'gitlab-org::&34',
   weight: '1',
   'not[weight]': '3',
 };
@@ -111,8 +111,8 @@ export const urlParams = {
   confidential: 'no',
   iteration_title: 'season: #4',
   'not[iteration_title]': 'season: #20',
-  epic_id: '12',
-  'not[epic_id]': '34',
+  epic_id: 'gitlab-org%3A%3A%2612',
+  'not[epic_id]': 'gitlab-org::&34',
   weight: '1',
   'not[weight]': '3',
 };
diff --git a/spec/frontend/issues_list/utils_spec.js b/spec/frontend/issues_list/utils_spec.js
index 17127753972c4f62e0570445b318402b726dba90..e377c35a0aa4bac85aae7a0c3dbc9012eb68d722 100644
--- a/spec/frontend/issues_list/utils_spec.js
+++ b/spec/frontend/issues_list/utils_spec.js
@@ -82,7 +82,10 @@ describe('getFilterTokens', () => {
 
 describe('convertToParams', () => {
   it('returns api params given filtered tokens', () => {
-    expect(convertToParams(filteredTokens, API_PARAM)).toEqual(apiParams);
+    expect(convertToParams(filteredTokens, API_PARAM)).toEqual({
+      ...apiParams,
+      epic_id: 'gitlab-org::&12',
+    });
   });
 
   it('returns api params given filtered tokens with special values', () => {
@@ -92,7 +95,10 @@ describe('convertToParams', () => {
   });
 
   it('returns url params given filtered tokens', () => {
-    expect(convertToParams(filteredTokens, URL_PARAM)).toEqual(urlParams);
+    expect(convertToParams(filteredTokens, URL_PARAM)).toEqual({
+      ...urlParams,
+      epic_id: 'gitlab-org::&12',
+    });
   });
 
   it('returns url params given filtered tokens with special values', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
index 23e4deab9c1d78450a0a0001619992c5ab4cf221..134c6c8b929561687f21cd2bb5135c80ca5674ef 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
@@ -65,8 +65,8 @@ export const mockMilestones = [
 ];
 
 export const mockEpics = [
-  { iid: 1, id: 1, title: 'Foo' },
-  { iid: 2, id: 2, title: 'Bar' },
+  { iid: 1, id: 1, title: 'Foo', group_full_path: 'gitlab-org' },
+  { iid: 2, id: 2, title: 'Bar', group_full_path: 'gitlab-org/design' },
 ];
 
 export const mockEmoji1 = {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js
index addc058f65810fc2f4976a0407cf3980116bdd4d..68ed46fc3a21744f61fccbab880937eb49b9b5d8 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js
@@ -67,18 +67,6 @@ describe('EpicToken', () => {
 
       await wrapper.vm.$nextTick();
     });
-
-    describe('activeEpic', () => {
-      it('returns object for currently present `value.data`', async () => {
-        wrapper.setProps({
-          value: { data: `${mockEpics[0].iid}` },
-        });
-
-        await wrapper.vm.$nextTick();
-
-        expect(wrapper.vm.activeEpic).toEqual(mockEpics[0]);
-      });
-    });
   });
 
   describe('methods', () => {
@@ -86,9 +74,12 @@ describe('EpicToken', () => {
       it('calls `config.fetchEpics` with provided searchTerm param', () => {
         jest.spyOn(wrapper.vm.config, 'fetchEpics');
 
-        wrapper.vm.fetchEpicsBySearchTerm('foo');
+        wrapper.vm.fetchEpicsBySearchTerm({ search: 'foo' });
 
-        expect(wrapper.vm.config.fetchEpics).toHaveBeenCalledWith('foo');
+        expect(wrapper.vm.config.fetchEpics).toHaveBeenCalledWith({
+          epicPath: '',
+          search: 'foo',
+        });
       });
 
       it('sets response to `epics` when request is successful', async () => {
@@ -96,7 +87,7 @@ describe('EpicToken', () => {
           data: mockEpics,
         });
 
-        wrapper.vm.fetchEpicsBySearchTerm();
+        wrapper.vm.fetchEpicsBySearchTerm({});
 
         await waitForPromises();
 
@@ -106,7 +97,7 @@ describe('EpicToken', () => {
       it('calls `createFlash` with flash error message when request fails', async () => {
         jest.spyOn(wrapper.vm.config, 'fetchEpics').mockRejectedValue({});
 
-        wrapper.vm.fetchEpicsBySearchTerm('foo');
+        wrapper.vm.fetchEpicsBySearchTerm({ search: 'foo' });
 
         await waitForPromises();
 
@@ -118,7 +109,7 @@ describe('EpicToken', () => {
       it('sets `loading` to false when request completes', async () => {
         jest.spyOn(wrapper.vm.config, 'fetchEpics').mockRejectedValue({});
 
-        wrapper.vm.fetchEpicsBySearchTerm('foo');
+        wrapper.vm.fetchEpicsBySearchTerm({ search: 'foo' });
 
         await waitForPromises();
 
@@ -128,9 +119,11 @@ describe('EpicToken', () => {
   });
 
   describe('template', () => {
+    const getTokenValueEl = () => wrapper.findAllComponents(GlFilteredSearchTokenSegment).at(2);
+
     beforeEach(async () => {
       wrapper = createComponent({
-        value: { data: `${mockEpics[0].iid}` },
+        value: { data: `${mockEpics[0].group_full_path}::&${mockEpics[0].iid}` },
         data: { epics: mockEpics },
       });
 
@@ -147,5 +140,19 @@ describe('EpicToken', () => {
       expect(tokenSegments).toHaveLength(3);
       expect(tokenSegments.at(2).text()).toBe(`${mockEpics[0].title}::&${mockEpics[0].iid}`);
     });
+
+    it.each`
+      value                                                      | valueType   | tokenValueString
+      ${`${mockEpics[0].group_full_path}::&${mockEpics[0].iid}`} | ${'string'} | ${`${mockEpics[0].title}::&${mockEpics[0].iid}`}
+      ${`${mockEpics[1].group_full_path}::&${mockEpics[1].iid}`} | ${'number'} | ${`${mockEpics[1].title}::&${mockEpics[1].iid}`}
+    `('renders token item when selection is a $valueType', async ({ value, tokenValueString }) => {
+      wrapper.setProps({
+        value: { data: value },
+      });
+
+      await wrapper.vm.$nextTick();
+
+      expect(getTokenValueEl().text()).toBe(tokenValueString);
+    });
   });
 });