diff --git a/content_lock.install b/content_lock.install
new file mode 100644
index 0000000..1e14b8f
--- /dev/null
+++ b/content_lock.install
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @file
+ * Create content_lock table.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function content_lock_schema() {
+  $schema['content_lock'] = array(
+    'description' => 'content lock module table.',
+    'fields' => array(
+      'nid' => array(
+        'description' => 'The primary identifier for a node.',
+        'type' => 'int',
+        'size' => 'normal',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'uid' => array(
+        'description' => 'User that holds the lock.',
+        'type' => 'int',
+        'size' => 'normal',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'timestamp' => array(
+        'description' => 'Time the lock occurred.',
+        'size' => 'normal',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'indexes' => array(
+      'user' => array('uid'),
+    ),
+    'primary key' => array('nid'),
+  );
+
+  return $schema;
+}
diff --git a/content_lock.links.action.yml b/content_lock.links.action.yml
new file mode 100644
index 0000000..0739e35
--- /dev/null
+++ b/content_lock.links.action.yml
@@ -0,0 +1,3 @@
+content_lock.break_lock.node:
+  route_name: content_lock.break_lock.node
+  title: 'Break Lock'
diff --git a/content_lock.module b/content_lock.module
index 33dbba9..38b6d05 100644
--- a/content_lock.module
+++ b/content_lock.module
@@ -19,3 +19,118 @@ function content_lock_help($route_name, RouteMatchInterface $route_match) {
       return $output;
   }
 }
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function content_lock_form_node_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
+  // First, fetch the node id.
+  $node = $form_state->getFormObject()->getEntity();
+  $nid = $node->id();
+
+  // Only lock if we are editing an existing node.
+  if (!is_null($nid)) {
+    $current_user = Drupal::currentUser();
+    $uid = $current_user->id();
+    // This hook function is called twice, first when the page loads
+    // and second when the page submits.
+    // Only perform set and check for lock on inital form load.
+    if (empty($form_state->getUserInput())) {
+      // Check if the node is locked.
+      $db = \Drupal::database();
+      $data = $db->query('SELECT uid, timestamp FROM content_lock WHERE nid = :nid', array(':nid' => $nid))->fetchAssoc();
+
+      // If we get something returned, that means there is lock.
+      if ($data !== FALSE) {
+        $lock_user = \Drupal\user\Entity\User::load($data['uid']);
+        // Don't disable if the current user has the lock.
+        // They may have clicked away and came back.
+        if ($lock_user->id() != $uid) {
+          // Set the message.
+          $message = t('Currently locked for editing by @name. Locked initiated @time ago.', array(
+            '@name' => $lock_user->getDisplayName(),
+            '@time' => \Drupal::service('date.formatter')->formatInterval(time() - $data['timestamp']),
+          ));
+          drupal_set_message($message, 'warning');
+
+          // If the user has permission, and a break lock link.
+          if ($current_user->hasPermission('break content lock')) {
+            $break_url = \Drupal\Core\Url::fromRoute('content_lock.break_lock.node', array('node' => $nid));
+            $message = t('<a href="@break_url">Break lock</a>.', array(
+              '@break_url' => $break_url->toString(),
+            ));
+            drupal_set_message($message, 'warning');
+          }
+
+          // Disable the form so it can't be saved.
+          $form['#disabled'] = TRUE;
+          if (isset($form['actions']['delete'])) {
+            // Do not allow deletion if locked.
+            unset($form['actions']['delete']);
+          }
+        }
+      }
+      else {
+        // Add a lock, if not locked alread.
+        $values = array(
+          'uid' => $uid,
+          'nid' => $nid,
+          'timestamp' => time(),
+        );
+        $db->insert('content_lock')->fields($values)->execute();
+      }
+    }
+    // Add the user ID to the form, so lock can be deleted on form submit.
+    $form['content_lock_uid'] = array(
+      '#type' => 'hidden',
+      '#value' => $uid,
+    );
+    $form['actions']['submit']['#submit'][] = 'content_lock_node_form_submit';
+  }
+}
+
+/**
+ * Submit handler for content_lock.
+ */
+function content_lock_node_form_submit($form, \Drupal\Core\Form\FormStateInterface $form_state) {
+  // Signals editing is finished; remove the lock.
+  $uid = $form_state->getValue('content_lock_uid');
+  if ($uid) {
+    $node = $form_state->getFormObject()->getEntity();
+    $nid = $node->id();
+    $db = \Drupal::database();
+    $db->delete('content_lock')
+      ->condition('uid', $uid)
+      ->condition('nid', $nid)
+      ->execute();
+  }
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_predelete().
+ *
+ * Check if the node attempting to be deleted is locked and prevent deletion.
+ */
+function content_lock_node_predelete(Drupal\Core\Entity\EntityInterface $entity) {
+  $nid = $entity->id();
+
+  $db = \Drupal::database();
+  $data = $db->query('SELECT nid, uid, timestamp FROM content_lock WHERE nid = :nid', array(':nid' => $nid))->fetchAssoc();
+
+  if ($data !== FALSE) {
+    // If the node is locked, set a message and stop deletion.
+    $lock_user = \Drupal\user\Entity\User::load($data['uid']);
+
+    $message = t('@node cannot be deleted because it was locked by @user on @time.', array(
+      '@node' => $entity->getTitle(),
+      '@user' => $lock_user->getDisplayName(),
+      '@time' => \Drupal::service('date.formatter')->format($data['timestamp']),
+    ));
+
+    drupal_set_message($message, 'warning');
+
+    $redirect = new \Drupal\Core\Routing\LocalRedirectResponse('/admin/content');
+    $redirect->send();
+    exit(0);
+  }
+}
diff --git a/content_lock.permissions.yml b/content_lock.permissions.yml
new file mode 100644
index 0000000..0c38aac
--- /dev/null
+++ b/content_lock.permissions.yml
@@ -0,0 +1,3 @@
+break content lock:
+  title: 'Break content lock'
+  description: 'Break a lock on content so that it may be edited.'
diff --git a/content_lock.routing.yml b/content_lock.routing.yml
new file mode 100644
index 0000000..c4412b9
--- /dev/null
+++ b/content_lock.routing.yml
@@ -0,0 +1,9 @@
+content_lock.break_lock.node:
+  path: '/admin/node/break-lock/{node}'
+  defaults:
+    _form: '\Drupal\content_lock\Form\BreakLockForm'
+    _title: 'Break lock'
+  requirements:
+    _permission: 'break content lock'
+  options:
+    _admin_route: TRUE
diff --git a/src/Form/BreakLockForm.php b/src/Form/BreakLockForm.php
new file mode 100644
index 0000000..840bcee
--- /dev/null
+++ b/src/Form/BreakLockForm.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_lock\Form\BreakLockForm.
+ */
+
+namespace Drupal\content_lock\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\node\Entity\Node;
+
+class BreakLockForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'break_lock';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, Node $node = NULL) {
+    $form['#title'] = t('Break Lock');
+    $form['node_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $node->id(),
+    );
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Confirm break lock'),
+    );
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $nid = $form_state->getValue('node_id');
+    $db = \Drupal::database();
+    $db->delete('content_lock')
+      ->condition('nid', $nid)
+      ->execute();
+    drupal_set_message('Lock broken');
+    $this->redirect('entity.node.edit_form', array('node' => $nid))->send();
+  }
+}
