diff --git a/lib/Drupal/privatemsg/Access/PrivatemsgAccessCheck.php b/lib/Drupal/privatemsg/Access/PrivatemsgAccessCheck.php
new file mode 100644
index 0000000..7016280
--- /dev/null
+++ b/lib/Drupal/privatemsg/Access/PrivatemsgAccessCheck.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\privatemsg\Access\PrivatemsgAccessCheck.
+ */
+
+namespace Drupal\privatemsg\Access;
+
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+
+/**
+ * Determines access to for Privatemsg pages.
+ */
+class PrivatemsgAccessCheck implements StaticAccessCheckInterface{
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $requirementsKey = '_privatemsg_user_access';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array($this->requirementsKey);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request, AccountInterface $account) {
+  
+    static $disabled_displayed = FALSE;
+    $permission = $route->getRequirement('_privatemsg_user_access');
+    if (!$account->id()) { // Disallow anonymous access, regardless of permissions
+      return static::DENY;
+    }
+    if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
+      if (arg(0) == 'messages' && variable_get('privatemsg_display_disabled_message', TRUE) && !$disabled_displayed) {
+        $disabled_displayed = TRUE;
+        drupal_set_message(t('You have disabled Privatemsg and are not allowed to write messages. Go to your <a href="@settings_url">Account settings</a> to enable it again.', array('@settings_url' => url('user/' . $account->uid . '/edit'))), 'warning');
+      }
+      return static::DENY;
+    }
+    if (!user_access($permission, $account)) {
+      return static::DENY;
+    }
+    return static::ALLOW;
+  }
+
+}
diff --git a/lib/Drupal/privatemsg/Entity/PrivatemsgMessage.php b/lib/Drupal/privatemsg/Entity/PrivatemsgMessage.php
new file mode 100644
index 0000000..6d1baa5
--- /dev/null
+++ b/lib/Drupal/privatemsg/Entity/PrivatemsgMessage.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\privatemsg\Entity\PrivatemsgMessasge.
+ */
+
+namespace Drupal\privatemsg\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\privatemsg\PrivatemsgMessasgeInterface;
+
+/**
+ * Defines the Privatemsg entity class.
+ *
+ * @EntityType(
+ *   id = "privatemsg_message",
+ *   label = @Translation("Privatemsg"),
+ *   bundle_label = @Translation("Privatemsg"),
+ *   controllers = {
+ *     "storage" = "Drupal\privatemsg\PrivatemsgStorageController",
+ *     "form" = {
+ *       "new" = "Drupal\privatemsg\Form\PrivatemsgNewFormController"
+ *     }
+ *   },
+ *   base_table = "pm_message",
+ *   fieldable = TRUE,
+ *   translatable = FALSE,
+ *   entity_keys = {
+ *     "id" = "mid",
+ *   },
+ *   route_base_path = "admin/config/messaging/privatemsg"
+ * )
+ */
+class PrivatemsgMessage extends ContentEntityBase implements PrivatemsgMessasgeInterface {
+
+  /**
+   * Implements Drupal\Core\Entity\EntityInterface::id().
+   */
+  public function id() {
+    return $this->get('mid')->value;
+  }
+  
+  /**
+   * {@inheritdoc}
+   */
+  public function getChangedTime() {
+    return $this->get('timestamp')->value;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions($entity_type) {
+    $properties = array(
+      'mid' => array(
+        'type'  => 'integer_field',
+        'label' => t('Private message ID'),
+        'description' => t('Private message ID'),
+      ),
+      'thread_id' => array(
+        'type'  => 'integer_field',
+        'label' => t('Private message thread ID'),
+        'description' => t('Private message thread ID'),
+      ),
+      'author' => array(
+        'type'  => 'entity_reference_field',
+        'label' => t('Private message author'),
+        'description' => t('Private message author'),
+        'settings' => array(
+          'target_type' => 'user',
+          'default_value' => 0,
+        ),
+      ),
+      'subject' => array(
+        'type'  => 'string_field',
+        'label' => t('Private message subject'),
+        'description' => t('Private message subject'),
+        'settings' => array(
+          'default_value' => '',
+        ),
+      ),
+      'body' => array(
+        'type'  => 'string_field',
+        'label' => t('Private message body'),
+        'description' => t('Private message body'),
+      ),
+      'timestamp' => array(
+        'type' => 'integer_field',
+        'label' => t('Private message sent date'),
+        'description' => t('Private message sent date'),
+      ),
+    );
+   
+    return $properties;
+  }
+
+}
diff --git a/lib/Drupal/privatemsg/Form/PrivatemsgNewFormController.php b/lib/Drupal/privatemsg/Form/PrivatemsgNewFormController.php
new file mode 100644
index 0000000..eec7535
--- /dev/null
+++ b/lib/Drupal/privatemsg/Form/PrivatemsgNewFormController.php
@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\Form\PrivatemsgNewFormController.
+ */
+
+namespace Drupal\privatemsg\Form;
+
+use Drupal\Core\Entity\ContentEntityFormController;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for administering a single book's hierarchy.
+ */
+class PrivatemsgNewFormController extends ContentEntityFormController {
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'privatemsg_new';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state, $recipients = '', $subject = '') {
+    $message = $this->entity;
+     // Convert recipients to array of user objects.
+    $unique = FALSE;
+    if (!empty($recipients) && is_string($recipients) || is_int($recipients)) {
+      $unique = TRUE;
+      $recipients = _privatemsg_generate_user_array($recipients);
+    }
+    else {
+      $recipients = array();
+    }
+  
+    // Subject has / encoded twice if clean urls are enabled to get it through
+    // mod_rewrite and the menu system. Decode it once more.
+    $subject = str_replace('%2F', '/', $subject);
+  
+    if (isset($form_state['values'])) {
+      if (isset($form_state['values']['recipient'])) {
+        $recipients_plain = $form_state['values']['recipient'];
+      }
+      $subject   = $form_state['values']['subject'];
+    }
+    else {
+      $to = _privatemsg_get_allowed_recipients($recipients);
+  
+      $recipients_plain = '';
+      if (!empty($to)) {
+        $to_plain = array();
+        $to_title = array();
+        foreach ($to as $recipient) {
+          // Load user(s) with that name
+          $to_user = _privatemsg_parse_userstring($recipient->name);
+          // If the count of duplicated is more than 0 that means the recipient name is used by the other recipient type,
+          // so we should use unique.
+          $to_plain[] = privatemsg_recipient_format($recipient, array('plain' => TRUE, 'unique' => (count($to_user[2]) > 0)));
+          $to_title[] = privatemsg_recipient_format($recipient, array('plain' => TRUE));
+        }
+        $recipients_plain = implode(', ', $to_plain);
+        $recipients_title = implode(', ', $to_title);
+      }
+    }
+  
+    if (!empty($recipients_title)) {
+      drupal_set_title(t('Write new message to %recipient', array('%recipient' => $recipients_title)), PASS_THROUGH);
+    }
+    else {
+      drupal_set_title(t('Write new message'));
+    }
+  
+    $form = array(
+      '#access' => privatemsg_user_access('write privatemsg'),
+    );
+  
+    $form += _privatemsg_form_base_fields($form, $form_state);
+  
+    $description_array = array();
+    foreach (privatemsg_recipient_get_types() as $name => $type) {
+      if (privatemsg_recipient_access($name, 'write')) {
+        $description_array[] = $type['description'];
+      }
+    }
+    $description = t('Enter the recipient, separate recipients with commas.');
+    $description .= theme('item_list', array('items' => $description_array));
+  
+    $form['recipient'] = array(
+      '#type'               => 'textfield',
+      '#title'              => t('To'),
+      '#description'        => $description,
+      '#default_value'      => $recipients_plain,
+      '#required'           => TRUE,
+      '#weight'             => -10,
+      '#size'               => 50,
+      '#autocomplete_path'  => 'messages/autocomplete',
+      // Disable #maxlength, make it configurable by number of recipients, not
+      // their name length.
+      '#after_build'        => array('privatemsg_disable_maxlength'),
+    );
+    $form['subject'] = array(
+      '#type'               => 'textfield',
+      '#title'              => t('Subject'),
+      '#size'               => 50,
+      '#maxlength'          => 255,
+      '#default_value'      => $subject,
+      '#weight'             => -5,
+    );
+  
+    $url = 'messages';
+    if (isset($_REQUEST['destination'])) {
+      $url = $_REQUEST['destination'];
+    }
+  
+    $form['actions']['cancel'] = array(
+      '#value'  => l(t('Cancel'), $url, array('attributes' => array('id' => 'edit-cancel'))),
+      '#weight' => 20,
+    );
+  
+    return parent::form($form, $form_state, $message);
+  }
+
+  
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::submit().
+   */
+  public function validate(array $form, array &$form_state) {
+    parent::validate($form, $form_state);
+    $message = $this->buildEntity($form, $form_state);
+    _privatemsg_send($message);
+  
+    $trimmed_body = trim(truncate_utf8(strip_tags($message->body), 50, TRUE, TRUE));
+    if (empty($message->thread_id) && empty($message->subject) && !empty($trimmed_body)) {
+      $message->subject = $trimmed_body;
+    }
+    // Only parse the user string for a new thread.
+    if (!isset($message->thread_id)) {
+      list($message->recipients, $invalid, $duplicates, $denieds) = _privatemsg_parse_userstring($message->recipient);
+    }
+    else {
+      // Load participants. Limit recipients to visible unless read_all is TRUE.
+      $message->recipients = _privatemsg_load_thread_participants($message->thread_id, $message->read_all ? FALSE : $message->author);
+    }
+  
+    if (!empty($invalid)) {
+      // Display information about invalid recipients.
+      drupal_set_message(t('The following recipients will not receive this private message: @invalid.', array('@invalid' => implode(", ", $invalid))), 'error');
+    }
+    if (!empty($denieds)) {
+      // Display information about denied recipients.
+      drupal_set_message(t('You do not have access to write these recipients: @denieds.', array('@denieds' => implode(", ", $denieds))), 'error');
+    }
+  
+    if (!empty($duplicates)) {
+      // Add JS and CSS to allow choosing the recipient.
+      drupal_add_js(drupal_get_path('module', 'privatemsg') . '/privatemsg-alternatives.js');
+  
+      // Display information about recipients that couldn't be identified
+      // uniquely.
+      $js_duplicates = array();
+      foreach ($duplicates as $string => $duplicate) {
+        $alternatives = array();
+        foreach ($duplicate as $match) {
+          $formatted_match = privatemsg_recipient_format($match, array('plain' => TRUE, 'unique' => TRUE));
+          $js_duplicates[$formatted_match] = $string;
+          $alternatives[] = '<span class="privatemsg-recipient-alternative">' . $formatted_match . '</span>';
+        }
+        // Build a formatted list of possible recipients.
+        $alternatives = theme('item_list', array('items' => $alternatives, 'attributes' => array('class' => array('action-links'))));
+        form_set_error('recipient', '<span class="privatemsg-alternative-description">' . t('The site has multiple recipients named %string. Please choose your intended recipient: !list', array('%string' => $string, '!list' => $alternatives)) . '</span>');
+      }
+  
+      // Also make that information available to the javascript replacement code.
+      drupal_add_js(array('privatemsg_duplicates' => $js_duplicates), 'setting');
+    }
+  
+    $validated = _privatemsg_validate_message($message, TRUE);
+    foreach ($validated['messages'] as $type => $texts) {
+      foreach ($texts as $text) {
+        drupal_set_message($text, $type);
+      }
+    }
+    $this->entity = $message;
+    
+  }
+
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::submit().
+   */
+  public function submit(array $form, array &$form_state) {
+    parent::submit($form, $form_state);
+    $message = $this-entity;
+  
+    // Format each recipient.
+    $recipient_names = array();
+    foreach ($message->recipients as $recipient) {
+      $recipient_names[] = privatemsg_recipient_format($recipient);
+    }
+    try {
+      $message->save;
+      _privatemsg_handle_recipients($message->mid, $message->recipients);
+      drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
+      // Only redirect on new threads.
+      if ($message->mid == $message->thread_id || variable_get('privatemsg_default_redirect_reply', FALSE)) {
+        $redirect = variable_get('privatemsg_default_redirect', '<new-message>');
+        if ($redirect == '<new-message>' || (!empty($_REQUEST['destination']) && $_REQUEST['destination'] == '[new-message]')) {
+          if (!empty($_REQUEST['destination']) && $_REQUEST['destination'] == '[new-message]') {
+            // Remove GET param so that drupal_goto() uses the redirect from
+            // $form_state.
+            unset($_GET['destination']);
+          }
+          // Forward to the new message in the thread.
+          $form_state['redirect'] = array('messages/view/' . $message->thread_id, array('fragment' => 'privatemsg-mid-' . $message->mid));
+        }
+        elseif (!empty($redirect)) {
+          $form_state['redirect'] = $redirect;
+        }
+      }
+    }
+    catch (Exception $e) {
+      if (error_displayable()) {
+        require_once DRUPAL_ROOT . '/includes/errors.inc';
+        $variables = _drupal_decode_exception($e);
+        drupal_set_message(t('Failed to send a message to !recipients. %type: !message in %function (line %line of %file).', array('!recipients' => implode(', ', $recipient_names)) + $variables), 'error');
+      }
+      else {
+        drupal_set_message(t('Failed to send a message to !recipients. Contact your site administrator.', array('!recipients' => implode(', ', $recipient_names))), 'error');
+      }
+    }
+  }
+
+
+}
diff --git a/lib/Drupal/privatemsg/PrivatemsgMessasgeInterface.php b/lib/Drupal/privatemsg/PrivatemsgMessasgeInterface.php
new file mode 100644
index 0000000..2108935
--- /dev/null
+++ b/lib/Drupal/privatemsg/PrivatemsgMessasgeInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\privatemsg\PrivatemsgMessasgeInterface.
+ */
+
+namespace Drupal\privatemsg;
+
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+
+/**
+ * Provides an interface defining a PrivatemsgMessasge entity.
+ */
+interface PrivatemsgMessasgeInterface extends ContentEntityInterface, EntityChangedInterface {
+
+}
diff --git a/lib/Drupal/privatemsg/PrivatemsgStorageController.php b/lib/Drupal/privatemsg/PrivatemsgStorageController.php
new file mode 100644
index 0000000..3f28f89
--- /dev/null
+++ b/lib/Drupal/privatemsg/PrivatemsgStorageController.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\privatemsg\PrivatemsgStorageController.
+ */
+
+namespace Drupal\privatemsg;
+
+use Drupal\Core\Entity\FieldableDatabaseStorageController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Controller class for privatemsg.
+ *
+ * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
+ * required special handling for privatemsg entities.
+ */
+class PrivatemsgStorageController extends FieldableDatabaseStorageController {
+
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
+   */
+  protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
+    $queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision);
+
+    // Create an array of nodes for each content type and pass this to the
+    // object type specific callback. To preserve backward-compatibility we
+    // pass on BC decorators to node-specific hooks, while we pass on the
+    // regular entity objects else.
+    $typed_nodes = array();
+    foreach ($queried_entities as $id => $node) {
+      $typed_nodes[$node->bundle()][$id] = $queried_entities[$id];
+    }
+
+    if ($load_revision) {
+      $this->loadFieldItems($queried_entities, static::FIELD_LOAD_REVISION);
+    }
+    else {
+      $this->loadFieldItems($queried_entities, static::FIELD_LOAD_CURRENT);
+    }
+
+    // Besides the list of nodes, pass one additional argument to
+    // hook_node_load(), containing a list of node types that were loaded.
+    $argument = array_keys($typed_nodes);
+    $this->hookLoadArguments = array($argument);
+
+    // Call hook_entity_load().
+    foreach (\Drupal::moduleHandler()->getImplementations('entity_load') as $module) {
+      $function = $module . '_entity_load';
+      $function($queried_entities, $this->entityType);
+    }
+    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
+    // always the queried entities, followed by additional arguments set in
+    // $this->hookLoadArguments.
+    $args = array_merge(array($queried_entities), $this->hookLoadArguments);
+    foreach (\Drupal::moduleHandler()->getImplementations($this->entityType . '_load') as $module) {
+      call_user_func_array($module . '_' . $this->entityType . '_load', $args);
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
+   */
+  protected function postDelete($nodes) {
+    // Delete values from other tables also referencing this node.
+    $ids = array_keys($nodes);
+
+    db_delete('node_access')
+      ->condition('nid', $ids, 'IN')
+      ->execute();
+  }
+
+}
diff --git a/privatemsg.info b/privatemsg.info
deleted file mode 100644
index cf9dfb3..0000000
--- a/privatemsg.info
+++ /dev/null
@@ -1,15 +0,0 @@
-name = Private messages
-description = Allow private messages between users.
-package = Mail
-core = 7.x
-files[] = privatemsg.module
-files[] = privatemsg.test
-files[] = views/views_handler_field_privatemsg_link.inc
-files[] = views/privatemsg.views.inc
-files[] = privatemsg.theme.inc
-files[] = privatemsg.pages.inc
-files[] = privatemsg.admin.inc
-files[] = privatemsg.devel_generate.inc
-configure=admin/config/messaging/privatemsg
-
-test_dependencies[] = token
diff --git a/privatemsg.info.yml b/privatemsg.info.yml
new file mode 100644
index 0000000..f1b1290
--- /dev/null
+++ b/privatemsg.info.yml
@@ -0,0 +1,6 @@
+name: Private messages
+type: module
+description: 'Allow private messages between users.'
+package: Mail
+version: VERSION
+core: 8.x
diff --git a/privatemsg.module b/privatemsg.module
index 5697650..e9a161f 100755
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -218,17 +218,7 @@ function privatemsg_menu() {
     'weight'           => -10,
     'menu_name'        => 'user-menu',
   );
