diff --git a/ee/app/assets/javascripts/usage_quotas/add_on/graphql/user_add_on_assignment_bulk_create.mutation.graphql b/ee/app/assets/javascripts/usage_quotas/add_on/graphql/user_add_on_assignment_bulk_create.mutation.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..b2e563c296b1434bc56654c6824f7619a2450532
--- /dev/null
+++ b/ee/app/assets/javascripts/usage_quotas/add_on/graphql/user_add_on_assignment_bulk_create.mutation.graphql
@@ -0,0 +1,27 @@
+mutation userAddOnAssignmentBulkCreate(
+  $userIds: [UserID!]!
+  $addOnPurchaseId: GitlabSubscriptionsAddOnPurchaseID!
+) {
+  userAddOnAssignmentBulkCreate(input: { userIds: $userIds, addOnPurchaseId: $addOnPurchaseId }) {
+    errors
+    users {
+      nodes {
+        id
+        addOnAssignments(addOnPurchaseIds: [$addOnPurchaseId]) {
+          nodes {
+            # eslint-disable-next-line @graphql-eslint/require-id-when-available
+            addOnPurchase {
+              name
+            }
+          }
+        }
+      }
+    }
+    addOnPurchase {
+      id
+      name
+      purchasedQuantity
+      assignedQuantity
+    }
+  }
+}
diff --git a/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal.vue b/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal.vue
index 8bd7d90a08d5499f10abb1c68ea1e2c7a8aea188..bd77cfc81e6c9c5e68e9636da305fcbc2f8458f5 100644
--- a/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal.vue
+++ b/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal.vue
@@ -18,6 +18,11 @@ export default {
       type: String,
       required: true,
     },
+    isBulkActionInProgress: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
   },
   computed: {
     isBulkActionToAssignSeats() {
@@ -56,6 +61,9 @@ export default {
     hide() {
       this.$emit('cancel');
     },
+    confirmSeatAssignment() {
+      this.$emit('confirm-seat-assignment');
+    },
   },
 };
 </script>
@@ -72,13 +80,19 @@ export default {
 
     <template #modal-footer>
       <div class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0">
-        <gl-button data-testid="bulk-action-cancel-button" @click="hide">
+        <gl-button
+          data-testid="bulk-action-cancel-button"
+          :disabled="isBulkActionInProgress"
+          @click="hide"
+        >
           {{ __('Cancel') }}
         </gl-button>
         <gl-button
           v-if="isBulkActionToAssignSeats"
           variant="confirm"
           data-testid="assign-confirmation-button"
+          :loading="isBulkActionInProgress"
+          @click="confirmSeatAssignment"
         >
           {{ s__('Billing|Assign seats') }}
         </gl-button>
diff --git a/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_eligible_user_list.vue b/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_eligible_user_list.vue
index 07d44637ac238eda131665ce5d2784b804cdeed0..62bb66ac63bbe54d884e1044e9bd11da8c87643d 100644
--- a/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_eligible_user_list.vue
+++ b/ee/app/assets/javascripts/usage_quotas/code_suggestions/components/add_on_eligible_user_list.vue
@@ -15,6 +15,7 @@ import { s__, n__, sprintf } from '~/locale';
 import SafeHtml from '~/vue_shared/directives/safe_html';
 import { ADD_ON_ERROR_DICTIONARY } from 'ee/usage_quotas/error_constants';
 import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
 import {
   addOnEligibleUserListTableFields,
   ASSIGN_SEATS_BULK_ACTION,
@@ -24,6 +25,7 @@ import ErrorAlert from 'ee/vue_shared/components/error_alert/error_alert.vue';
 import { scrollToElement } from '~/lib/utils/common_utils';
 import CodeSuggestionsAddonAssignment from 'ee/usage_quotas/code_suggestions/components/code_suggestions_addon_assignment.vue';
 import AddOnBulkActionConfirmationModal from 'ee/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal.vue';
+import userAddOnAssignmentBulkCreateMutation from 'ee/usage_quotas/add_on/graphql/user_add_on_assignment_bulk_create.mutation.graphql';
 
 export default {
   name: 'AddOnEligibleUserList',
@@ -77,6 +79,7 @@ export default {
       addOnAssignmentError: undefined,
       selectedUsers: [],
       bulkAction: undefined,
+      isBulkActionInProgress: false,
       isConfirmationModalVisible: false,
     };
   },
@@ -172,18 +175,18 @@ export default {
       scrollToElement(this.$el);
     },
     isUserSelected(item) {
-      return this.selectedUsers.includes(item.username);
+      return this.selectedUsers.includes(item.id);
     },
     handleUserSelection(user, value) {
       if (value) {
-        this.selectedUsers.push(user.username);
+        this.selectedUsers.push(user.id);
       } else {
-        this.selectedUsers = this.selectedUsers.filter((username) => username !== user.username);
+        this.selectedUsers = this.selectedUsers.filter((id) => id !== user.id);
       }
     },
     handleSelectAllUsers(value) {
       if (value) {
-        this.selectedUsers = this.users.map((user) => user.username);
+        this.selectedUsers = this.users.map((user) => user.id);
       } else {
         this.unselectAllUsers();
       }
@@ -199,6 +202,35 @@ export default {
       this.isConfirmationModalVisible = false;
       this.bulkAction = undefined;
     },
+    async assignSeats() {
+      this.isBulkActionInProgress = true;
+
+      try {
+        const {
+          data: { userAddOnAssignmentBulkCreate },
+        } = await this.$apollo.mutate({
+          mutation: userAddOnAssignmentBulkCreateMutation,
+          variables: {
+            userIds: this.selectedUsers,
+            addOnPurchaseId: this.addOnPurchaseId,
+          },
+        });
+
+        const errors = userAddOnAssignmentBulkCreate?.errors || [];
+
+        if (!errors.length) {
+          this.unselectAllUsers();
+        }
+      } catch (e) {
+        this.handleBulkActionError(e);
+      } finally {
+        this.isBulkActionInProgress = false;
+        this.isConfirmationModalVisible = false;
+      }
+    },
+    handleBulkActionError(e) {
+      Sentry.captureException(e);
+    },
   },
 };
 </script>
@@ -327,6 +359,8 @@ export default {
       v-if="isConfirmationModalVisible"
       :bulk-action="bulkAction"
       :user-count="selectedUsers.length"
+      :is-bulk-action-in-progress="isBulkActionInProgress"
+      @confirm-seat-assignment="assignSeats"
       @cancel="handleCancelBulkAction"
     />
   </section>
diff --git a/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal_spec.js b/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal_spec.js
index 28aaeb2a4feed4610f3340203d54d27461587a9f..3b84790a6e9a55a06e7ebe9892e19274164f11cb 100644
--- a/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal_spec.js
+++ b/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal_spec.js
@@ -53,6 +53,14 @@ describe('Add On Bulk Action Confirmation Modal', () => {
         'This action will assign a GitLab Duo Pro seat to 2 users. Are you sure you want to continue?',
       );
     });
+
+    it('emits appropriate event on assignment confirmation', () => {
+      createComponent();
+
+      findAssignSeatsButton().vm.$emit('click');
+
+      expect(wrapper.emitted('confirm-seat-assignment')).toHaveLength(1);
+    });
   });
 
   describe('when bulk action is for seat unassignment', () => {
@@ -80,6 +88,35 @@ describe('Add On Bulk Action Confirmation Modal', () => {
     });
   });
 
