diff --git a/admin/tool/dataprivacy/classes/expired_contexts_manager.php b/admin/tool/dataprivacy/classes/expired_contexts_manager.php
index e91601f667b..e71422df3b7 100644
--- a/admin/tool/dataprivacy/classes/expired_contexts_manager.php
+++ b/admin/tool/dataprivacy/classes/expired_contexts_manager.php
@@ -51,6 +51,13 @@ abstract class expired_contexts_manager {
      */
     abstract protected function get_expired_contexts();
 
+    /**
+     * Specify with context levels this expired contexts manager is deleting.
+     *
+     * @return int[]
+     */
+    abstract protected function get_context_levels();
+
     /**
      * Flag expired contexts as expired.
      *
@@ -85,28 +92,16 @@ abstract class expired_contexts_manager {
 
         $privacymanager = new \core_privacy\manager();
 
-        $levels = [CONTEXT_USER, CONTEXT_MODULE, CONTEXT_BLOCK, CONTEXT_COURSE];
-        foreach ($levels as $level) {
+        foreach ($this->get_context_levels() as $level) {
 
             $expiredcontexts = expired_context::get_records_by_contextlevel($level, expired_context::STATUS_APPROVED);
 
             foreach ($expiredcontexts as $expiredctx) {
 
-                $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
-                if (!$context) {
-                    api::delete_expired_context($expiredctx->get('contextid'));
+                if (!$this->delete_expired_context($privacymanager, $expiredctx)) {
                     continue;
                 }
 
-                if (!PHPUNIT_TEST) {
-                    mtrace('Deleting context ' . $context->id . ' - ' .
-                        shorten_text($context->get_context_name(true, true)));
-                }
-
-                $privacymanager->delete_data_for_all_users_in_context($context);
-
-                api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
-
                 $numprocessed += 1;
                 if ($numprocessed == self::DELETE_LIMIT) {
                     // Close the recordset.
@@ -119,6 +114,32 @@ abstract class expired_contexts_manager {
         return $numprocessed;
     }
 
+    /**
+     * Deletes user data from the provided context.
+     *
+     * @param \core_privacy\manager $privacymanager
+     * @param \tool_dataprivacy\expired_context $expiredctx
+     * @return \context|false
+     */
+    protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
+
+        $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
+        if (!$context) {
+            api::delete_expired_context($expiredctx->get('contextid'));
+            return false;
+        }
+
+        if (!PHPUNIT_TEST) {
+            mtrace('Deleting context ' . $context->id . ' - ' .
+                shorten_text($context->get_context_name(true, true)));
+        }
+
+        $privacymanager->delete_data_for_all_users_in_context($context);
+        api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
+
+        return $context;
+    }
+
     /**
      * Check that the requirements to start deleting contexts are satisified.
      *
diff --git a/admin/tool/dataprivacy/classes/expired_course_related_contexts.php b/admin/tool/dataprivacy/classes/expired_course_related_contexts.php
index 24fc5b22c15..f878edff093 100644
--- a/admin/tool/dataprivacy/classes/expired_course_related_contexts.php
+++ b/admin/tool/dataprivacy/classes/expired_course_related_contexts.php
@@ -35,6 +35,15 @@ defined('MOODLE_INTERNAL') || die();
  */
 class expired_course_related_contexts extends \tool_dataprivacy\expired_contexts_manager {
 
+    /**
+     * Course-related context levels.
+     *
+     * @return int[]
+     */
+    protected function get_context_levels() {
+        return [CONTEXT_MODULE, CONTEXT_BLOCK, CONTEXT_COURSE];
+    }
+
     /**
      * Returns a recordset with user context instances that are possibly expired (to be confirmed by get_recordset_callback).
      *
diff --git a/admin/tool/dataprivacy/classes/expired_user_contexts.php b/admin/tool/dataprivacy/classes/expired_user_contexts.php
index a29fc5dd7c6..62d9485e47d 100644
--- a/admin/tool/dataprivacy/classes/expired_user_contexts.php
+++ b/admin/tool/dataprivacy/classes/expired_user_contexts.php
@@ -36,6 +36,15 @@ defined('MOODLE_INTERNAL') || die();
  */
 class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
 
+    /**
+     * Only user level.
+     *
+     * @return int[]
+     */
+    protected function get_context_levels() {
+        return [CONTEXT_USER];
+    }
+
     /**
      * Returns the user context instances that are expired.
      *
@@ -95,4 +104,25 @@ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
 
         return $expiredcontexts;
     }
+
+    /**
+     * Deletes user data from the provided context.
+     *
+     * Overwritten to delete the user.
+     *
+     * @param \core_privacy\manager $privacymanager
+     * @param \tool_dataprivacy\expired_context $expiredctx
+     * @return \context|false
+     */
+    protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
+        if (!$context = parent::delete_expired_context($privacymanager, $expiredctx)) {
+            return false;
+        }
+
+        // Delete the user.
+        $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
+        delete_user($user);
+
+        return $context;
+    }
 }
diff --git a/admin/tool/dataprivacy/classes/task/process_data_request_task.php b/admin/tool/dataprivacy/classes/task/process_data_request_task.php
index b0615c602dd..9a471d6bd70 100644
--- a/admin/tool/dataprivacy/classes/task/process_data_request_task.php
+++ b/admin/tool/dataprivacy/classes/task/process_data_request_task.php
@@ -199,5 +199,10 @@ class process_data_request_task extends adhoc_task {
             }
             mtrace('Message sent to requester: ' . $messagetextdata['username']);
         }
+
+        if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
+            // Delete the user.
+            delete_user($foruser);
+        }
     }
 }
diff --git a/admin/tool/dataprivacy/tests/expired_contexts_test.php b/admin/tool/dataprivacy/tests/expired_contexts_test.php
index 2f1f1b66f12..6aa16a645f9 100644
--- a/admin/tool/dataprivacy/tests/expired_contexts_test.php
+++ b/admin/tool/dataprivacy/tests/expired_contexts_test.php
@@ -77,7 +77,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         // Old course.
         $course2 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => '2']);
         // Ongoing course.
-        $course3 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => time()]);
+        $course3 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => time() + YEARSECS]);
 
         $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
         $this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student');
@@ -90,7 +90,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         $this->assertEquals(2, $numexpired);
         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_EXPIRED]));
 
-        // Approve user1 to be deleted.
+        // Approve user2 to be deleted.
         $user2ctx = \context_user::instance($user2->id);
         $expiredctx = expired_context::get_record(['contextid' => $user2ctx->id]);
         api::set_expired_context_status($expiredctx, expired_context::STATUS_APPROVED);
@@ -108,6 +108,10 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
         $deleted = $expired->delete();
         $this->assertEquals(0, $deleted);
+
+        // The user is deleted.
+        $deleteduser = \core_user::get_user($user2->id, 'id, deleted', IGNORE_MISSING);
+        $this->assertEquals(1, $deleteduser->deleted);
     }
 
     /**