-  $items['messages/new'] = array(
-    'title'            => 'Write new message',
-    'page callback'    => 'drupal_get_form',
-    'page arguments'   => array('privatemsg_new', 2, 3, NULL),
-    'file'             => 'privatemsg.pages.inc',
-    'access callback'  => 'privatemsg_user_access',
-    'access arguments' => array('write privatemsg'),
-    'type'             => MENU_LOCAL_ACTION,
-    'weight'           => -3,
-    'menu_name'        => 'user-menu',
-  );
+
   // Auto-completes available user names & removes duplicates.
   $items['messages/autocomplete'] = array(
     'page callback'    => 'privatemsg_autocomplete',
@@ -328,13 +318,13 @@ function privatemsg_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  *
  * @ingroup api
  */
-function privatemsg_user_access($permission = 'read privatemsg', $account = NULL) {
+function privatemsg_user_access($permission = 'read privatemsg', $op = 'update', $account = NULL) {
   static $disabled_displayed = FALSE;
-  if ( $account === NULL ) {
-    global $user;
-    $account = $user;
+
+  if ($account === NULL) {
+    $account = Drupal::currentUser();
   }
-  if (!$account->uid) { // Disallow anonymous access, regardless of permissions
+  if (!$account->id()) { // Disallow anonymous access, regardless of permissions
     return FALSE;
   }
   if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
@@ -679,6 +669,7 @@ function privatemsg_cron() {
 }
 
 function privatemsg_theme() {
+  debug(\Drupal::entityManager()->getEntityTypeLabels(), 'SIZ', true);
   $templates = array(
     'privatemsg_view'    => array(
       'variables'        => array('message' => NULL),
@@ -834,12 +825,12 @@ function privatemsg_unread_count($account = NULL) {
     global $user;
     $account = $user;
   }
-  if (!isset($counts[$account->uid])) {
-    $counts[$account->uid] = _privatemsg_assemble_query('unread_count', $account)
+  if (!isset($counts[$account->id()])) {
+    $counts[$account->id()] = _privatemsg_assemble_query('unread_count', $account)
       ->execute()
       ->fetchField();
   }
-  return $counts[$account->uid];
+  return $counts[$account->id()];
 }
 
 /**
@@ -1173,7 +1164,7 @@ function privatemsg_sql_unread_count($account) {
   return $query
     ->condition('pmi.deleted', 0)
     ->condition('pmi.is_new', 1)
-    ->condition('pmi.recipient', $account->uid)
+    ->condition('pmi.recipient', $account->id())
     ->condition('pmi.type', array('hidden', 'user'));
 }
 
@@ -1810,16 +1801,6 @@ function _privatemsg_validate_message(&$message, $form = FALSE) {
     }
   }
 
-  // Check if an allowed format is used.
-  if (!filter_access(filter_format_load($message->format), $message->author)) {
-    if ($form) {
-      form_set_error('format', t('You are not allowed to use the specified format.'));
-    }
-    else {
-      $messages['error'][] = t('@user is not allowed to use the specified input format.', array('@user' => privatemsg_recipient_format($message->author, array('plain' => TRUE))));
-    }
-  }
-
   if (empty($message->recipients) || !is_array($message->recipients)) {
     if ($form) {
       form_set_error('recipient', t('You must include at least one valid recipient.'));
@@ -1851,9 +1832,6 @@ function _privatemsg_validate_message(&$message, $form = FALSE) {
     }
   }
 
-  // Verify if message has tokens and user is allowed to use them.
-  $message->has_tokens = privatemsg_user_access('use tokens in privatemsg', $message->author) && count(token_scan($message->subject . $message->body));
-
   $messages = array_merge_recursive(module_invoke_all('privatemsg_message_validate', $message, $form), $messages);
 
   // Check if there are errors in $messages or if $form is TRUE, there are form errors.
@@ -1879,7 +1857,6 @@ function _privatemsg_send($message) {
   $transaction = db_transaction();
   try {
     drupal_alter('privatemsg_message_presave', $message);
-    field_attach_presave('privatemsg_message', $message);
 
     $query = db_insert('pm_index')->fields(array('mid', 'thread_id', 'recipient', 'type', 'is_new', 'deleted'));
     if (isset($message->read_all) && $message->read_all && isset($message->thread_id)) {
@@ -1948,7 +1925,6 @@ function _privatemsg_send($message) {
     $query->execute();
 
     module_invoke_all('privatemsg_message_insert', $message);
-    field_attach_insert('privatemsg_message', $message);
 
   } catch (Exception $exception) {
     $transaction->rollback();
@@ -3123,49 +3099,49 @@ function privatemsg_entity_property_info() {
   return $info;
 }
 
-/**
- * Private message controller, loads private messages.
- */
-class PrivatemsgMessageController extends DrupalDefaultEntityController {
-
-  protected $account = NULL;
-
-  protected function attachLoad(&$messages, $revision_id = FALSE) {
-    global $user;
-    foreach ($messages as $message) {
-      $message->user = $this->account ? $this->account : $user;
-      // Load author of message.
-      if (!($message->author = user_load($message->author))) {
-        // If user does not exist, load anonymous user.
-        $message->author = user_load(0);
-      }
-    }
-    parent::attachLoad($messages, $revision_id);
-  }
-
-  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
-    // Remove account from conditions.
-    if (isset($conditions['account'])) {
-      $this->account = $conditions['account'];
-      unset($conditions['account']);
-    }
-
-    $query = parent::buildQuery($ids, $conditions, $revision_id);
-    $query->fields('pmi', array('is_new', 'thread_id'));
-    if ($this->account) {
-      $query
-        ->condition('pmi.recipient', $this->account->uid)
-        ->condition('pmi.type', array('hidden', 'user'));
-    }
-    else {
-      // If no account is given, at least limit the result to a single row per
-      // message.
-      $query->distinct();
-    }
-    $query->join('pm_index', 'pmi', "base.mid = pmi.mid");
-    return $query;
-  }
-}
+///**
+// * Private message controller, loads private messages.
+// */
+//class PrivatemsgMessageController extends DrupalDefaultEntityController {
+//
+//  protected $account = NULL;
+//
+//  protected function attachLoad(&$messages, $revision_id = FALSE) {
+//    global $user;
+//    foreach ($messages as $message) {
+//      $message->user = $this->account ? $this->account : $user;
+//      // Load author of message.
+//      if (!($message->author = user_load($message->author))) {
+//        // If user does not exist, load anonymous user.
+//        $message->author = user_load(0);
+//      }
+//    }
+//    parent::attachLoad($messages, $revision_id);
+//  }
+//
+//  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+//    // Remove account from conditions.
+//    if (isset($conditions['account'])) {
+//      $this->account = $conditions['account'];
+//      unset($conditions['account']);
+//    }
+//
+//    $query = parent::buildQuery($ids, $conditions, $revision_id);
+//    $query->fields('pmi', array('is_new', 'thread_id'));
+//    if ($this->account) {
+//      $query
+//        ->condition('pmi.recipient', $this->account->uid)
+//        ->condition('pmi.type', array('hidden', 'user'));
+//    }
+//    else {
+//      // If no account is given, at least limit the result to a single row per
+//      // message.
+//      $query->distinct();
+//    }
+//    $query->join('pm_index', 'pmi', "base.mid = pmi.mid");
+//    return $query;
+//  }
+//}
 
 /**
  * Implements hook_date_formats().
@@ -3508,3 +3484,176 @@ function privatemsg_get_default_setting_ids($account = NULL) {
     'global' => array(0),
   );
 }
+
+
+/**
+ * Check if the current user is allowed to write these recipients.
+ *
+ * @param $recipients
+ *   Array of recipient objects.
+ *
+ * @return
+ *   Array of allowed recipient objects.
+ */
+function _privatemsg_get_allowed_recipients($recipients, $thread_id = NULL) {
+  global $user;
+
+  $usercount = 0;
+  $valid = array();
+  $blocked_messages = &drupal_static('privatemsg_blocked_messages', array());
+  foreach ($recipients as $recipient) {
+    // Allow to pass in normal user objects.
+    if (empty($recipient->type)) {
+      $recipient->type = 'user';
+      $recipient->recipient = $recipient->uid;
+    }
+    if ($recipient->type == 'hidden') {
+      continue;
+    }
+    if (isset($valid[privatemsg_recipient_key($recipient)])) {
+      // We already added the recipient to the list, skip him.
+      continue;
+    }
+    if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
+      // User does not have access to write to this recipient, continue.
+      continue;
+    }
+    if ($recipient->type == 'user' && $recipient->recipient == $user->uid) {
+      // Skip putting author in the recipients list for now.
+      // Will be added if he is the only recipient.
+      $usercount++;
+      continue;
+    }
+    $valid[privatemsg_recipient_key($recipient)] = $recipient;
+  }
+
+  foreach (module_invoke_all('privatemsg_block_message', $user, $valid, array('thread_id' => $thread_id)) as $blocked) {
+    // Unset the recipient.
+    unset($valid[$blocked['recipient']]);
+    // Store blocked messages. These are only displayed if all recipients
+    // are blocked.
+    $blocked_messages[] = $blocked['message'];
+  }
+
+  if (empty($valid) && $usercount >= 1 && empty($blocked_messages)) {
+    // Assume the user sent message to own account as if the usercount is one or
+    // less, then the user sent a message but not to self.
+    $valid['user_' . $user->uid] = $user;
+  }
+  return $valid;
+}
+
+
+/**
+ * Returns the common fields of the reply and new form.
+ */
+function _privatemsg_form_base_fields($form, &$form_state) {
+  $user = Drupal::currentUser();
+
+  if (isset($form_state['privatemsg_preview'])) {
+
+    $preview_subject = '';
+    // Only display subject on preview for new messages.
+    if (empty($form_state['validate_built_message']->thread_id)) {
+      $preview_subject = check_plain($form_state['validate_built_message']->subject);
+      // If message has tokens, replace them.
+      if ($form_state['validate_built_message']->has_tokens) {
+        $preview_subject = privatemsg_token_replace($preview_subject, array('privatemsg_message' => $form_state['validate_built_message']), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
+      }
+    }
+    $form['message_header'] = array(
+      '#type' => 'fieldset',
+      '#title' => !empty($preview_subject) ? $preview_subject : t('Preview'),
+      '#attributes' => array('class' => array('preview')),
+      '#weight' => -20,
+    );
+    $form['message_header']['message_preview'] = $form_state['privatemsg_preview'];
+  }
+
+  $form['author'] = array(
+    '#type' => 'value',
+    '#value' => $user->id(),
+  );
+
+  // The input filter widget looses the format during preview, specify it
+  // explicitly.
+  if (isset($form_state['values']) && array_key_exists('format', $form_state['values'])) {
+    $format = $form_state['values']['format'];
+  }
+  $form['body'] = array(
+    '#type'               => 'text_format',
+    '#title'              => t('Message'),
+    '#rows'               => 6,
+    '#weight'             => -3,
+    '#resizable'          => TRUE,
+    '#format'             => isset($format) ? $format : NULL,
+    '#after_build'        => array('privatemsg_check_format_access'),
+  );
+
+  if (privatemsg_user_access('use tokens in privatemsg') && module_exists('token')) {
+    $form['token'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Token browser'),
+      '#weight' => -1,
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $form['token']['browser'] = array(
+      '#theme' => 'token_tree',
+      '#token_types' => array('privatemsg_message'),
+    );
+  }
+  $form['actions'] = array('#type' => 'actions');
+
+  if (variable_get('privatemsg_display_preview_button', FALSE)) {
+    $form['actions']['preview'] = array(
+      '#type'     => 'submit',
+      '#value'    => t('Preview message'),
+      '#validate' => array('privatemsg_new_validate'),
+      '#submit'   => array('privatemsg_new_preview'),
+      '#weight'   => 10,
+    );
+  }
+
+  $form['actions']['submit'] = array(
+    '#type'     => 'submit',
+    '#value'    => t('Send message'),
+    '#weight'   => 15,
+    '#validate' => array('privatemsg_new_validate'),
+    '#submit'   => array('privatemsg_new_submit'),
+  );
+
+  // Attach field widgets.
+  $message = \Drupal::entityManager()->getStorageController('privatemsg_message')->create(array());
+
+  if (isset($form_state['validate_built_message'])) {
+    $message = $form_state['validate_built_message'];
+  }
+
+  // If a module (e.g. OG) adds a validate or submit callback to the form in
+  // field_attach_form, the form system will not add ours automatically
+  // anymore. Therefore, explicitly adding them here.
+  $form['#submit'] = array('privatemsg_new_submit');
+  $form['#validate'] = array('privatemsg_new_validate');
+
+  field_attach_form($message, $form, $form_state);
+  return $form;
+}
+
+/**
+ * Remove the maxlength attribute from a field.
+ */
+function privatemsg_disable_maxlength($element) {
+  unset($element['#maxlength']);
+  return $element;
+}
+
+/**
+ * After build callback; Hide format widget if user doesn't have permission.
+ */
+function privatemsg_check_format_access($element) {
+  if (isset($element['format'])) {
+    $element['format']['#access'] = privatemsg_user_access('select text format for privatemsg');
+  }
+  return $element;
+}
diff --git a/privatemsg.pages.inc b/privatemsg.pages.inc
index 0dbd4aa..0b5ed6c 100644
--- a/privatemsg.pages.inc
+++ b/privatemsg.pages.inc
@@ -375,14 +375,6 @@ function privatemsg_new($form, &$form_state, $recipients = '', $subject = '') {
 }
 
 /**
- * Remove the maxlength attribute from a field.
- */
-function privatemsg_disable_maxlength($element) {
-  unset($element['#maxlength']);
-  return $element;
-}
-
-/**
  * Form builder function; Write a reply to a thread.
  */
 function privatemsg_form_reply($form, &$form_state, $thread) {
@@ -441,167 +433,6 @@ function privatemsg_form_reply($form, &$form_state, $thread) {
   return $form;
 }
 
-/**
- * Returns the common fields of the reply and new form.
- */
-function _privatemsg_form_base_fields($form, &$form_state) {
-  global $user;
-
-  if (isset($form_state['privatemsg_preview'])) {
-
-    $preview_subject = '';
-    // Only display subject on preview for new messages.
-    if (empty($form_state['validate_built_message']->thread_id)) {
-      $preview_subject = check_plain($form_state['validate_built_message']->subject);
-      // If message has tokens, replace them.
-      if ($form_state['validate_built_message']->has_tokens) {
-        $preview_subject = privatemsg_token_replace($preview_subject, array('privatemsg_message' => $form_state['validate_built_message']), array('sanitize' => TRUE, 'privatemsg-show-span' => FALSE));
-      }
-    }
-    $form['message_header'] = array(
-      '#type' => 'fieldset',
-      '#title' => !empty($preview_subject) ? $preview_subject : t('Preview'),
-      '#attributes' => array('class' => array('preview')),
-      '#weight' => -20,
-    );
-    $form['message_header']['message_preview'] = $form_state['privatemsg_preview'];
-  }
-
-  $form['author'] = array(
-    '#type' => 'value',
-    '#value' => $user,
-  );
-
-  // The input filter widget looses the format during preview, specify it
-  // explicitly.
-  if (isset($form_state['values']) && array_key_exists('format', $form_state['values'])) {
-    $format = $form_state['values']['format'];
-  }
-  $form['body'] = array(
-    '#type'               => 'text_format',
-    '#title'              => t('Message'),
-    '#rows'               => 6,
-    '#weight'             => -3,
-    '#resizable'          => TRUE,
-    '#format'             => isset($format) ? $format : NULL,
-    '#after_build'        => array('privatemsg_check_format_access'),
-  );
-
-  if (privatemsg_user_access('use tokens in privatemsg') && module_exists('token')) {
-    $form['token'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Token browser'),
-      '#weight' => -1,
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    $form['token']['browser'] = array(
-      '#theme' => 'token_tree',
-      '#token_types' => array('privatemsg_message'),
-    );
-  }
-  $form['actions'] = array('#type' => 'actions');
-
-  if (variable_get('privatemsg_display_preview_button', FALSE)) {
-    $form['actions']['preview'] = array(
-      '#type'     => 'submit',
-      '#value'    => t('Preview message'),
-      '#validate' => array('privatemsg_new_validate'),
-      '#submit'   => array('privatemsg_new_preview'),
-      '#weight'   => 10,
-    );
-  }
-
-  $form['actions']['submit'] = array(
-    '#type'     => 'submit',
-    '#value'    => t('Send message'),
-    '#weight'   => 15,
-    '#validate' => array('privatemsg_new_validate'),
-    '#submit'   => array('privatemsg_new_submit'),
-  );
-
-  // Attach field widgets.
-  $message = (object) array();
-  if (isset($form_state['validate_built_message'])) {
-    $message = $form_state['validate_built_message'];
-  }
-
-  // If a module (e.g. OG) adds a validate or submit callback to the form in
-  // field_attach_form, the form system will not add ours automatically
-  // anymore. Therefore, explicitly adding them here.
-  $form['#submit'] = array('privatemsg_new_submit');
-  $form['#validate'] = array('privatemsg_new_validate');
-
-  field_attach_form('privatemsg_message', $message, $form, $form_state);
-  return $form;
-}
-
-/**
- * Check if the current user is allowed to write these recipients.
- *
- * @param $recipients
- *   Array of recipient objects.
- *
- * @return
- *   Array of allowed recipient objects.
- */
-function _privatemsg_get_allowed_recipients($recipients, $thread_id = NULL) {
-  global $user;
-
-  $usercount = 0;
-  $valid = array();
-  $blocked_messages = &drupal_static('privatemsg_blocked_messages', array());
-  foreach ($recipients as $recipient) {
-    // Allow to pass in normal user objects.
-    if (empty($recipient->type)) {
-      $recipient->type = 'user';
-      $recipient->recipient = $recipient->uid;
-    }
-    if ($recipient->type == 'hidden') {
-      continue;
-    }
-    if (isset($valid[privatemsg_recipient_key($recipient)])) {
-      // We already added the recipient to the list, skip him.
-      continue;
-    }
-    if (!privatemsg_recipient_access($recipient->type, 'write', $recipient)) {
-      // User does not have access to write to this recipient, continue.
-      continue;
-    }
-    if ($recipient->type == 'user' && $recipient->recipient == $user->uid) {
-      // Skip putting author in the recipients list for now.
-      // Will be added if he is the only recipient.
-      $usercount++;
-      continue;
-    }
-    $valid[privatemsg_recipient_key($recipient)] = $recipient;
-  }
-
-  foreach (module_invoke_all('privatemsg_block_message', $user, $valid, array('thread_id' => $thread_id)) as $blocked) {
-    // Unset the recipient.
-    unset($valid[$blocked['recipient']]);
-    // Store blocked messages. These are only displayed if all recipients
-    // are blocked.
-    $blocked_messages[] = $blocked['message'];
-  }
-
-  if (empty($valid) && $usercount >= 1 && empty($blocked_messages)) {
-    // Assume the user sent message to own account as if the usercount is one or
-    // less, then the user sent a message but not to self.
-    $valid['user_' . $user->uid] = $user;
-  }
-  return $valid;
-}
-
-/**
- * After build callback; Hide format widget if user doesn't have permission.
- */
-function privatemsg_check_format_access($element) {
-  if (isset($element['format'])) {
-    $element['format']['#access'] = privatemsg_user_access('select text format for privatemsg');
-  }
-  return $element;
-}
 
 function privatemsg_new_validate($form, &$form_state) {
   // The actual message that is being sent, we create this during validation and
diff --git a/privatemsg.routing.yml b/privatemsg.routing.yml
new file mode 100644
index 0000000..cbb5bdb
--- /dev/null
+++ b/privatemsg.routing.yml
@@ -0,0 +1,7 @@
+privatemsg.new:
+  path: '/messages/new'
+  defaults:
+    _title: 'Write new message'
+    _entity_form: 'privatemsg_message.new'
+  requirements:
+    _privatemsg_user_access: 'write privatemsg'
diff --git a/privatemsg.services.yml b/privatemsg.services.yml
new file mode 100644
index 0000000..e843084
--- /dev/null
+++ b/privatemsg.services.yml
@@ -0,0 +1,5 @@
+services:
+  access_check.privatemsg:
+    class: Drupal\privatemsg\Access\PrivatemsgAccessCheck
+    tags:
+      - { name: access_check }
