diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js
index aad7712a9f05f0d54a8214f6410f9c6e6b0a1a4b..95ddf12afae7f8b1d6d779dc6d951f188c46b77c 100644
--- a/app/assets/javascripts/graphql_shared/constants.js
+++ b/app/assets/javascripts/graphql_shared/constants.js
@@ -5,6 +5,7 @@ export const TYPE_ITERATION = 'Iteration';
 export const TYPE_ITERATIONS_CADENCE = 'Iterations::Cadence';
 export const TYPE_MERGE_REQUEST = 'MergeRequest';
 export const TYPE_MILESTONE = 'Milestone';
+export const TYPE_PROJECT = 'Project';
 export const TYPE_SCANNER_PROFILE = 'DastScannerProfile';
 export const TYPE_SITE_PROFILE = 'DastSiteProfile';
 export const TYPE_USER = 'User';
diff --git a/app/assets/javascripts/runner/components/runner_registration_token_reset.vue b/app/assets/javascripts/runner/components/runner_registration_token_reset.vue
index 2335faa4f859d5c496f3aa1996d168b3ade02b7e..cdf14abd4f9fa9227fe191f98413b2346fd29d7d 100644
--- a/app/assets/javascripts/runner/components/runner_registration_token_reset.vue
+++ b/app/assets/javascripts/runner/components/runner_registration_token_reset.vue
@@ -1,6 +1,8 @@
 <script>
 import { GlButton } from '@gitlab/ui';
 import createFlash, { FLASH_TYPES } from '~/flash';
+import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
 import { __, s__ } from '~/locale';
 import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql';
 import { captureException } from '~/runner/sentry_utils';
