diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
index 876085349d0df193dd6c7e52d8fa52a67a43a16b..c9fb98c5012e764e79868f096944ca7078f9e11b 100644
--- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
+++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
@@ -50,6 +50,9 @@ use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\AbstractMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
+use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
 use TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException;
@@ -636,6 +639,42 @@ class EditDocumentController
             // Recompile the store* values since editconf changed...
             $this->compileStoreData($request);
         }
+
+        // Explicitly require a save operation
+        if ($this->doSave) {
+            $erroneousRecords = $tce->printLogErrorMessages();
+            $messages = [];
+            $table = (string)key($this->editconf);
+            $uidList = GeneralUtility::intExplode(',', (string)key($this->editconf[$table]));
+
+            foreach ($uidList as $uid) {
+                $uid = (int)abs($uid);
+                if (!in_array($table . '.' . $uid, $erroneousRecords, true)) {
+                    $realUidInPayload = ($tceSubstId = array_search($uid, $tce->substNEWwithIDs, true)) !== false ? $tceSubstId : $uid;
+                    $row = $this->data[$table][$uid] ?? $this->data[$table][$realUidInPayload] ?? null;
+                    if ($row !== null) {
+                        $recordTitle = GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $row), $this->getBackendUser()->uc['titleLen']);
+                        $messages[] = sprintf($this->getLanguageService()->getLL('notification.record_saved.message'), $recordTitle);
+                    }
+                }
+            }
+
+            // Add messages to the flash message container only if the request is a save action (excludes "duplicate")
+            if ($messages !== []) {
+                $title = count($messages) === 1 ? 'notification.record_saved.title.singular' : 'notification.record_saved.title.plural';
+                $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(FlashMessageQueue::NOTIFICATION_QUEUE);
+                $flashMessage = GeneralUtility::makeInstance(
+                    FlashMessage::class,
+                    implode(LF, $messages),
+                    $this->getLanguageService()->getLL($title),
+                    AbstractMessage::OK,
+                    true
+                );
+                $defaultFlashMessageQueue->enqueue($flashMessage);
+            }
+        }
+
         // If a document should be duplicated.
         if (isset($parsedBody['_duplicatedoc']) && is_array($this->editconf)) {
             $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT, $request);
@@ -693,7 +732,6 @@ class EditDocumentController
             // Inform the user of the duplication
             $view->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.recordDuplicated'));
         }
-        $tce->printLogErrorMessages();
 
         if ($this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
             || isset($parsedBody['_saveandclosedok'])
diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_alt_doc.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_alt_doc.xlf
index 39115e4547017a66baa1d668e96c780a7cd21d5a..767025a2cfe6687afc5020a6bfc88f13f55bb12b 100644
--- a/typo3/sysext/backend/Resources/Private/Language/locallang_alt_doc.xlf
+++ b/typo3/sysext/backend/Resources/Private/Language/locallang_alt_doc.xlf
@@ -144,6 +144,15 @@
 			<trans-unit id="buttons.pageTsConfig" resname="buttons.pageTsConfig">
 				<source>PageTS Config</source>
 			</trans-unit>
+			<trans-unit id="notification.record_saved.title.singular" resname="notification.record_saved.title.singular">
+				<source>Record saved</source>
+			</trans-unit>
+			<trans-unit id="notification.record_saved.title.plural" resname="notification.record_saved.title.plural">
+				<source>Records saved</source>
+			</trans-unit>
+			<trans-unit id="notification.record_saved.message" resname="notification.record_saved.message">
+				<source>Record "%s" has been successfully saved.</source>
+			</trans-unit>
 		</body>
 	</file>
 </xliff>
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index 4ec85c1ede9f7021e3e7223ec2781278a3585788..fa05d4eb079baefa06b3cc854eb5cff9436fec05 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -9181,10 +9181,13 @@ class DataHandler implements LoggerAwareInterface
     }
 
     /**
-     * Print log error messages from the operations of this script instance
+     * Print log error messages from the operations of this script instance and return a list of the erroneous records
+     *
      * @internal should only be used from within TYPO3 Core
+     *
+     * @return non-empty-string[]
      */
-    public function printLogErrorMessages()
+    public function printLogErrorMessages(): array
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
         $queryBuilder->getRestrictions()->removeAll();
@@ -9205,7 +9208,10 @@ class DataHandler implements LoggerAwareInterface
             )
             ->executeQuery();
 
+        $affectedRecords = [];
         while ($row = $result->fetchAssociative()) {
+            $affectedRecords[] = $row['tablename'] . '.' . $row['recuid'];
+
             $msg = $this->formatLogDetails($row['details'], $row['log_data'] ?? '');
             $msg = $row['error'] . ': ' . $msg;
             $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, '', $row['error'] === SystemLogErrorClassification::WARNING ? FlashMessage::WARNING : FlashMessage::ERROR, true);
@@ -9213,6 +9219,8 @@ class DataHandler implements LoggerAwareInterface
             $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
             $defaultFlashMessageQueue->enqueue($flashMessage);
         }
+
+        return $affectedRecords;
     }
 
     /*****************************