+  describe('loading state', () => {
+    describe('when loading', () => {
+      beforeEach(() => {
+        createComponent({ isBulkActionInProgress: true });
+      });
+
+      it('shows loading state for confirm assignment button', () => {
+        expect(findAssignSeatsButton().props().loading).toBe(true);
+      });
+
+      it('disables cancel button', () => {
+        expect(findCancelButton().props().disabled).toBe(true);
+      });
+    });
+    describe('when not loading', () => {
+      beforeEach(() => {
+        createComponent({ isBulkActionInProgress: false });
+      });
+
+      it('does not show loading state', () => {
+        expect(findAssignSeatsButton().props().loading).toBe(false);
+      });
+
+      it('does not disable cancel button', () => {
+        expect(findCancelButton().props().disabled).toBe(false);
+      });
+    });
+  });
+
   it('should emit cancel when cancel button is clicked', () => {
     createComponent();
 
diff --git a/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_eligible_user_list_spec.js b/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_eligible_user_list_spec.js
index 4255ea22e01f03aa997e903f0da9ab05e3a2022d..54d91c9401984a6221e0dcd50d0ad803d5272e09 100644
--- a/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_eligible_user_list_spec.js
+++ b/ee/spec/frontend/usage_quotas/code_suggestions/components/add_on_eligible_user_list_spec.js
@@ -7,7 +7,9 @@ import {
   GlFormCheckbox,
 } from '@gitlab/ui';
 import { mount, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
 import CodeSuggestionsAddOnAssignment from 'ee/usage_quotas/code_suggestions/components/code_suggestions_addon_assignment.vue';
 import AddOnEligibleUserList from 'ee/usage_quotas/code_suggestions/components/add_on_eligible_user_list.vue';
 import waitForPromises from 'helpers/wait_for_promises';
@@ -16,28 +18,103 @@ import {
   pageInfoWithNoPages,
   pageInfoWithMorePages,
   eligibleUsersWithMaxRole,
+  mockAddOnEligibleUsers,
 } from 'ee_jest/usage_quotas/code_suggestions/mock_data';
 import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import { ADD_ON_ERROR_DICTIONARY } from 'ee/usage_quotas/error_constants';
 import { scrollToElement } from '~/lib/utils/common_utils';
 import AddOnBulkActionConfirmationModal from 'ee/usage_quotas/code_suggestions/components/add_on_bulk_action_confirmation_modal.vue';
+import { ADD_ON_CODE_SUGGESTIONS } from 'ee/usage_quotas/code_suggestions/constants';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import getAddOnEligibleUsers from 'ee/usage_quotas/add_on/graphql/saas_add_on_eligible_users.query.graphql';
+import userAddOnAssignmentBulkCreateMutation from 'ee/usage_quotas/add_on/graphql/user_add_on_assignment_bulk_create.mutation.graphql';
+
+Vue.use(VueApollo);
 
 jest.mock('~/lib/utils/common_utils');
+jest.mock('~/sentry/sentry_browser_wrapper');
 
 describe('Add On Eligible User List', () => {
   let wrapper;
 
   const addOnPurchaseId = 'gid://gitlab/GitlabSubscriptions::AddOnPurchase/1';
 
+  const codeSuggestionsAddOn = { addOnPurchase: { name: ADD_ON_CODE_SUGGESTIONS } };
+
+  const addOnPurchase = {
+    id: addOnPurchaseId,
+    name: ADD_ON_CODE_SUGGESTIONS,
+    purchasedQuantity: 3,
+    assignedQuantity: 2,
+    __typename: 'AddOnPurchase',
+  };
+
+  const addOnEligibleUsersQueryVariables = {
+    fullPath: 'namespace/full-path',
+    addOnType: 'CODE_SUGGESTIONS',
+    addOnPurchaseIds: [addOnPurchaseId],
+  };
+
+  const bulkAddOnAssignmentSuccess = {
+    clientMutationId: '1',
+    errors: [],
+    addOnPurchase,
+    users: {
+      nodes: [
+        {
+          id: eligibleUsers[1].id,
+          addOnAssignments: {
+            nodes: codeSuggestionsAddOn,
+            __typename: 'UserAddOnAssignmentConnection',
+          },
+          __typename: 'AddOnUser',
+        },
+        {
+          id: eligibleUsers[2].id,
+          addOnAssignments: {
+            nodes: codeSuggestionsAddOn,
+            __typename: 'UserAddOnAssignmentConnection',
+          },
+          __typename: 'AddOnUser',
+        },
+      ],
+    },
+  };
+
+  const bulkAssignAddOnHandler = jest.fn().mockResolvedValue({
+    data: { userAddOnAssignmentBulkCreate: bulkAddOnAssignmentSuccess },
+  });
+
+  const createMockApolloProvider = (addonAssignmentCreateHandler) => {
+    const mockApollo = createMockApollo([
+      [userAddOnAssignmentBulkCreateMutation, addonAssignmentCreateHandler],
+    ]);
+
+    // Needed to check if cache update is successful on successful mutation
+    mockApollo.clients.defaultClient.cache.writeQuery({
+      query: getAddOnEligibleUsers,
+      variables: addOnEligibleUsersQueryVariables,
+      data: mockAddOnEligibleUsers.data,
+    });
+
+    return mockApollo;
+  };
+
+  let mockApolloClient;
+
   const createComponent = ({
     enableAddOnUsersFiltering = false,
     isBulkAddOnAssignmentEnabled = false,
+    addonAssignmentBulkCreateHandler = bulkAssignAddOnHandler,
     mountFn = shallowMount,
     props = {},
     slots = {},
   } = {}) => {
+    mockApolloClient = createMockApolloProvider(addonAssignmentBulkCreateHandler);
+
     wrapper = extendedWrapper(
       mountFn(AddOnEligibleUserList, {
+        apolloProvider: mockApolloClient,
         propsData: {
           addOnPurchaseId,
           users: eligibleUsers,
@@ -58,6 +135,12 @@ describe('Add On Eligible User List', () => {
     return waitForPromises();
   };
 
+  const getAddOnAssignmentStatusForUserFromCache = (userId) => {
+    return mockApolloClient.clients.defaultClient.cache
+      .readQuery({ query: getAddOnEligibleUsers, variables: addOnEligibleUsersQueryVariables })
+      .namespace.addOnEligibleUsers.nodes.find((node) => node.id === userId).addOnAssignments.nodes;
+  };
+
   const findTable = () => wrapper.findComponent(GlTable);
   const findTableKeys = () =>
     findTable()
@@ -546,6 +629,7 @@ describe('Add On Eligible User List', () => {
 
         expect(findConfirmationModal().props()).toEqual({
           bulkAction: 'ASSIGN_BULK_ACTION',
+          isBulkActionInProgress: false,
           userCount: eligibleUsers.length,
         });
       });
@@ -556,6 +640,7 @@ describe('Add On Eligible User List', () => {
 
         expect(findConfirmationModal().props()).toEqual({
           bulkAction: 'UNASSIGN_BULK_ACTION',
+          isBulkActionInProgress: false,
           userCount: eligibleUsers.length,
         });
       });
@@ -589,6 +674,7 @@ describe('Add On Eligible User List', () => {
 
         expect(findConfirmationModal().props()).toEqual({
           bulkAction: 'ASSIGN_BULK_ACTION',
+          isBulkActionInProgress: false,
           userCount: 2,
         });
       });
@@ -599,6 +685,7 @@ describe('Add On Eligible User List', () => {
 
         expect(findConfirmationModal().props()).toEqual({
           bulkAction: 'UNASSIGN_BULK_ACTION',
+          isBulkActionInProgress: false,
           userCount: 2,
         });
       });
@@ -627,6 +714,100 @@ describe('Add On Eligible User List', () => {
       });
     });
 
+    describe('bulk assignment confirmation', () => {
+      describe('successful assignment', () => {
+        beforeEach(async () => {
+          await createComponent({ mountFn: mount, isBulkAddOnAssignmentEnabled: true });
+
+          findSelectUserCheckboxAt(1).find('input').setChecked(true);
+          findSelectUserCheckboxAt(2).find('input').setChecked(true);
+          await nextTick();
+
+          findAssignSeatsButton().vm.$emit('click');
+          await nextTick();
+
+          findConfirmationModal().vm.$emit('confirm-seat-assignment');
+          await nextTick();
+        });
+
+        it('calls bulk addon assigment mutation with appropriate params', () => {
+          expect(bulkAssignAddOnHandler).toHaveBeenCalledWith({
+            addOnPurchaseId,
+            userIds: [eligibleUsers[1].id, eligibleUsers[2].id],
+          });
+        });
+
+        it('shows a loading state', () => {
+          expect(findConfirmationModal().props().isBulkActionInProgress).toBe(true);
+        });
+
+        it('updates the cache with latest add-on assignment status', async () => {
+          await waitForPromises();
+
+          expect(getAddOnAssignmentStatusForUserFromCache(eligibleUsers[1].id)).toEqual(
+            codeSuggestionsAddOn,
+          );
+          expect(getAddOnAssignmentStatusForUserFromCache(eligibleUsers[2].id)).toEqual(
+            codeSuggestionsAddOn,
+          );
+        });
+
+        it('does not show the confirmation modal on successful API call', async () => {
+          await waitForPromises();
+
+          expect(findConfirmationModal().exists()).toBe(false);
+        });
+
+        it('unselects users on successful API call', async () => {
+          expect(findSelectedUsersSummary().exists()).toBe(true);
+
+          await waitForPromises();
+
+          expect(findSelectedUsersSummary().exists()).toBe(false);
+        });
+      });
+
+      describe('unsuccessful assignment', () => {
+        const error = new Error('An error');
+
+        beforeEach(async () => {
+          await createComponent({
+            mountFn: mount,
+            isBulkAddOnAssignmentEnabled: true,
+            addonAssignmentBulkCreateHandler: jest.fn().mockRejectedValue(error),
+          });
+
+          findSelectUserCheckboxAt(1).find('input').setChecked(true);
+          findSelectUserCheckboxAt(2).find('input').setChecked(true);
+          await nextTick();
+
+          findAssignSeatsButton().vm.$emit('click');
+          await nextTick();
+
+          findConfirmationModal().vm.$emit('confirm-seat-assignment');
+          await nextTick();
+        });
+
+        it('captures error on Sentry for generic errors', async () => {
+          await waitForPromises();
+
+          expect(Sentry.captureException).toHaveBeenCalledWith(error);
+        });
+
+        it('does not show the confirmation modal on unsuccessful API call', async () => {
+          await waitForPromises();
+
+          expect(findConfirmationModal().exists()).toBe(false);
+        });
+
+        it('retains user selection on unsuccessful API call', async () => {
+          await waitForPromises();
+
+          expect(findSelectedUsersSummary().text()).toMatchInterpolatedText('2 users selected');
+        });
+      });
+    });
+
     describe('when paginating', () => {
       beforeEach(async () => {
         createComponent({