@@ -11,6 +13,14 @@ export default {
   components: {
     GlButton,
   },
+  inject: {
+    groupId: {
+      default: null,
+    },
+    projectId: {
+      default: null,
+    },
+  },
   props: {
     type: {
       type: String,
@@ -25,7 +35,28 @@ export default {
       loading: false,
     };
   },
-  computed: {},
+  computed: {
+    resetTokenInput() {
+      switch (this.type) {
+        case INSTANCE_TYPE:
+          return {
+            type: this.type,
+          };
+        case GROUP_TYPE:
+          return {
+            id: convertToGraphQLId(TYPE_GROUP, this.groupId),
+            type: this.type,
+          };
+        case PROJECT_TYPE:
+          return {
+            id: convertToGraphQLId(TYPE_PROJECT, this.projectId),
+            type: this.type,
+          };
+        default:
+          return null;
+      }
+    },
+  },
   methods: {
     async resetToken() {
       // TODO Replace confirmation with gl-modal
@@ -44,13 +75,7 @@ export default {
         } = await this.$apollo.mutate({
           mutation: runnersRegistrationTokenResetMutation,
           variables: {
-            // TODO Currently INTANCE_TYPE only is supported
-            // In future iterations this component will support
-            // other registration token types.
-            // See: https://gitlab.com/gitlab-org/gitlab/-/issues/19819
-            input: {
-              type: this.type,
-            },
+            input: this.resetTokenInput,
           },
         });
         if (errors && errors.length) {
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index 92d881c43ea564ca722555ca4f334eea245773a8..07bbf60c4538b5b9fe017b08bc0e1047885d3b15 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -1,10 +1,20 @@
 <script>
+import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue';
 import RunnerTypeHelp from '../components/runner_type_help.vue';
+import { GROUP_TYPE } from '../constants';
 
 export default {
   components: {
+    RunnerManualSetupHelp,
     RunnerTypeHelp,
   },
+  props: {
+    registrationToken: {
+      type: String,
+      required: true,
+    },
+  },
+  GROUP_TYPE,
 };
 </script>
 
@@ -14,6 +24,12 @@ export default {
       <div class="col-sm-6">
         <runner-type-help />
       </div>
+      <div class="col-sm-6">
+        <runner-manual-setup-help
+          :registration-token="registrationToken"
+          :type="$options.GROUP_TYPE"
+        />
+      </div>
     </div>
   </div>
 </template>
diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js
index 5a72b09de3a4f75752e20cc1d27ae795275cfb62..e14c583d73ecad65065130b97189dce74196a423 100644
--- a/app/assets/javascripts/runner/group_runners/index.js
+++ b/app/assets/javascripts/runner/group_runners/index.js
@@ -1,6 +1,10 @@
 import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
 import GroupRunnersApp from './group_runners_app.vue';
 
+Vue.use(VueApollo);
+
 export const initGroupRunners = (selector = '#js-group-runners') => {
   const el = document.querySelector(selector);
 
@@ -8,10 +12,29 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
     return null;
   }
 
+  const { registrationToken, groupId } = el.dataset;
+
+  const apolloProvider = new VueApollo({
+    defaultClient: createDefaultClient(
+      {},
+      {
+        assumeImmutableResults: true,
+      },
+    ),
+  });
+
   return new Vue({
     el,
+    apolloProvider,
+    provide: {
+      groupId,
+    },
     render(h) {
-      return h(GroupRunnersApp);
+      return h(GroupRunnersApp, {
+        props: {
+          registrationToken,
+        },
+      });
     },
   });
 };
diff --git a/app/views/groups/runners/index.html.haml b/app/views/groups/runners/index.html.haml
index 08be0f93d82dafeab0d3e7fcc06a8770529b2c25..4e7bc99b1f0c305a85acd678b9c0a847f4a47fb3 100644
--- a/app/views/groups/runners/index.html.haml
+++ b/app/views/groups/runners/index.html.haml
@@ -3,4 +3,4 @@
 %h2.page-title
   = s_('Runners|Group Runners')
 
-#js-group-runners
+#js-group-runners{ data: { registration_token: @group.runners_token, group_id: @group.id } }
diff --git a/spec/frontend/runner/components/runner_registration_token_reset_spec.js b/spec/frontend/runner/components/runner_registration_token_reset_spec.js
index 6dc207e369c3031ca68c9d68c05b2b336a683d4f..8b360b88417d1bd50776874d93dff6ddcfb56568 100644
--- a/spec/frontend/runner/components/runner_registration_token_reset_spec.js
+++ b/spec/frontend/runner/components/runner_registration_token_reset_spec.js
@@ -1,11 +1,12 @@
 import { GlButton } from '@gitlab/ui';
 import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
 import VueApollo from 'vue-apollo';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import createFlash, { FLASH_TYPES } from '~/flash';
 import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue';
-import { INSTANCE_TYPE } from '~/runner/constants';
+import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
 import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql';
 import { captureException } from '~/runner/sentry_utils';
 
@@ -23,11 +24,13 @@ describe('RunnerRegistrationTokenReset', () => {
 
   const findButton = () => wrapper.findComponent(GlButton);
 
-  const createComponent = () => {
+  const createComponent = ({ props, provide = {} } = {}) => {
     wrapper = shallowMount(RunnerRegistrationTokenReset, {
       localVue,
+      provide,
       propsData: {
         type: INSTANCE_TYPE,
+        ...props,
       },
       apolloProvider: createMockApollo([
         [runnersRegistrationTokenResetMutation, runnersRegistrationTokenResetMutationHandler],
@@ -59,31 +62,47 @@ describe('RunnerRegistrationTokenReset', () => {
   });
 
   describe('On click and confirmation', () => {
-    beforeEach(async () => {
-      window.confirm.mockReturnValueOnce(true);
-      await findButton().vm.$emit('click');
-    });
+    const mockGroupId = '11';
+    const mockProjectId = '22';
+
+    describe.each`
+      type             | provide                         | expectedInput
+      ${INSTANCE_TYPE} | ${{}}                           | ${{ type: INSTANCE_TYPE }}
+      ${GROUP_TYPE}    | ${{ groupId: mockGroupId }}     | ${{ type: GROUP_TYPE, id: `gid://gitlab/Group/${mockGroupId}` }}
+      ${PROJECT_TYPE}  | ${{ projectId: mockProjectId }} | ${{ type: PROJECT_TYPE, id: `gid://gitlab/Project/${mockProjectId}` }}
+    `('Resets token of type $type', ({ type, provide, expectedInput }) => {
+      beforeEach(async () => {
+        createComponent({
+          provide,
+          props: { type },
+        });
+
+        window.confirm.mockReturnValueOnce(true);
+        findButton().vm.$emit('click');
+        await waitForPromises();
+      });
 
-    it('resets token', () => {
-      expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1);
-      expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({
-        input: { type: INSTANCE_TYPE },
+      it('resets token', () => {
+        expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledTimes(1);
+        expect(runnersRegistrationTokenResetMutationHandler).toHaveBeenCalledWith({
+          input: expectedInput,
+        });
       });
-    });
 
-    it('emits result', () => {
-      expect(wrapper.emitted('tokenReset')).toHaveLength(1);
-      expect(wrapper.emitted('tokenReset')[0]).toEqual([mockNewToken]);
-    });
+      it('emits result', () => {
+        expect(wrapper.emitted('tokenReset')).toHaveLength(1);
+        expect(wrapper.emitted('tokenReset')[0]).toEqual([mockNewToken]);
+      });
 
-    it('does not show a loading state', () => {
-      expect(findButton().props('loading')).toBe(false);
-    });
+      it('does not show a loading state', () => {
+        expect(findButton().props('loading')).toBe(false);
+      });
 
-    it('shows confirmation', () => {
-      expect(createFlash).toHaveBeenLastCalledWith({
-        message: expect.stringContaining('registration token generated'),
-        type: FLASH_TYPES.SUCCESS,
+      it('shows confirmation', () => {
+        expect(createFlash).toHaveBeenLastCalledWith({
+          message: expect.stringContaining('registration token generated'),
+          type: FLASH_TYPES.SUCCESS,
+        });
       });
     });
   });
@@ -91,7 +110,8 @@ describe('RunnerRegistrationTokenReset', () => {
   describe('On click without confirmation', () => {
     beforeEach(async () => {
       window.confirm.mockReturnValueOnce(false);
-      await findButton().vm.$emit('click');
+      findButton().vm.$emit('click');
+      await waitForPromises();
     });
 
     it('does not reset token', () => {
@@ -118,7 +138,7 @@ describe('RunnerRegistrationTokenReset', () => {
       runnersRegistrationTokenResetMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
 
       window.confirm.mockReturnValueOnce(true);
-      await findButton().vm.$emit('click');
+      findButton().vm.$emit('click');
       await waitForPromises();
 
       expect(createFlash).toHaveBeenLastCalledWith({
@@ -144,7 +164,7 @@ describe('RunnerRegistrationTokenReset', () => {
       });
 
       window.confirm.mockReturnValueOnce(true);
-      await findButton().vm.$emit('click');
+      findButton().vm.$emit('click');
       await waitForPromises();
 
       expect(createFlash).toHaveBeenLastCalledWith({
@@ -160,7 +180,8 @@ describe('RunnerRegistrationTokenReset', () => {
   describe('Immediately after click', () => {
     it('shows loading state', async () => {
       window.confirm.mockReturnValue(true);
-      await findButton().vm.$emit('click');
+      findButton().vm.$emit('click');
+      await nextTick();
 
       expect(findButton().props('loading')).toBe(true);
     });
diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js
index 06329686a0032ec76b0ab1065e5d673d7092d3c9..6a0863e92b4201b685a6d7c26f41632088d0fe29 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -1,14 +1,22 @@
 import { shallowMount } from '@vue/test-utils';
+import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue';
 import RunnerTypeHelp from '~/runner/components/runner_type_help.vue';
 import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
 
+const mockRegistrationToken = 'AABBCC';
+
 describe('GroupRunnersApp', () => {
   let wrapper;
 
   const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp);
+  const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp);
 
   const createComponent = ({ mountFn = shallowMount } = {}) => {
-    wrapper = mountFn(GroupRunnersApp);
+    wrapper = mountFn(GroupRunnersApp, {
+      propsData: {
+        registrationToken: mockRegistrationToken,
+      },
+    });
   };
 
   beforeEach(() => {
@@ -18,4 +26,9 @@ describe('GroupRunnersApp', () => {
   it('shows the runner type help', () => {
     expect(findRunnerTypeHelp().exists()).toBe(true);
   });
+
+  it('shows the runner setup instructions', () => {
+    expect(findRunnerManualSetupHelp().exists()).toBe(true);
+    expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken);
+  });
 });