diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php
index 41fa8778eeee7c48027f380095980e8c65eb2c95..127ce39b3c9ba3e2755d3f1341347e31e067fb72 100644
--- a/extension/Classes/Core/Constants.php
+++ b/extension/Classes/Core/Constants.php
@@ -955,6 +955,7 @@ const API_SUBMIT_REASON_SAVE_FORCE = 'save,force';
 const API_LOCK_ACTION_LOCK = 'lock';
 const API_LOCK_ACTION_EXTEND = 'extend';
 const API_LOCK_ACTION_RELEASE = 'release';
+const API_LOCK_ACTION_CHECK = 'check';
 
 const API_ANSWER_STATUS_SUCCESS = 'success';
 const API_ANSWER_STATUS_CONFLICT = 'conflict';
diff --git a/extension/Classes/Core/Form/Dirty.php b/extension/Classes/Core/Form/Dirty.php
index 6c4c42dc6f0eaeac9720e34da74dda66873ea60e..261476060ced81a85325ce36abf419f0eb987f4f 100644
--- a/extension/Classes/Core/Form/Dirty.php
+++ b/extension/Classes/Core/Form/Dirty.php
@@ -117,7 +117,7 @@ class Dirty {
         $recordId = empty($sipVars[SIP_RECORD_ID]) ? 0 : $sipVars[SIP_RECORD_ID];
         if ($recordId == 0) {
             // For r=0 (new) , 'dirty' will always succeed.
-            return [API_STATUS => 'success', API_MESSAGE => ''];
+            return [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
         }
 
         $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM);
@@ -138,6 +138,12 @@ class Dirty {
             case API_LOCK_ACTION_RELEASE:
                 $answer = $this->checkDirtyAndRelease(FORM_SAVE, $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS], $tableVars[F_DIRTY_MODE], $tableVars[F_TABLE_NAME], $tableVars[F_PRIMARY_KEY], $recordId);
                 break;
+            case API_LOCK_ACTION_CHECK:
+                $rcRecordDirty = array();
+                $rcMsg = '';
+                $rc = $this->getCheckDirty($tableVars[F_TABLE_NAME], $recordId, $rcRecordDirty, $rcMsg);
+                $answer = ($rc === LOCK_FOUND_CONFLICT) ? [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $rcMsg] : [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
+                break;
             default;
                 throw new \CodeException("Unknown action: " . $this->client[API_LOCK_ACTION], ERROR_DIRTY_UNKNOWN_ACTION);
         }
@@ -178,7 +184,7 @@ class Dirty {
 
         if (count($recordDirty) == 0) {
             if ($formDirtyMode == DIRTY_MODE_NONE) {
-                $answer = [API_STATUS => 'success', API_MESSAGE => ''];
+                $answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
             } else {
                 // No dirty record found: create lock
                 $answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableVars, $feUser, $rcMd5, $tabUniqId);
@@ -186,7 +192,7 @@ class Dirty {
         } else {
             if ($tabUniqId == $recordDirty[TAB_UNIQ_ID]) {
                 // In case it's the same tab (page reload): OK
-                $answer = [API_STATUS => 'success', API_MESSAGE => ''];
+                $answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
             } else {
                 // Here is probably a conflict.
                 $answer = $this->conflict($recordDirty, $formDirtyMode, $primaryKey);
diff --git a/javascript/src/Dirty.js b/javascript/src/Dirty.js
index 333332e11516c3a379982fbf3b04605529df55af..1246223810ffc1065931250cb84af81e5e890c6a 100644
--- a/javascript/src/Dirty.js
+++ b/javascript/src/Dirty.js
@@ -43,16 +43,23 @@ var QfqNS = QfqNS || {};
         RENEWAL_ENDED: 'dirty.renewal.ended',
         RENEWAL_SUCCESS: 'dirty.renewal.success',
         RENEWAL_DENIED: 'dirty.renewal.denied',
-        RENEWAL_FAILED: 'dirty.renewal.failed'
+        RENEWAL_FAILED: 'dirty.renewal.failed',
+
+        CHECK_STARTED: 'dirty.check.started',
+        CHECK_SUCCESS: 'dirty.check.success',
+        CHECK_FAILED: 'dirty.check.failed',
+        CHECK_ENDED: 'dirty.check.ended',
     };
 
     n.Dirty.ENDPOINT_OPERATIONS = {
-        /** Aquire Lock */
+        /** Acquire Lock */
         LOCK: "lock",
         /** Release Lock */
         RELEASE: "release",
         /** Renew Lock */
-        RENEW: "extend"
+        RENEW: "extend",
+        /** Check Lock */
+        CHECK: "check"
     };
 
     n.Dirty.MINIMUM_TIMER_DELAY_IN_SECONDS = 5;
@@ -330,4 +337,65 @@ var QfqNS = QfqNS || {};
         queryString = $.param(mergedQueryParameterObject);
         return this.dirtyUrl + "?" + queryString;
     };
+
+    /**
+     * Check with the server if record is already locked.
+     *
+     * @param sip {string} sip.
+     * @public
+     */
+    n.Dirty.prototype.check = function (sip, optionalQueryParameters) {
+        var eventData;
+
+        if (!this.dirtyUrl) {
+            n.Log.debug("notify: cannot contact server, no dirtyUrl.");
+            return;
+        }
+        eventData = n.EventEmitter.makePayload(this, null);
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_STARTED, eventData);
+        $.ajax({
+            url: this.makeUrl(sip, n.Dirty.ENDPOINT_OPERATIONS.CHECK, optionalQueryParameters),
+            type: 'GET',
+            cache: false
+        })
+            .done(this.ajaxCheckSuccessHandler.bind(this))
+            .fail(this.ajaxCheckErrorHandler.bind(this));
+    };
+
+    /**
+     * @private
+     * @param data
+     * @param textStatus
+     * @param jqXHR
+     */
+    n.Dirty.prototype.ajaxCheckSuccessHandler = function (data, textStatus, jqXHR) {
+        var eventData = n.EventEmitter.makePayload(this, data);
+        if (data.status && data.status === "success") {
+            this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_SUCCESS, eventData);
+        } else {
+            this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_FAILED, eventData);
+        }
+
+        this.setTimeoutIfRequired(data, eventData);
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_ENDED, eventData);
+    };
+
+    /**
+     * @private
+     * @param jqXHR
+     * @param textStatus
+     * @param errorThrown
+     */
+    n.Dirty.prototype.ajaxCheckErrorHandler = function (jqXHR, textStatus, errorThrown) {
+        var eventData = n.EventEmitter.makePayload(this, null, {
+            textStatus: textStatus,
+            errorThrown: errorThrown,
+            jqXHR: jqXHR
+        });
+
+        this.lockTimeoutInMilliseconds = n.Dirty.NO_LOCK_TIMEOUT;
+
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_FAILED, eventData);
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_ENDED, eventData);
+    };
 })(QfqNS);
diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js
index e292a30b5a4819d9700216d32e6bb5cb0992d3a9..994f8a20dc72d87032607d4b3fba4f4ca42fd73f 100644
--- a/javascript/src/QfqForm.js
+++ b/javascript/src/QfqForm.js
@@ -22,7 +22,7 @@ var QfqNS = QfqNS || {};
     /**
      * Represents a QFQ Form.
      *
-     * QfqForm will autonomously fire an lock `extend` request when the lock expired, but the last change `t_c` has
+     * QfqForm will autonomously fire a lock `extend` request when the lock expired, but the last change `t_c` has
      * been made during the lock period `t_l`. I.e. let `t_{current}` be the current time, an `extend` request is made
      * when
      *
@@ -95,7 +95,8 @@ var QfqNS = QfqNS || {};
         this.dirty.on(n.Dirty.EVENTS.SUCCESS_TIMEOUT, this.dirtyTimeout.bind(this));
         this.dirty.on(n.Dirty.EVENTS.RENEWAL_DENIED, this.dirtyRenewalDenied.bind(this));
         this.dirty.on(n.Dirty.EVENTS.RENEWAL_SUCCESS, this.dirtyRenewalSuccess.bind(this));
-
+        this.dirty.on(n.Dirty.EVENTS.CHECK_SUCCESS, this.dirtyCheckSuccess.bind(this));
+        this.dirty.on(n.Dirty.EVENTS.CHECK_FAILED, this.dirtyCheckFailed.bind(this));
 
         this.form.on('form.changed', this.changeHandler.bind(this));
         this.form.on('form.reset', this.resetHandler.bind(this));
@@ -171,6 +172,8 @@ var QfqNS = QfqNS || {};
                 checkboxes[i].checked = $(this).is(':checked');
             }
         });
+
+        this.dirty.check(this.getSip(), this.getRecordHashMd5AsQueryParameter());
     };
 
     n.QfqForm.prototype.on = n.EventEmitter.onMixin;
@@ -180,6 +183,24 @@ var QfqNS = QfqNS || {};
         // Intentionally empty. May be used later on.
     };
 
+    n.QfqForm.prototype.dirtyCheckSuccess = function (obj) {
+        // Intentionally empty. May be used later on.
+    };
+
+    n.QfqForm.prototype.dirtyCheckFailed = function (obj) {
+        var message = new n.Alert({
+            message: obj.data.message,
+            type: "error",
+            timeout: n.Alert.constants.NO_TIMEOUT,
+            modal: true,
+            buttons: [{
+                label: "Close",
+                eventName: 'close'
+            }]
+        });
+        message.show();
+    };
+
     n.QfqForm.prototype.dirtyRenewalSuccess = function (obj) {
         this.lockAcquired = true;
     };