diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue
index 01af0be20db50cfb002234cf3b9fc05c8afdc84f..152ce8474f24659c05cce9c6c7e5b6556c25932a 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue
@@ -1,7 +1,17 @@
 <script>
-import { GlButton, GlCard, GlTable, GlLoadingIcon, GlKeysetPagination } from '@gitlab/ui';
+import {
+  GlAlert,
+  GlButton,
+  GlCard,
+  GlTable,
+  GlLoadingIcon,
+  GlKeysetPagination,
+  GlModal,
+  GlModalDirective,
+} from '@gitlab/ui';
 import packagesProtectionRuleQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql';
 import { getPackageTypeLabel } from '~/packages_and_registries/package_registry/utils';
+import deletePackagesProtectionRuleMutation from '~/packages_and_registries/settings/project/graphql/mutations/delete_packages_protection_rule.mutation.graphql';
 import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
 import PackagesProtectionRuleForm from '~/packages_and_registries/settings/project/components/packages_protection_rule_form.vue';
 import { s__, __ } from '~/locale';
@@ -19,17 +29,28 @@ export default {
     SettingsBlock,
     GlButton,
     GlCard,
+    GlAlert,
     GlTable,
     GlLoadingIcon,
     PackagesProtectionRuleForm,
     GlKeysetPagination,
+    GlModal,
+  },
+  directives: {
+    GlModal: GlModalDirective,
   },
   inject: ['projectPath'],
   i18n: {
-    settingBlockTitle: s__('PackageRegistry|Protected packages'),
+    settingBlockTitle: s__('PackageRegistry|Package protection rules'),
     settingBlockDescription: s__(
       'PackageRegistry|When a package is protected then only certain user roles are able to update and delete the protected package. This helps to avoid tampering with the package.',
     ),
+    protectionRuleDeletionConfirmModal: {
+      title: s__('PackageRegistry|Are you sure you want to delete the package protection rule?'),
+      description: s__(
+        'PackageRegistry|Users with at least the Developer role for this project will be able to publish, edit, and delete packages.',
+      ),
+    },
   },
   data() {
     return {
@@ -38,12 +59,18 @@ export default {
       protectionRuleFormVisibility: false,
       packageProtectionRulesQueryPayload: { nodes: [], pageInfo: {} },
       packageProtectionRulesQueryPaginationParams: { first: PAGINATION_DEFAULT_PER_PAGE },
+      deleteInProgress: false,
+      deleteItem: null,
+      alertErrorMessage: '',
+      protectionRuleDeletionInProgress: false,
+      protectionRuleDeletionItem: null,
     };
   },
   computed: {
     tableItems() {
       return this.packageProtectionRulesQueryResult.map((packagesProtectionRule) => {
         return {
+          id: packagesProtectionRule.id,
           col_1_package_name_pattern: packagesProtectionRule.packageNamePattern,
           col_2_package_type: getPackageTypeLabel(packagesProtectionRule.packageType),
           col_3_push_protected_up_to_access_level:
@@ -65,6 +92,19 @@ export default {
     isAddProtectionRuleButtonDisabled() {
       return this.protectionRuleFormVisibility;
     },
+    modalActionPrimary() {
+      return {
+        text: __('Delete'),
+        attributes: {
+          variant: 'danger',
+        },
+      };
+    },
+    modalActionCancel() {
+      return {
+        text: __('Cancel'),
+      };
+    },
   },
   apollo: {
     packageProtectionRulesQueryPayload: {
@@ -106,7 +146,44 @@ export default {
         last: PAGINATION_DEFAULT_PER_PAGE,
       };
     },
+    isButtonDisabled(item) {
+      return this.protectionRuleDeletionItem === item && this.protectionRuleDeletionInProgress;
+    },
+    showProtectionRuleDeletionConfirmModal(protectionRule) {
+      this.protectionRuleDeletionItem = protectionRule;
+    },
+    deleteProtectionRule(protectionRule) {
+      this.clearAlertMessage();
+
+      this.protectionRuleDeletionInProgress = true;
+
+      return this.$apollo
+        .mutate({
+          mutation: deletePackagesProtectionRuleMutation,
+          variables: { input: { id: protectionRule.id } },
+        })
+        .then(({ data }) => {
+          const [errorMessage] = data?.deletePackagesProtectionRule?.errors ?? [];
+          if (errorMessage) {
+            this.alertErrorMessage = errorMessage;
+            return;
+          }
+          this.refetchProtectionRules();
+          this.$toast.show(s__('PackageRegistry|Package protection rule deleted.'));
+        })
+        .catch((e) => {
+          this.alertErrorMessage = e.message;
+        })
+        .finally(() => {
+          this.protectionRuleDeletionItem = null;
+          this.protectionRuleDeletionInProgress = false;
+        });
+    },
+    clearAlertMessage() {
+      this.alertErrorMessage = '';
+    },
   },
+  table: {},
   fields: [
     {
       key: 'col_1_package_name_pattern',
@@ -117,7 +194,14 @@ export default {
       key: 'col_3_push_protected_up_to_access_level',
       label: s__('PackageRegistry|Push protected up to access level'),
     },
+    {
+      key: 'col_4_actions',
+      label: '',
+      thClass: 'gl-display-none',
+      tdClass: 'gl-w-15p',
+    },
   ],
+  modal: { id: 'delete-package-protection-rule-confirmation-modal' },
 };
 </script>
 
@@ -157,18 +241,38 @@ export default {
             @submit="refetchProtectionRules"
           />
 
+          <gl-alert
+            v-if="alertErrorMessage"
+            class="gl-mb-5"
+            variant="danger"
+            @dismiss="clearAlertMessage"
+          >
+            {{ alertErrorMessage }}
+          </gl-alert>
+
           <gl-table
             :items="tableItems"
             :fields="$options.fields"
             show-empty
             stacked="md"
-            class="gl-mb-5!"
             :aria-label="$options.i18n.settingBlockTitle"
             :busy="isLoadingPackageProtectionRules"
           >
             <template #table-busy>
               <gl-loading-icon size="sm" class="gl-my-5" />
             </template>
+
+            <template #cell(col_4_actions)="{ item }">
+              <gl-button
+                v-gl-modal="$options.modal.id"
+                category="secondary"
+                variant="danger"
+                size="small"
+                :disabled="isButtonDisabled(item)"
+                @click="showProtectionRuleDeletionConfirmModal(item)"
+                >{{ __('Delete') }}</gl-button
+              >
+            </template>
           </gl-table>
 
           <div class="gl-display-flex gl-justify-content-center gl-mb-3">
@@ -182,6 +286,17 @@ export default {
           </div>
         </template>
       </gl-card>
+
+      <gl-modal
+        :modal-id="$options.modal.id"
+        size="sm"
+        :title="$options.i18n.protectionRuleDeletionConfirmModal.title"
+        :action-primary="modalActionPrimary"
+        :action-cancel="modalActionCancel"
+        @primary="deleteProtectionRule(protectionRuleDeletionItem)"
+      >
+        <p>{{ $options.i18n.protectionRuleDeletionConfirmModal.description }}</p>
+      </gl-modal>
     </template>
   </settings-block>
 </template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_packages_protection_rule.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_packages_protection_rule.mutation.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..b5d6b69440d8f927649c0ed241435993a7e7946b
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_packages_protection_rule.mutation.graphql
@@ -0,0 +1,10 @@
+mutation deletePackagesProtectionRule($input: DeletePackagesProtectionRuleInput!) {
+  deletePackagesProtectionRule(input: $input) {
+    packageProtectionRule {
+      id
+      packageType
+      packageNamePattern
+    }
+    errors
+  }
+}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7c97f3a7ed7d28cc3d68f9810fdc4be252ab3197..9b7a724c1f0b1ae75516e361858fa64c56c1b6ba 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -34937,6 +34937,9 @@ msgstr ""
 msgid "PackageRegistry|App name: %{name}"
 msgstr ""
 
+msgid "PackageRegistry|Are you sure you want to delete the package protection rule?"
+msgstr ""
+
 msgid "PackageRegistry|Author email: %{authorEmail}"
 msgstr ""
 
@@ -35218,6 +35221,12 @@ msgstr[1] ""
 msgid "PackageRegistry|Package name pattern"
 msgstr ""
 
+msgid "PackageRegistry|Package protection rule deleted."
+msgstr ""
+
+msgid "PackageRegistry|Package protection rules"
+msgstr ""
+
 msgid "PackageRegistry|Package type"
 msgstr ""
 
@@ -35245,9 +35254,6 @@ msgstr ""
 msgid "PackageRegistry|Project-level"
 msgstr ""
 
-msgid "PackageRegistry|Protected packages"
-msgstr ""
-
 msgid "PackageRegistry|Publish packages if their name or version matches this regex."
 msgstr ""
 
@@ -35383,6 +35389,9 @@ msgstr ""
 msgid "PackageRegistry|Unable to load package"
 msgstr ""
 
+msgid "PackageRegistry|Users with at least the Developer role for this project will be able to publish, edit, and delete packages."
+msgstr ""
+
 msgid "PackageRegistry|When a package is protected then only certain user roles are able to update and delete the protected package. This helps to avoid tampering with the package."
 msgstr ""
 
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_protection_rules_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_protection_rules_spec.js
index 8a2a17176b0d74deb611f9a9ed4843c04d97c887..45088d49c8221bcaf5cd033e6dc16f30b0556aa1 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_protection_rules_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_protection_rules_spec.js
@@ -1,16 +1,21 @@
-import { GlLoadingIcon, GlKeysetPagination } from '@gitlab/ui';
+import { GlLoadingIcon, GlKeysetPagination, GlModal } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
 import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
+import { getBinding } from 'helpers/vue_mock_directive';
 import PackagesProtectionRules from '~/packages_and_registries/settings/project/components/packages_protection_rules.vue';
 import PackagesProtectionRuleForm from '~/packages_and_registries/settings/project/components/packages_protection_rule_form.vue';
 import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
 import packagesProtectionRuleQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql';
-
-import { packagesProtectionRuleQueryPayload, packagesProtectionRulesData } from '../mock_data';
+import deletePackagesProtectionRuleMutation from '~/packages_and_registries/settings/project/graphql/mutations/delete_packages_protection_rule.mutation.graphql';
+import {
+  packagesProtectionRuleQueryPayload,
+  packagesProtectionRulesData,
+  deletePackagesProtectionRuleMutationPayload,
+} from '../mock_data';
 
 Vue.use(VueApollo);
 
@@ -21,17 +26,30 @@ describe('Packages protection rules project settings', () => {
   const defaultProvidedValues = {
     projectPath: 'path',
   };
+
+  const $toast = { show: jest.fn() };
+
   const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
   const findTable = () => extendedWrapper(wrapper.findByRole('table', /protected packages/i));
   const findTableBody = () => extendedWrapper(findTable().findAllByRole('rowgroup').at(1));
   const findTableRow = (i) => extendedWrapper(findTableBody().findAllByRole('row').at(i));
+  const findTableRowButtonDelete = (i) => findTableRow(i).findByRole('button', { name: /delete/i });
   const findTableLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
   const findProtectionRuleForm = () => wrapper.findComponent(PackagesProtectionRuleForm);
   const findAddProtectionRuleButton = () =>
     wrapper.findByRole('button', { name: /add package protection rule/i });
+  const findAlert = () => wrapper.findByRole('alert');
+  const findModal = () => wrapper.findComponent(GlModal);
 
   const mountComponent = (mountFn = shallowMount, provide = defaultProvidedValues, config) => {
     wrapper = mountFn(PackagesProtectionRules, {
+      stubs: {
+        SettingsBlock,
+        GlModal: true,
+      },
+      mocks: {
+        $toast,
+      },
       provide,
       ...config,
     });
@@ -40,14 +58,24 @@ describe('Packages protection rules project settings', () => {
   const createComponent = ({
     mountFn = shallowMount,
     provide = defaultProvidedValues,
-    resolver = jest.fn().mockResolvedValue(packagesProtectionRuleQueryPayload()),
+    packagesProtectionRuleQueryResolver = jest
+      .fn()
+      .mockResolvedValue(packagesProtectionRuleQueryPayload()),
+    deletePackagesProtectionRuleMutationResolver = jest
+      .fn()
+      .mockResolvedValue(deletePackagesProtectionRuleMutationPayload()),
+    config = {},
   } = {}) => {
-    const requestHandlers = [[packagesProtectionRuleQuery, resolver]];
+    const requestHandlers = [
+      [packagesProtectionRuleQuery, packagesProtectionRuleQueryResolver],
+      [deletePackagesProtectionRuleMutation, deletePackagesProtectionRuleMutationResolver],
+    ];
 
     fakeApollo = createMockApollo(requestHandlers);
 
     mountComponent(mountFn, provide, {
       apolloProvider: fakeApollo,
+      ...config,
     });
   };
 
@@ -92,10 +120,12 @@ describe('Packages protection rules project settings', () => {
     });
 
     it('calls graphql api query', () => {
-      const resolver = jest.fn().mockResolvedValue(packagesProtectionRuleQueryPayload());
-      createComponent({ resolver });
+      const packagesProtectionRuleQueryResolver = jest
+        .fn()
+        .mockResolvedValue(packagesProtectionRuleQueryPayload());
+      createComponent({ packagesProtectionRuleQueryResolver });
 
-      expect(resolver).toHaveBeenCalledWith(
+      expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledWith(
         expect.objectContaining({ projectPath: defaultProvidedValues.projectPath }),
       );
     });
@@ -118,10 +148,12 @@ describe('Packages protection rules project settings', () => {
       });
 
       it('calls initial graphql api query with pagination information', () => {
-        const resolver = jest.fn().mockResolvedValue(packagesProtectionRuleQueryPayload());
-        createComponent({ resolver });
+        const packagesProtectionRuleQueryResolver = jest
+          .fn()
+          .mockResolvedValue(packagesProtectionRuleQueryPayload());
+        createComponent({ packagesProtectionRuleQueryResolver });
 
-        expect(resolver).toHaveBeenCalledWith(
+        expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledWith(
           expect.objectContaining({
             projectPath: defaultProvidedValues.projectPath,
             first: 10,
@@ -130,7 +162,7 @@ describe('Packages protection rules project settings', () => {
       });
 
       describe('when button "Previous" is clicked', () => {
-        const resolver = jest
+        const packagesProtectionRuleQueryResolver = jest
           .fn()
           .mockResolvedValueOnce(
             packagesProtectionRuleQueryPayload({
@@ -149,7 +181,7 @@ describe('Packages protection rules project settings', () => {
           extendedWrapper(findPagination()).findByRole('button', { name: 'Previous' });
 
         beforeEach(async () => {
-          createComponent({ mountFn: mountExtended, resolver });
+          createComponent({ mountFn: mountExtended, packagesProtectionRuleQueryResolver });
 
           await waitForPromises();
 
@@ -157,8 +189,8 @@ describe('Packages protection rules project settings', () => {
         });
 
         it('sends a second graphql api query with new pagination params', () => {
-          expect(resolver).toHaveBeenCalledTimes(2);
-          expect(resolver).toHaveBeenLastCalledWith(
+          expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledTimes(2);
+          expect(packagesProtectionRuleQueryResolver).toHaveBeenLastCalledWith(
             expect.objectContaining({
               before: '10',
               last: 10,
@@ -169,7 +201,7 @@ describe('Packages protection rules project settings', () => {
       });
 
       describe('when button "Next" is clicked', () => {
-        const resolver = jest
+        const packagesProtectionRuleQueryResolver = jest
           .fn()
           .mockResolvedValueOnce(packagesProtectionRuleQueryPayload())
           .mockResolvedValueOnce(
@@ -188,7 +220,7 @@ describe('Packages protection rules project settings', () => {
           extendedWrapper(findPagination()).findByRole('button', { name: 'Next' });
 
         beforeEach(async () => {
-          createComponent({ mountFn: mountExtended, resolver });
+          createComponent({ mountFn: mountExtended, packagesProtectionRuleQueryResolver });
 
           await waitForPromises();
 
@@ -196,8 +228,8 @@ describe('Packages protection rules project settings', () => {
         });
 
         it('sends a second graphql api query with new pagination params', () => {
-          expect(resolver).toHaveBeenCalledTimes(2);
-          expect(resolver).toHaveBeenLastCalledWith(
+          expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledTimes(2);
+          expect(packagesProtectionRuleQueryResolver).toHaveBeenLastCalledWith(
             expect.objectContaining({
               after: '10',
               first: 10,
@@ -207,6 +239,164 @@ describe('Packages protection rules project settings', () => {
         });
       });
     });
+
+    describe('table rows', () => {
+      describe('button "Delete"', () => {
+        it('exists in table', async () => {
+          createComponent({ mountFn: mountExtended });
+
+          await waitForPromises();
+
+          expect(findTableRowButtonDelete(0).exists()).toBe(true);
+        });
+
+        describe('when button is clicked', () => {
+          it('binds confirmation modal', async () => {
+            createComponent({ mountFn: mountExtended });
+
+            await waitForPromises();
+
+            const modalId = getBinding(findTableRowButtonDelete(0).element, 'gl-modal');
+
+            expect(findModal().props('modal-id')).toBe(modalId);
+            expect(findModal().props('title')).toBe(
+              'Are you sure you want to delete the package protection rule?',
+            );
+            expect(findModal().text()).toBe(
+              'Users with at least the Developer role for this project will be able to publish, edit, and delete packages.',
+            );
+          });
+        });
+      });
+    });
+  });
+
+  describe('modal "confirmation"', () => {
+    const createComponentAndClickButtonDeleteInTableRow = async ({
+      tableRowIndex = 0,
+      deletePackagesProtectionRuleMutationResolver = jest
+        .fn()
+        .mockResolvedValue(deletePackagesProtectionRuleMutationPayload()),
+    } = {}) => {
+      createComponent({
+        mountFn: mountExtended,
+        deletePackagesProtectionRuleMutationResolver,
+      });
+
+      await waitForPromises();
+
+      findTableRowButtonDelete(tableRowIndex).trigger('click');
+    };
+
+    describe('when modal button "primary" clicked', () => {
+      const clickOnModalPrimaryBtn = () => findModal().vm.$emit('primary');
+
+      it('disables the button when graphql mutation is executed', async () => {
+        await createComponentAndClickButtonDeleteInTableRow();
+
+        await clickOnModalPrimaryBtn();
+
+        expect(findTableRowButtonDelete(0).props().disabled).toBe(true);
+
+        expect(findTableRowButtonDelete(1).props().disabled).toBe(false);
+      });
+
+      it('sends graphql mutation', async () => {
+        const deletePackagesProtectionRuleMutationResolver = jest
+          .fn()
+          .mockResolvedValue(deletePackagesProtectionRuleMutationPayload());
+
+        await createComponentAndClickButtonDeleteInTableRow({
+          deletePackagesProtectionRuleMutationResolver,
+        });
+
+        await clickOnModalPrimaryBtn();
+
+        expect(deletePackagesProtectionRuleMutationResolver).toHaveBeenCalledTimes(1);
+        expect(deletePackagesProtectionRuleMutationResolver).toHaveBeenCalledWith({
+          input: { id: packagesProtectionRulesData[0].id },
+        });
+      });
+
+      it('handles erroneous graphql mutation', async () => {
+        const alertErrorMessage = 'Client error message';
+        const deletePackagesProtectionRuleMutationResolver = jest
+          .fn()
+          .mockRejectedValue(new Error(alertErrorMessage));
+
+        await createComponentAndClickButtonDeleteInTableRow({
+          deletePackagesProtectionRuleMutationResolver,
+        });
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect(findAlert().isVisible()).toBe(true);
+        expect(findAlert().text()).toBe(alertErrorMessage);
+      });
+
+      it('handles graphql mutation with error response', async () => {
+        const alertErrorMessage = 'Server error message';
+        const deletePackagesProtectionRuleMutationResolver = jest.fn().mockResolvedValue({
+          data: {
+            deletePackagesProtectionRule: {
+              packageProtectionRule: null,
+              errors: [alertErrorMessage],
+            },
+          },
+        });
+
+        await createComponentAndClickButtonDeleteInTableRow({
+          deletePackagesProtectionRuleMutationResolver,
+        });
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect(findAlert().isVisible()).toBe(true);
+        expect(findAlert().text()).toBe(alertErrorMessage);
+      });
+
+      it('refetches package protection rules after successful graphql mutation', async () => {
+        const deletePackagesProtectionRuleMutationResolver = jest
+          .fn()
+          .mockResolvedValue(deletePackagesProtectionRuleMutationPayload());
+
+        const packagesProtectionRuleQueryResolver = jest
+          .fn()
+          .mockResolvedValue(packagesProtectionRuleQueryPayload());
+
+        createComponent({
+          mountFn: mountExtended,
+          packagesProtectionRuleQueryResolver,
+          deletePackagesProtectionRuleMutationResolver,
+        });
+
+        await waitForPromises();
+
+        expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledTimes(1);
+
+        await findTableRowButtonDelete(0).trigger('click');
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledTimes(2);
+      });
+
+      it('shows a toast with success message', async () => {
+        await createComponentAndClickButtonDeleteInTableRow();
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect($toast.show).toHaveBeenCalledWith('Package protection rule deleted.');
+      });
+    });
   });
 
   it('does not initially render package protection form', async () => {
@@ -247,12 +437,14 @@ describe('Packages protection rules project settings', () => {
   });
 
   describe('form "add protection rule"', () => {
-    let resolver;
+    let packagesProtectionRuleQueryResolver;
 
     beforeEach(async () => {
-      resolver = jest.fn().mockResolvedValue(packagesProtectionRuleQueryPayload());
+      packagesProtectionRuleQueryResolver = jest
+        .fn()
+        .mockResolvedValue(packagesProtectionRuleQueryPayload());
 
-      createComponent({ resolver, mountFn: mountExtended });
+      createComponent({ packagesProtectionRuleQueryResolver, mountFn: mountExtended });
 
       await waitForPromises();
 
@@ -262,7 +454,7 @@ describe('Packages protection rules project settings', () => {
     it('handles event "submit"', async () => {
       await findProtectionRuleForm().vm.$emit('submit');
 
-      expect(resolver).toHaveBeenCalledTimes(2);
+      expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledTimes(2);
 
       expect(findProtectionRuleForm().exists()).toBe(false);
       expect(findAddProtectionRuleButton().attributes('disabled')).not.toBeDefined();
@@ -271,10 +463,37 @@ describe('Packages protection rules project settings', () => {
     it('handles event "cancel"', async () => {
       await findProtectionRuleForm().vm.$emit('cancel');
 
-      expect(resolver).toHaveBeenCalledTimes(1);
+      expect(packagesProtectionRuleQueryResolver).toHaveBeenCalledTimes(1);
 
       expect(findProtectionRuleForm().exists()).toBe(false);
       expect(findAddProtectionRuleButton().attributes()).not.toHaveProperty('disabled');
     });
   });
+
+  describe('alert "errorMessage"', () => {
+    const findAlertButtonDismiss = () => wrapper.findByRole('button', { name: /dismiss/i });
+
+    it('renders alert and dismisses it correctly', async () => {
+      const alertErrorMessage = 'Error message';
+      createComponent({
+        mountFn: mountExtended,
+        config: {
+          data() {
+            return {
+              alertErrorMessage,
+            };
+          },
+        },
+      });
+
+      await waitForPromises();
+
+      expect(findAlert().isVisible()).toBe(true);
+      expect(findAlert().text()).toBe(alertErrorMessage);
+
+      await findAlertButtonDismiss().trigger('click');
+
+      expect(findAlert().exists()).toBe(false);
+    });
+  });
 });
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
index e49bf8c6131e6e532d65fc7716f386911c31b346..23a1179011d0d6132457f75c79bb58e4d36071a2 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
@@ -138,3 +138,15 @@ export const createPackagesProtectionRuleMutationInput = {
 export const createPackagesProtectionRuleMutationPayloadErrors = [
   'Package name pattern has already been taken',
 ];
+
+export const deletePackagesProtectionRuleMutationPayload = ({
+  packageProtectionRule = { ...packagesProtectionRulesData[0] },
+  errors = [],
+} = {}) => ({
+  data: {
+    deletePackagesProtectionRule: {
+      packageProtectionRule,
+      errors,
+    },
+  },
+});