diff --git a/app/assets/javascripts/api/projects_api.js b/app/assets/javascripts/api/projects_api.js
index c72a913aacd3172dac21748f758df0ef9a5a4d61..35b18c5f56d2cc12e12d810ff528da4387063154 100644
--- a/app/assets/javascripts/api/projects_api.js
+++ b/app/assets/javascripts/api/projects_api.js
@@ -8,6 +8,7 @@ const PROJECT_ALL_MEMBERS_PATH = '/api/:version/projects/:id/members/all';
 const PROJECT_IMPORT_MEMBERS_PATH = '/api/:version/projects/:id/import_project_members/:project_id';
 const PROJECT_REPOSITORY_SIZE_PATH = '/api/:version/projects/:id/repository_size';
 const PROJECT_TRANSFER_LOCATIONS_PATH = 'api/:version/projects/:id/transfer_locations';
+const PROJECT_SHARE_LOCATIONS_PATH = 'api/:version/projects/:id/share_locations';
 
 export function getProjects(query, options, callback = () => {}) {
   const url = buildApiUrl(PROJECTS_PATH);
@@ -70,3 +71,10 @@ export const getProjectMembers = (projectId, inherited = false) => {
 
   return axios.get(url);
 };
+
+export const getProjectShareLocations = (projectId, params = {}, axiosOptions = {}) => {
+  const url = buildApiUrl(PROJECT_SHARE_LOCATIONS_PATH).replace(':id', projectId);
+  const defaultParams = { per_page: DEFAULT_PER_PAGE };
+
+  return axios.get(url, { params: { ...defaultParams, ...params }, ...axiosOptions });
+};
diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue
index 42257127bbce53b976e4155c0096b471620e8749..d8669bff973234688887e7cf78e04f4a68b33f32 100644
--- a/app/assets/javascripts/invite_members/components/group_select.vue
+++ b/app/assets/javascripts/invite_members/components/group_select.vue
@@ -3,7 +3,7 @@ import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui';
 import axios from 'axios';
 import { debounce } from 'lodash';
 import { s__ } from '~/locale';
-import { getGroups, getDescendentGroups } from '~/rest_api';
+import { getGroups, getDescendentGroups, getProjectShareLocations } from '~/rest_api';
 import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
 import { SEARCH_DELAY, GROUP_FILTERS } from '../constants';
 
@@ -26,6 +26,14 @@ export default {
       required: false,
       default: GROUP_FILTERS.ALL,
     },
+    isProject: {
+      type: Boolean,
+      required: true,
+    },
+    sourceId: {
+      type: String,
+      required: true,
+    },
     parentGroupId: {
       type: Number,
       required: false,
@@ -78,7 +86,7 @@ export default {
         value: group.id,
         id: group.id,
         name: group.full_name,
-        path: group.path,
+        path: group.full_path,
         avatarUrl: group.avatar_url,
       }));
 
@@ -104,15 +112,25 @@ export default {
 
       this.activeApiRequestAbortController = new AbortController();
 
+      const axiosConfig = {
+        signal: this.activeApiRequestAbortController.signal,
+      };
+
+      if (this.isProject) {
+        return this.fetchGroupsNew(axiosConfig);
+      }
+
+      return this.fetchGroupsLegacy(options, axiosConfig);
+    },
+    fetchGroupsNew(axiosConfig) {
+      return getProjectShareLocations(this.sourceId, { search: this.searchTerm }, axiosConfig);
+    },
+    fetchGroupsLegacy(options, axiosConfig) {
       const combinedOptions = {
         ...this.$options.defaultFetchOptions,
         ...options,
       };
 
-      const axiosConfig = {
-        signal: this.activeApiRequestAbortController.signal,
-      };
-
       switch (this.groupsFilter) {
         case GROUP_FILTERS.DESCENDANT_GROUPS:
           return getDescendentGroups(
diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
index 9893572ae16fbc0364eac5a446ebdd8dddfdb9c9..673c4fa6f00a4ea9e3768b101e302476f2b73554 100644
--- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
@@ -217,9 +217,11 @@ export default {
     <template #select>
       <group-select
         v-model="groupToBeSharedWith"
+        :source-id="id"
         :groups-filter="groupSelectFilter"
         :parent-group-id="groupSelectParentId"
         :invalid-groups="invalidGroups"
+        :is-project="isProject"
         @input="clearValidation"
         @error="onGroupSelectError"
       />
diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js
index 4ceed885e6eb90f3f295e95ff46b09b0ac6dd2aa..7b8419e036d1d19fc54f17caab6f0029c3c93fca 100644
--- a/spec/frontend/api/projects_api_spec.js
+++ b/spec/frontend/api/projects_api_spec.js
@@ -146,4 +146,33 @@ describe('~/api/projects_api.js', () => {
       });
     });
   });
