diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
index cd99250a9e5b..06bb91926563 100644
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
@@ -126,10 +126,11 @@ describe('Charts list', () => {
       cy.get('[aria-label="checkbox-on"]').should('have.length', 26);
       cy.getBySel('bulk-select-copy').contains('25 Selected');
       cy.getBySel('bulk-select-action')
-        .should('have.length', 2)
+        .should('have.length', 3)
         .then($btns => {
           expect($btns).to.contain('Delete');
           expect($btns).to.contain('Export');
+          expect($btns).to.contain('Certify');
         });
       cy.getBySel('bulk-select-deselect-all').click();
       cy.get('[aria-label="checkbox-on"]').should('have.length', 0);
@@ -154,10 +155,11 @@ describe('Charts list', () => {
       cy.getBySel('styled-card').click({ multiple: true });
       cy.getBySel('bulk-select-copy').contains('25 Selected');
       cy.getBySel('bulk-select-action')
-        .should('have.length', 2)
+        .should('have.length', 3)
         .then($btns => {
           expect($btns).to.contain('Delete');
           expect($btns).to.contain('Export');
+          expect($btns).to.contain('Certify');
         });
       cy.getBySel('bulk-select-deselect-all').click();
       cy.getBySel('bulk-select-copy').contains('0 Selected');
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
index 322306a4c64e..d30170891170 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
@@ -91,10 +91,11 @@ describe('Dashboards list', () => {
       cy.get('[aria-label="checkbox-on"]').should('have.length', 6);
       cy.getBySel('bulk-select-copy').contains('5 Selected');
       cy.getBySel('bulk-select-action')
-        .should('have.length', 2)
+        .should('have.length', 3)
         .then($btns => {
           expect($btns).to.contain('Delete');
           expect($btns).to.contain('Export');
+          expect($btns).to.contain('Certify');
         });
       cy.getBySel('bulk-select-deselect-all').click();
       cy.get('[aria-label="checkbox-on"]').should('have.length', 0);
@@ -119,10 +120,11 @@ describe('Dashboards list', () => {
       cy.getBySel('styled-card').click({ multiple: true });
       cy.getBySel('bulk-select-copy').contains('5 Selected');
       cy.getBySel('bulk-select-action')
-        .should('have.length', 2)
+        .should('have.length', 3)
         .then($btns => {
           expect($btns).to.contain('Delete');
           expect($btns).to.contain('Export');
+          expect($btns).to.contain('Certify');
         });
       cy.getBySel('bulk-select-deselect-all').click();
       cy.getBySel('bulk-select-copy').contains('0 Selected');
diff --git a/superset-frontend/src/features/bulkUpdate/BulkCertifyModal.test.tsx b/superset-frontend/src/features/bulkUpdate/BulkCertifyModal.test.tsx
new file mode 100644
index 000000000000..1b0faab9f60d
--- /dev/null
+++ b/superset-frontend/src/features/bulkUpdate/BulkCertifyModal.test.tsx
@@ -0,0 +1,171 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import { render, screen, fireEvent } from 'spec/helpers/testing-library';
+import fetchMock from 'fetch-mock';
+import { Chart } from 'src/types/Chart';
+import { Dashboard } from 'src/pages/DashboardList';
+import BulkCertifyModal from './BulkCertifyModal';
+
+const mockedChartProps = {
+  onHide: jest.fn(),
+  refreshData: jest.fn(),
+  addSuccessToast: jest.fn(),
+  addDangerToast: jest.fn(),
+  show: true,
+  selected: [
+    {
+      id: 1,
+      slice_name: 'Chart 1',
+      url: '/chart/1',
+      viz_type: 'table',
+      creator: 'user',
+      changed_on: 'now',
+    },
+    {
+      id: 2,
+      slice_name: 'Chart 2',
+      url: '/chart/2',
+      viz_type: 'line',
+      creator: 'another_user',
+      changed_on: 'then',
+    },
+  ] as Chart[],
+  resourceName: 'chart' as 'chart' | 'dashboard',
+  resourceLabel: 'chart',
+};
+
+const mockedDashboardProps = {
+  onHide: jest.fn(),
+  refreshData: jest.fn(),
+  addSuccessToast: jest.fn(),
+  addDangerToast: jest.fn(),
+  show: true,
+  selected: [
+    {
+      id: 1,
+      dashboard_title: 'Dashboard 1',
+      url: '/dashboard/1',
+      published: true,
+      changed_by_name: 'user',
+      changed_on_delta_humanized: 'a while ago',
+      changed_by: 'user',
+    },
+    {
+      id: 2,
+      dashboard_title: 'Dashboard 2',
+      url: '/dashboard/2',
+      published: false,
+      changed_by_name: 'admin',
+      changed_on_delta_humanized: 'recently',
+      changed_by: 'admin',
+    },
+  ] as Dashboard[],
+  resourceName: 'dashboard' as 'chart' | 'dashboard',
+  resourceLabel: 'dashboard',
+};
+
+describe('BulkCertifyModal', () => {
+  afterEach(() => {
+    fetchMock.reset();
+    jest.clearAllMocks();
+  });
+
+  describe('when resourceName is chart', () => {
+    test('should render', () => {
+      const { container } = render(<BulkCertifyModal {...mockedChartProps} />);
+      expect(container).toBeInTheDocument();
+    });
+
+    test('renders the correct title and message for charts', () => {
+      render(<BulkCertifyModal {...mockedChartProps} />);
+      expect(
+        screen.getByText(content =>
+          content.startsWith('You are certifying 2 charts'),
+        ),
+      ).toBeInTheDocument();
+      expect(screen.getByText(/bulk certify charts/i)).toBeInTheDocument();
+    });
+  });
+
+  describe('when resourceName is dashboard', () => {
+    test('should render', () => {
+      const { container } = render(
+        <BulkCertifyModal {...mockedDashboardProps} />,
+      );
+      expect(container).toBeInTheDocument();
+    });
+
+    test('renders the correct title and message for dashboards', () => {
+      render(<BulkCertifyModal {...mockedDashboardProps} />);
+      expect(
+        screen.getByText(content =>
+          content.startsWith('You are certifying 2 dashboards'),
+        ),
+      ).toBeInTheDocument();
+      expect(screen.getByText(/bulk certify dashboards/i)).toBeInTheDocument();
+    });
+  });
+
+  test('calls onHide when the Cancel button is clicked', () => {
+    render(<BulkCertifyModal {...mockedChartProps} />);
+    const cancelButton = screen.getByText('Cancel');
+    fireEvent.click(cancelButton);
+    expect(mockedChartProps.onHide).toHaveBeenCalled();
+  });
+
+  describe('rendering', () => {
+    describe('when resourceName is chart', () => {
+      test('should render', () => {
+        const { container } = render(
+          <BulkCertifyModal {...mockedChartProps} />,
+        );
+        expect(container).toBeInTheDocument();
+      });
+
+      test('renders the correct title and message for charts', () => {
+        render(<BulkCertifyModal {...mockedChartProps} />);
+        expect(
+          screen.getByText(content =>
+            content.startsWith('You are certifying 2 charts'),
+          ),
+        ).toBeInTheDocument();
+        expect(screen.getByText(/bulk certify charts/i)).toBeInTheDocument();
+      });
+    });
+
+    describe('when resourceName is dashboard', () => {
+      test('should render', () => {
+        const { container } = render(
+          <BulkCertifyModal {...mockedDashboardProps} />,
+        );
+        expect(container).toBeInTheDocument();
+      });
+
+      test('renders the correct title and message for dashboards', () => {
+        render(<BulkCertifyModal {...mockedDashboardProps} />);
+        expect(
+          screen.getByText(content =>
+            content.startsWith('You are certifying 2 dashboards'),
+          ),
+        ).toBeInTheDocument();
+        expect(
+          screen.getByText(/bulk certify dashboards/i),
+        ).toBeInTheDocument();
+      });
+    });
+  });
+});
diff --git a/superset-frontend/src/features/bulkUpdate/BulkCertifyModal.tsx b/superset-frontend/src/features/bulkUpdate/BulkCertifyModal.tsx
new file mode 100644
index 000000000000..16b619cfb896
--- /dev/null
+++ b/superset-frontend/src/features/bulkUpdate/BulkCertifyModal.tsx
@@ -0,0 +1,158 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+import { useState, FC } from 'react';
+
+import { t, SupersetClient } from '@superset-ui/core';
+import { FormLabel } from 'src/components/Form';
+import Modal from 'src/components/Modal';
+import { Input } from 'src/components/Input';
+import Button from 'src/components/Button';
+
+import Chart from 'src/types/Chart';
+import { Dashboard } from 'src/pages/DashboardList';
+import { Row, Col } from 'src/components';
+
+interface BulkCertifyModalProps {
+  onHide: () => void;
+  refreshData: () => void;
+  addSuccessToast: (msg: string) => void;
+  addDangerToast: (msg: string) => void;
+  show: boolean;
+  resourceName: 'chart' | 'dashboard';
+  resourceLabel: string;
+  selected: Chart[] | Dashboard[];
+}
+
+const BulkCertifyModal: FC<BulkCertifyModalProps> = ({
+  show,
+  selected = [],
+  resourceName,
+  resourceLabel,
+  onHide,
+  refreshData,
+  addSuccessToast,
+  addDangerToast,
+}) => {
+  const [certifiedBy, setCertifiedBy] = useState<string>('');
+  const [certificationDetails, setCertificationDetails] = useState<string>('');
+
+  const resourceLabelPlural = t(
+    '%s',
+    selected.length > 1 ? `${resourceLabel}s` : resourceLabel,
+  );
+
+  const onSave = async () => {
+    if (!certifiedBy) {
+      addDangerToast(t('Please enter who certified these items'));
+      return;
+    }
+
+    Promise.all(
+      selected.map(item => {
+        const url = `/api/v1/${resourceName}/${item.id}`;
+        const payload = {
+          certified_by: certifiedBy,
+          certification_details: certificationDetails,
+        };
+
+        return SupersetClient.put({
+          url,
+          headers: { 'Content-Type': 'application/json' },
+          jsonPayload: payload,
+        });
+      }),
+    )
+      .then(() => {
+        addSuccessToast(t('Successfully certified %s', resourceLabelPlural));
+      })
+      .catch(() => {
+        addDangerToast(t('Failed to certify %s', resourceLabelPlural));
+      });
+
+    refreshData();
+    onHide();
+    setCertifiedBy('');
+    setCertificationDetails('');
+  };
+
+  return (
+    <Modal
+      title={<h4>{t('Bulk certify %s', resourceLabelPlural)}</h4>}
+      show={show}
+      onHide={() => {
+        setCertifiedBy('');
+        setCertificationDetails('');
+        onHide();
+      }}
+      footer={
+        <div>
+          <Button
+            data-test="modal-cancel-certify-button"
+            buttonStyle="secondary"
+            onClick={onHide}
+          >
+            {t('Cancel')}
+          </Button>
+          <Button
+            data-test="modal-save-certify-button"
+            buttonStyle="primary"
+            onClick={onSave}
+            disabled={!certifiedBy}
+          >
+            {t('Certify')}
+          </Button>
+        </div>
+      }
+    >
+      <Row gutter={16}>
+        <Col xs={24} md={12}>
+          <div className="bulk-certify-text">
+            {t(
+              'You are certifying %s %s',
+              selected.length,
+              resourceLabelPlural,
+            )}
+          </div>
+        </Col>
+      </Row>
+      <Row gutter={16}>
+        <Col xs={24} md={12}>
+          <FormLabel>{t('Certified by')}</FormLabel>
+          <Input
+            value={certifiedBy}
+            onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
+              setCertifiedBy(event.target.value)
+            }
+            placeholder={t('e.g., Data Governance Team')}
+          />
+        </Col>
+        <Col xs={24} md={12}>
+          <FormLabel>{t('Certification details')} (optional)</FormLabel>
+          <Input.TextArea
+            rows={1}
+            value={certificationDetails}
+            onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
+              setCertificationDetails(event.target.value)
+            }
+            placeholder={t('Optional details about the certification')}
+          />
+        </Col>
+      </Row>
+    </Modal>
+  );
+};
+
+export default BulkCertifyModal;
diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx
index 197e0a425f24..710ded444d7a 100644
--- a/superset-frontend/src/pages/ChartList/index.tsx
+++ b/superset-frontend/src/pages/ChartList/index.tsx
@@ -75,6 +75,7 @@ import { findPermission } from 'src/utils/findPermission';
 import { DashboardCrossLinks } from 'src/components/ListView/DashboardCrossLinks';
 import { ModifiedInfo } from 'src/components/AuditInfo';
 import { QueryObjectColumns } from 'src/views/CRUD/types';
+import BulkCertifyModal from 'src/features/bulkUpdate/BulkCertifyModal';
 
 const FlexRowContainer = styled.div`
   align-items: center;
@@ -213,6 +214,8 @@ function ChartList(props: ChartListProps) {
     sshTunnelPrivateKeyPasswordFields,
     setSSHTunnelPrivateKeyPasswordFields,
   ] = useState<string[]>([]);
+  const [showBulkCertifyModal, setShowBulkCertifyModal] = useState(false);
+  const [bulkSelected, setBulkSelected] = useState<Chart[]>([]);
 
   // TODO: Fix usage of localStorage keying on the user id
   const userSettings = dangerouslyGetItemDoNotUse(userId?.toString(), null) as {
@@ -233,6 +236,16 @@ function ChartList(props: ChartListProps) {
     addSuccessToast(t('Chart imported'));
   };
 
+  const openBulkCertifyModal = (selected: Chart[]) => {
+    setBulkSelected(selected);
+    setShowBulkCertifyModal(true);
+  };
+
+  const closeBulkCertifyModal = () => {
+    setShowBulkCertifyModal(false);
+    setBulkSelected([]);
+  };
+
   const canCreate = hasPerm('can_write');
   const canEdit = hasPerm('can_write');
   const canDelete = hasPerm('can_write');
@@ -830,6 +843,14 @@ function ChartList(props: ChartListProps) {
               onSelect: handleBulkChartExport,
             });
           }
+          if (canEdit) {
+            bulkActions.push({
+              key: 'certify',
+              name: t('Certify'),
+              type: 'primary',
+              onSelect: openBulkCertifyModal,
+            });
+          }
           return (
             <ListView<Chart>
               bulkActions={bulkActions}
@@ -865,7 +886,16 @@ function ChartList(props: ChartListProps) {
           );
         }}
       </ConfirmStatusChange>
-
+      <BulkCertifyModal
+        show={showBulkCertifyModal}
+        onHide={closeBulkCertifyModal}
+        selected={bulkSelected}
+        resourceName="chart"
+        resourceLabel={t('chart')}
+        refreshData={refreshData}
+        addSuccessToast={addSuccessToast}
+        addDangerToast={addDangerToast}
+      />
       <ImportModelsModal
         resourceName="chart"
         resourceLabel={t('chart')}
diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx
index a0c9e624df12..c5c729c94e3a 100644
--- a/superset-frontend/src/pages/DashboardList/index.tsx
+++ b/superset-frontend/src/pages/DashboardList/index.tsx
@@ -71,6 +71,7 @@ import { DashboardStatus } from 'src/features/dashboards/types';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import { findPermission } from 'src/utils/findPermission';
 import { ModifiedInfo } from 'src/components/AuditInfo';
+import BulkCertifyModal from 'src/features/bulkUpdate/BulkCertifyModal';
 import { navigateTo } from 'src/utils/navigationUtils';
 
 const PAGE_SIZE = 25;
@@ -195,6 +196,8 @@ function DashboardList(props: DashboardListProps) {
     sshTunnelPrivateKeyPasswordFields,
     setSSHTunnelPrivateKeyPasswordFields,
   ] = useState<string[]>([]);
+  const [showBulkCertifyModal, setShowBulkCertifyModal] = useState(false);
+  const [bulkSelected, setBulkSelected] = useState<Dashboard[]>([]);
 
   const openDashboardImportModal = () => {
     showImportModal(true);
@@ -210,6 +213,16 @@ function DashboardList(props: DashboardListProps) {
     addSuccessToast(t('Dashboard imported'));
   };
 
+  const openBulkCertifyModal = (selected: Dashboard[]) => {
+    setBulkSelected(selected);
+    setShowBulkCertifyModal(true);
+  };
+
+  const closeBulkCertifyModal = () => {
+    setShowBulkCertifyModal(false);
+    setBulkSelected([]);
+  };
+
   // TODO: Fix usage of localStorage keying on the user id
   const userKey = dangerouslyGetItemDoNotUse(user?.userId?.toString(), null);
 
@@ -745,6 +758,14 @@ function DashboardList(props: DashboardListProps) {
               onSelect: handleBulkDashboardExport,
             });
           }
+          if (canEdit) {
+            bulkActions.push({
+              key: 'certify',
+              name: t('Certify'),
+              type: 'primary',
+              onSelect: openBulkCertifyModal,
+            });
+          }
           return (
             <>
               {dashboardToEdit && (
@@ -814,7 +835,16 @@ function DashboardList(props: DashboardListProps) {
           );
         }}
       </ConfirmStatusChange>
-
+      <BulkCertifyModal
+        show={showBulkCertifyModal}
+        onHide={closeBulkCertifyModal}
+        selected={bulkSelected}
+        resourceName="dashboard"
+        resourceLabel={t('dashboard')}
+        refreshData={refreshData}
+        addSuccessToast={addSuccessToast}
+        addDangerToast={addDangerToast}
+      />
       <ImportModelsModal
         resourceName="dashboard"
         resourceLabel={t('dashboard')}