+
+  describe('getProjectShareLocations', () => {
+    it('requests share locations for a project', async () => {
+      const expectedUrl = `/api/v7/projects/1/share_locations`;
+      const params = { search: 'foo' };
+      const axiosOptions = { mockOption: 'bar' };
+
+      const response = [
+        {
+          id: 27,
+          web_url: 'http://127.0.0.1:3000/groups/Commit451',
+          name: 'Commit451',
+          avatar_url: null,
+          full_name: 'Commit451',
+          full_path: 'Commit451',
+        },
+      ];
+
+      mock.onGet(expectedUrl).replyOnce(HTTP_STATUS_OK, response);
+
+      await expect(
+        projectsApi.getProjectShareLocations(projectId, params, axiosOptions),
+      ).resolves.toMatchObject({
+        data: response,
+      });
+      expect(mock.history.get[0].params).toEqual({ ...params, per_page: DEFAULT_PER_PAGE });
+      expect(mock.history.get[0].mockOption).toBe(axiosOptions.mockOption);
+    });
+  });
 });
diff --git a/spec/frontend/invite_members/components/group_select_spec.js b/spec/frontend/invite_members/components/group_select_spec.js
index 60501bfbd6a75f36bf814e43e8b44c9da7937c2c..9c22ca9796b954b313524e2c4b97b33acdc5ed32 100644
--- a/spec/frontend/invite_members/components/group_select_spec.js
+++ b/spec/frontend/invite_members/components/group_select_spec.js
@@ -4,9 +4,11 @@ import { mount } from '@vue/test-utils';
 import axios from 'axios';
 import waitForPromises from 'helpers/wait_for_promises';
 import { getGroups } from '~/api/groups_api';
+import { getProjectShareLocations } from '~/api/projects_api';
 import GroupSelect from '~/invite_members/components/group_select.vue';
 
 jest.mock('~/api/groups_api');
+jest.mock('~/api/projects_api');
 
 const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' };
 const group2 = { id: 2, full_name: 'Group Two', avatar_url: 'test' };
@@ -20,23 +22,25 @@ const headers = {
   'X-Total-Pages': 2,
 };
 
+const defaultProps = {
+  selectedGroup: {},
+  invalidGroups: [],
+  sourceId: '1',
+  isProject: false,
+};
+
 describe('GroupSelect', () => {
   let wrapper;
 
   const createComponent = (props = {}) => {
     wrapper = mount(GroupSelect, {
       propsData: {
-        selectedGroup: {},
-        invalidGroups: [],
+        ...defaultProps,
         ...props,
       },
     });
   };
 
-  beforeEach(() => {
-    getGroups.mockResolvedValueOnce({ data: allGroups, headers });
-  });
-
   const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
   const findListboxToggle = () => findListbox().find('button[aria-haspopup="listbox"]');
   const findAvatarByLabel = (text) =>
@@ -45,178 +49,220 @@ describe('GroupSelect', () => {
       .wrappers.find((dropdownItemWrapper) => dropdownItemWrapper.props('label') === text);
 
   describe('when user types in the search input', () => {
-    beforeEach(async () => {
-      createComponent();
-      await waitForPromises();
-      getGroups.mockClear();
-      getGroups.mockReturnValueOnce(new Promise(() => {}));
-      findListbox().vm.$emit('search', group1.full_name);
-      await nextTick();
-    });
+    describe('isProject is false', () => {
+      beforeEach(async () => {
+        createComponent({ isProject: false });
+        await waitForPromises();
 
-    it('calls the API', () => {
-      expect(getGroups).toHaveBeenCalledWith(
-        group1.full_name,
-        {
-          exclude_internal: true,
-          active: true,
-          order_by: 'similarity',
-        },
-        undefined,
-        {
-          signal: expect.any(AbortSignal),
-        },
-      );
-    });
+        getGroups.mockReturnValueOnce(new Promise(() => {}));
+        findListbox().vm.$emit('search', group1.full_name);
+      });
 
-    it('displays loading icon while waiting for API call to resolve', () => {
-      expect(findListbox().props('searching')).toBe(true);
-    });
-  });
+      it('displays loading icon while waiting for API call to resolve', () => {
+        expect(findListbox().props('searching')).toBe(true);
+      });
 
-  describe('avatar label', () => {
-    it('includes the correct attributes with name and avatar_url', async () => {
-      createComponent();
-      await waitForPromises();
+      it('calls the legacy API', async () => {
+        await nextTick();
 
-      expect(findAvatarByLabel(group1.full_name).attributes()).toMatchObject({
-        src: group1.avatar_url,
-        'entity-id': `${group1.id}`,
-        'entity-name': group1.full_name,
-        size: '32',
+        expect(getGroups).toHaveBeenCalledWith(
+          group1.full_name,
+          {
+            exclude_internal: true,
+            active: true,
+            order_by: 'similarity',
+          },
+          undefined,
+          {
+            signal: expect.any(AbortSignal),
+          },
+        );
       });
     });
 
-    describe('when filtering out the group from results', () => {
+    describe('isProject is true', () => {
       beforeEach(async () => {
-        createComponent({ invalidGroups: [group1.id] });
+        createComponent({ isProject: true });
         await waitForPromises();
-      });
 
-      it('does not find an invalid group', () => {
-        expect(findAvatarByLabel(group1.full_name)).toBe(undefined);
+        getProjectShareLocations.mockReturnValueOnce(new Promise(() => {}));
+        findListbox().vm.$emit('search', group1.full_name);
       });
 
-      it('finds a group that is valid', () => {
-        expect(findAvatarByLabel(group2.full_name).exists()).toBe(true);
+      it('displays loading icon while waiting for API call to resolve', () => {
+        expect(findListbox().props('searching')).toBe(true);
       });
-    });
-  });
 
-  describe('when group is selected from the dropdown', () => {
-    beforeEach(async () => {
-      createComponent({
-        selectedGroup: {
-          value: group1.id,
-          id: group1.id,
-          name: group1.full_name,
-          path: group1.path,
-          avatarUrl: group1.avatar_url,
-        },
-      });
-      await waitForPromises();
-      findListbox().vm.$emit('select', group1.id);
-      await nextTick();
-    });
+      it('calls the new API', async () => {
+        await nextTick();
 
-    it('emits `input` event used by `v-model`', () => {
-      expect(wrapper.emitted('input')).toMatchObject([
-        [
+        expect(getProjectShareLocations).toHaveBeenCalledWith(
+          defaultProps.sourceId,
           {
-            value: group1.id,
-            id: group1.id,
-            name: group1.full_name,
-            path: group1.path,
-            avatarUrl: group1.avatar_url,
+            search: group1.full_name,
           },
-        ],
-      ]);
-    });
-
-    it('sets dropdown toggle text to selected item', () => {
-      expect(findListboxToggle().text()).toBe(group1.full_name);
+          {
+            signal: expect.any(AbortSignal),
+          },
+        );
+      });
     });
   });
 
-  describe('infinite scroll', () => {
-    it('sets infinite scroll related props', async () => {
-      createComponent();
-      await waitForPromises();
+  describe.each`
+    isProject
+    ${true}
+    ${false}
+  `('isProject is $isProject', ({ isProject }) => {
+    const apiAction = isProject ? getProjectShareLocations : getGroups;
 
-      expect(findListbox().props()).toMatchObject({
-        infiniteScroll: true,
-        infiniteScrollLoading: false,
-        totalItems: 40,
-      });
+    beforeEach(() => {
+      apiAction.mockResolvedValueOnce({ data: allGroups, headers });
     });
 
-    describe('when `bottom-reached` event is fired', () => {
-      it('indicates new groups are loading and adds them to the listbox', async () => {
-        createComponent();
+    describe('avatar label', () => {
+      it('includes the correct attributes with name and avatar_url', async () => {
+        createComponent({ isProject });
         await waitForPromises();
 
-        const infiniteScrollGroup = {
-          id: 3,
-          full_name: 'Infinite scroll group',
-          avatar_url: 'test',
-        };
+        expect(findAvatarByLabel(group1.full_name).attributes()).toMatchObject({
+          src: group1.avatar_url,
+          'entity-id': `${group1.id}`,
+          'entity-name': group1.full_name,
+          size: '32',
+        });
+      });
 
-        getGroups.mockResolvedValueOnce({ data: [infiniteScrollGroup], headers });
+      describe('when filtering out the group from results', () => {
+        beforeEach(async () => {
+          createComponent({ isProject, invalidGroups: [group1.id] });
+          await waitForPromises();
+        });
 
-        findListbox().vm.$emit('bottom-reached');
+        it('does not find an invalid group', () => {
+          expect(findAvatarByLabel(group1.full_name)).toBe(undefined);
+        });
+
+        it('finds a group that is valid', () => {
+          expect(findAvatarByLabel(group2.full_name).exists()).toBe(true);
+        });
+      });
+    });
+
+    describe('when group is selected from the dropdown', () => {
+      beforeEach(async () => {
+        createComponent({
+          isProject,
+          selectedGroup: {
+            value: group1.id,
+            id: group1.id,
+            name: group1.full_name,
+            path: group1.path,
+            avatarUrl: group1.avatar_url,
+          },
+        });
+        await waitForPromises();
+        findListbox().vm.$emit('select', group1.id);
         await nextTick();
+      });
+
+      it('emits `input` event used by `v-model`', () => {
+        expect(wrapper.emitted('input')).toMatchObject([
+          [
+            {
+              value: group1.id,
+              id: group1.id,
+              name: group1.full_name,
+              path: group1.path,
+              avatarUrl: group1.avatar_url,
+            },
+          ],
+        ]);
+      });
 
-        expect(findListbox().props('infiniteScrollLoading')).toBe(true);
+      it('sets dropdown toggle text to selected item', () => {
+        expect(findListboxToggle().text()).toBe(group1.full_name);
+      });
+    });
 
+    describe('infinite scroll', () => {
+      beforeEach(async () => {
+        createComponent({ isProject });
         await waitForPromises();
+      });
 
-        expect(findListbox().props('items')[2]).toMatchObject({
-          value: infiniteScrollGroup.id,
-          id: infiniteScrollGroup.id,
-          name: infiniteScrollGroup.full_name,
-          avatarUrl: infiniteScrollGroup.avatar_url,
+      it('sets infinite scroll related props', () => {
+        expect(findListbox().props()).toMatchObject({
+          infiniteScroll: true,
+          infiniteScrollLoading: false,
+          totalItems: 40,
         });
       });
 
-      describe('when API request fails', () => {
-        it('emits `error` event', async () => {
-          createComponent();
-          await waitForPromises();
+      describe('when `bottom-reached` event is fired', () => {
+        it('indicates new groups are loading and adds them to the listbox', async () => {
+          const infiniteScrollGroup = {
+            id: 3,
+            full_name: 'Infinite scroll group',
+            avatar_url: 'test',
+          };
 
-          getGroups.mockRejectedValueOnce();
+          apiAction.mockResolvedValueOnce({ data: [infiniteScrollGroup], headers });
 
           findListbox().vm.$emit('bottom-reached');
+          await nextTick();
+
+          expect(findListbox().props('infiniteScrollLoading')).toBe(true);
+
           await waitForPromises();
 
-          expect(wrapper.emitted('error')).toEqual([[GroupSelect.i18n.errorMessage]]);
+          expect(findListbox().props('items')[2]).toMatchObject({
+            value: infiniteScrollGroup.id,
+            id: infiniteScrollGroup.id,
+            name: infiniteScrollGroup.full_name,
+            avatarUrl: infiniteScrollGroup.avatar_url,
+          });
         });
 
-        it('does not emit `error` event if error is from request cancellation', async () => {
-          createComponent();
-          await waitForPromises();
+        describe('when API request fails', () => {
+          it('emits `error` event', async () => {
+            apiAction.mockRejectedValueOnce();
 
-          getGroups.mockRejectedValueOnce(new axios.Cancel());
+            findListbox().vm.$emit('bottom-reached');
+            await waitForPromises();
 
-          findListbox().vm.$emit('bottom-reached');
-          await waitForPromises();
+            expect(wrapper.emitted('error')).toEqual([[GroupSelect.i18n.errorMessage]]);
+          });
+
+          it('does not emit `error` event if error is from request cancellation', async () => {
+            apiAction.mockRejectedValueOnce(new axios.Cancel());
+
+            findListbox().vm.$emit('bottom-reached');
+            await waitForPromises();
 
-          expect(wrapper.emitted('error')).toEqual(undefined);
+            expect(wrapper.emitted('error')).toEqual(undefined);
+          });
         });
       });
     });
-  });
 
-  describe('when multiple API calls are in-flight', () => {
-    it('aborts the first API call and resolves second API call', async () => {
-      const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
+    describe('when multiple API calls are in-flight', () => {
+      let abortSpy;
 
-      createComponent();
-      await waitForPromises();
+      beforeEach(async () => {
+        abortSpy = jest.spyOn(AbortController.prototype, 'abort');
+        apiAction.mockResolvedValueOnce({ data: allGroups, headers });
 
-      findListbox().vm.$emit('search', group1.full_name);
+        createComponent({ isProject });
+        await waitForPromises();
+      });
 
-      expect(abortSpy).toHaveBeenCalledTimes(1);
-      expect(wrapper.emitted('error')).toEqual(undefined);
+      it('aborts the first API call and resolves second API call', () => {
+        findListbox().vm.$emit('search', group1.full_name);
+
+        expect(abortSpy).toHaveBeenCalledTimes(1);
+        expect(wrapper.emitted('error')).toEqual(undefined);
+      });
     });
   });
 });
diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
index 358d70d8117a8ef3ea0570f2407330dd1260e922..09d4fdb12c1de548a78463bf6a8e83bd53efd4f4 100644
--- a/spec/frontend/invite_members/components/invite_groups_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
@@ -271,4 +271,17 @@ describe('InviteGroupsModal', () => {
       expect(wrapper.findComponent(GlAlert).text()).toBe(GroupSelect.i18n.errorMessage);
     });
   });
+
+  it('renders `GroupSelect` component and passes correct props', () => {
+    createComponent({ isProject: true });
+
+    expect(findGroupSelect().props()).toStrictEqual({
+      selectedGroup: {},
+      groupsFilter: 'all',
+      isProject: true,
+      sourceId: '1',
+      parentGroupId: null,
+      invalidGroups: [],
+    });
+  });
 });