diff --git a/domain_path.info b/domain_path.info
deleted file mode 100644
index e492b35..0000000
--- a/domain_path.info
+++ /dev/null
@@ -1,7 +0,0 @@
-name = Domain Path
-description = Allows separate path aliases per domain.
-package = Domain Access
-dependencies[] = domain
-dependencies[] = path
-core = 7.x
-files[] = domain_path.module
diff --git a/domain_path.info.yml b/domain_path.info.yml
new file mode 100644
index 0000000..63c7568
--- /dev/null
+++ b/domain_path.info.yml
@@ -0,0 +1,10 @@
+name: Domain Path
+type: module
+package: Domain
+description: 'Allow the same path to be used across different domains.'
+core: 8.x
+
+dependencies:
+  - domain
+  - domain_access
+  - domain_config
diff --git a/domain_path.install b/domain_path.install
deleted file mode 100644
index 4034828..0000000
--- a/domain_path.install
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-/**
- * @file
- * Install file for Domain Path.
- */
-
-/**
- * Implements hook_schema().
- */
-function domain_path_schema() {
-  $schema['domain_path'] = array(
-    'description' => 'Stores per-domain path data.',
-    'fields' => array(
-      'dpid' => array(
-        'description' => 'Primary key.',
-        'type' => 'serial',
-        'not null' => TRUE,
-      ),
-      'domain_id' => array(
-        'description' => 'Domain id for this alias',
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'source' => array(
-        'description' => 'System path for the alias',
-        'type' => 'varchar',
-        'length' => '255',
-        'not null' => TRUE,
-      ),
-      'alias' => array(
-        'description' => 'Path alias for the domain',
-        'type' => 'varchar',
-        'length' => '255',
-        'not null' => TRUE,
-      ),
-      'language' => array(
-        'description' => 'Language for thie alias.',
-        'type' => 'varchar',
-        'length' => '12',
-        'not null' => TRUE,
-        'default' => 'und',
-      ),
-      'entity_type' => array(
-        'description' => 'Entity type',
-        'type' => 'varchar',
-        'length' => '80',
-        'not null' => FALSE,
-      ),
-      'entity_id' => array(
-        'description' => 'Entity id',
-        'type' => 'int',
-        'not null' => FALSE,
-      ),
-    ),
-    'primary key' => array('dpid'),
-  );
-  return $schema;
-}
diff --git a/domain_path.js b/domain_path.js
deleted file mode 100644
index bd65eae..0000000
--- a/domain_path.js
+++ /dev/null
@@ -1,91 +0,0 @@
-(function($) {
-
-  Drupal.behaviors.domainPath = {
-    attach: function (context, settings) {
-      Drupal.domainPath.init(context);
-    }
-  };
-
-  /**
-   * Function to check for checked state in domain access options, and only show
-   * domain path fields if the domain access is enabled.
-   */
-  Drupal.domainPath = {
-    init: function(context) {
-      // Initial state for path fields.
-      Drupal.domainPath.showHideAll();
-
-      // The user doesn't have access to the domain settings so go ahead and
-      // show the path field for domain that was set from the form.
-      if (Drupal.settings.hasOwnProperty('domainPath') && Drupal.settings.domainPath.hasOwnProperty('domainId')) {
-        Drupal.domainPath.showHide(Drupal.settings.domainPath.domainId, 'show');
-      }
-
-      // Check to see if Publish to all is checked, if so, show all fields
-      $('#edit-domain-site').bind('change', function() {
-        Drupal.domainPath.showHideAll();
-      });
-
-      // Bind the click event on the checkboxes and check for "checked" then
-      // hide or show the domain path div accordingly.
-      $('#edit-domains .form-checkbox').each(function(index) {
-        $(this).bind('change', function () {
-          Drupal.domainPath.showHideSelectedDomain(this);
-        });
-      });
-    },
-
-    /**
-     * Determine the state of the "Publish to all" checkbox.
-     */
-    isDomainSite: function() {
-      if ($('#edit-domain-site').is(':checked')) {
-        return true;
-      }
-      else {
-        return false;
-      }
-    },
-
-    /**
-     * Update visibility of all domain path inputs.
-     */
-    showHideAll: function() {
-      // Hide all just incase the user doesn't have access to all domains.
-      $('#edit-domain-path .form-type-textfield').css('display', 'none');
-      $('#edit-domains .form-checkbox').each(function(index) {
-        Drupal.domainPath.showHideSelectedDomain(this);
-      });
-    },
-
-    /**
-     * Show or hide the path field for the corresponding domain.
-     */
-    showHide: function(domainId, state) {
-      var pathSelector = '.form-item-domain-path-' + domainId;
-
-      if (state == 'show') {
-        $(pathSelector).show();
-      }
-      else {
-        $(pathSelector).hide();
-      }
-    },
-
-    /**
-     * Show or hide each the path field depending on its corresponding access
-     * options state.
-     */
-    showHideSelectedDomain: function (selector) {
-      var domainId = $(selector).val();
-      var isDomainSite = Drupal.domainPath.isDomainSite();
-
-      if (isDomainSite == true || $(selector).is(':checked')) {
-        Drupal.domainPath.showHide(domainId, 'show');
-      }
-      else {
-        Drupal.domainPath.showHide(domainId);
-      }
-    }
-  }
-})(jQuery);
diff --git a/domain_path.module b/domain_path.module
index ff2fb36..123bd9a 100644
--- a/domain_path.module
+++ b/domain_path.module
@@ -1,546 +1,40 @@
 <?php
 
-/**
- * @file
- *  Path alias handling for multiple domains.
- */
-
-/**
- * Implements hook_permission().
- */
-function domain_path_permission() {
-  $permissions['edit domain paths'] = array(
-    'title' => t('Set domain-specific path aliases'),
-  );
-  return $permissions;
-}
-
-/**
- * Fetch a specific domain path alias from the database.
- *
- * @param $conditions
- *  A string representing the source, a number representing the dpid, or an
- *  array of query conditions.
- *
- * @return
- *  FALSE if no alias was found or an associative array containing the
- *   following keys:
- *    - source: The internal system path.
- *    - alias: The URL alias.
- *    - dpid: Unique path alias identifier.
- *    - domain_id: The domain ID.
- *    - entity_type: The entity type.
- *    - entity_id: The entity ID.
- *    - language: The language of the alias.
- */
-function domain_path_path_load($conditions) {
-  global $_domain;
-
-  if (is_numeric($conditions)) {
-    $conditions = array('dpid' => $conditions);
-  }
-  elseif (is_string($conditions)) {
-    $conditions = array('source' => $conditions);
-  }
-  elseif (!is_array($conditions)) {
-    return FALSE;
-  }
-  if (empty($conditions['domain_id'])) {
-    $conditions['domain_id'] = $_domain['domain_id'];
-  }
-  $select = db_select('domain_path');
-  foreach ($conditions as $field => $value) {
-    $select->condition($field, $value);
-  }
-  return $select
-    ->fields('domain_path')
-    ->execute()
-    ->fetchAssoc();
-}
-
-/**
- * Implements hook_domainpath().
- *
- * Wrapper around the new hook_domain_path() for 7.x.2.
- */
-function domain_path_domainpath($domain_id, &$path, &$options, $original_path) {
-  domain_path_domain_path($domain_id, $path, $options, $original_path);
-}
-
-/**
- * Implements hook_domainpath().
- */
-function domain_path_domain_path($domain_id, &$path, &$options, $original_path) {
-  // Let's not muck with absolute paths.
-  if (!empty($options['absolute'])) {
-    return;
-  }
-
-  $path_language = isset($options['language']->language) ? $options['language']->language : NULL;
-  if ($alias = domain_path_lookup_path('alias', $original_path, $domain_id, $path_language)) {
-    $path = $alias;
-    $options['alias'] = TRUE;
-  }
-  elseif($alias = drupal_get_path_alias($original_path, $path_language)) {
-    $path = $alias;
-    $options['alias'] = TRUE;
-  }
-}
-
-/**
- * Implements hook_url_inbound_alter().
- */
-function domain_path_url_inbound_alter(&$path, $original_path, $path_language) {
-  if ($source = domain_path_lookup_path('source', $original_path, NULL, $path_language)) {
-    $path = $source;
-  }
-}
-
-/**
- * Heavily modeled after drupal_lookup_path().
- *
- * Given an domain specific alias, return its Drupal system URL if one exists. Given a Drupal
- * system URL return one of its domain specific aliases if such a one exists. Otherwise,
- * return FALSE.
- *
- * @param $action
- *   One of the following string values:
- *   - wipe: delete the alias cache.
- *   - alias: return an alias for a given Drupal system path (if one exists).
- *   - source: return the Drupal system URL for a path alias (if one exists).
- * @param $path
- *   The path string to investigate for corresponding domain aliases or system URLs.
- * @param $domain_id
- *  The id of the domain to load the associated path for.
- * @param $path_language
- *   Optional language code to search the path with. Defaults to the page language.
- *   If there's no path defined for that language it will search paths without
- *   language.
- *
- * @return
- *   Either a Drupal system path, an aliased path, or FALSE if no path was
- *   found.
- */
-function domain_path_lookup_path($action, $path = '', $domain_id = NULL, $path_language = NULL) {
-  global $language_url;
-  global $_domain;
-
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
-  }
-  $cache = &$drupal_static_fast['cache'];
-
-  if ($action == 'wipe' || !isset($cache)) {
-    $cache = array(
-      'map' => array(),
-      'no_aliases' => array(),
-      'no_sources' => array(),
-      'domain_id' => $_domain['domain_id'],
-    );
-  }
-
-  // Check and load domain_id and cache if necessary.
-  if ($domain_id == NULL || $domain_id == $cache['domain_id']) {// Null or the same don't do anything.
-    $domain_id = $cache['domain_id'];
-  }
-  elseif($domain = domain_load($domain_id)) {// A differnt domain is wanted.
-    $cache['domain_id'] = $domain_id = $domain['domain_id'];
-  }
-  else {// Bad id? Oh well load current domain again just to make sure.
-    $cache['domain_id'] = $domain_id = $_domain['domain_id'];
-  }
-
-  // If no language is explicitly specified we default to the current URL
-  // language. If we used a language different from the one conveyed by the
-  // requested URL, we might end up being unable to check if there is a path
-  // alias matching the URL path.
-  $path_language = $path_language ? $path_language : $language_url->language;
-  // If the alias has already been loaded, return it.
-  if (isset($cache['map'][$domain_id][$path_language][$path])) {
-    return $cache['map'][$domain_id][$path_language][$path];
-  }
-
-  // Lookup Drupal system path.
-  if ($action == 'source') {
-    // Check $cache['no_sources'] for this $path in case we've already determined that there
-    // isn't a path that has this alias.
-    if (!isset($cache['no_sources'][$domain_id][$path_language][$path])) {
-      $args = array(
-        ':alias' => $path,
-        ':domain_id' => $domain_id,
-        ':language' => $path_language,
-        ':language_none' => LANGUAGE_NONE,
-      );
-
-      // Always get the language-specific alias before the language-neutral
-      // one. For example 'de' is less than 'und' so the order needs to be
-      // ASC, while 'xx-lolspeak' is more than 'und' so the order needs to
-      // be DESC. We also order by pid ASC so that fetchAllKeyed() returns
-      // the most recently created alias for each source. Subsequent queries
-      // using fetchField() must use pid DESC to have the same effect.
-      // For performance reasons, the query builder is not used here.
-      if ($path_language == LANGUAGE_NONE) {
-        unset($args[':language']);
-        $source = db_query("SELECT source FROM {domain_path} WHERE alias = :alias AND domain_id = :domain_id AND language = :language_none ORDER BY dpid DESC", $args)->fetchField();
-      }
-      elseif ($path_language > LANGUAGE_NONE) {
-        $source = db_query("SELECT source FROM {domain_path} WHERE alias = :alias AND domain_id = :domain_id AND language IN (:language, :language_none) ORDER BY language DESC, dpid DESC", $args)->fetchField();
-      }
-      else {
-        $source = db_query("SELECT source FROM {domain_path} WHERE alias = :alias AND domain_id = :domain_id AND language IN (:language, :language_none) ORDER BY language ASC, dpid DESC", $args)->fetchField();
-      }
-
-
-      if ($source) {// Source was found, store and return it.
-        $cache['map'][$domain_id][$path_language][$path] = $source;
-      }
-      else {// No source found store in no_sources for future lookups.
-        $cache['no_sources'][$domain_id][$path_language][$path] = TRUE;
-      }
-
-      return $source;
-    }
-  }
-  // Look up the domain specific alias.
-  elseif ($action == 'alias') {
-    // Check $cache['no_aliases'] for this $path in case we've already determined that there
-    // isn't an alias for this path.
-    if (!isset($cache['no_aliases'][$domain_id][$path_language][$path])) {
-      $args = array(
-        ':source' => $path,
-        ':domain_id' => $domain_id,
-        ':language' => $path_language,
-        ':language_none' => LANGUAGE_NONE,
-      );
-
-      // See the queries above.
-      if ($path_language == LANGUAGE_NONE) {
-        unset($args[':language']);
-        $alias = db_query("SELECT alias FROM {domain_path} WHERE source = :source AND domain_id = :domain_id AND language = :language_none ORDER BY dpid DESC", $args)->fetchField();
-      }
-      elseif ($path_language > LANGUAGE_NONE) {
-        $alias = db_query("SELECT alias FROM {domain_path} WHERE source = :source AND domain_id = :domain_id AND language IN (:language, :language_none) ORDER BY language DESC, dpid DESC", $args)->fetchField();
-      }
-      else {
-        $alias = db_query("SELECT alias FROM {domain_path} WHERE source = :source AND domain_id = :domain_id AND language IN (:language, :language_none) ORDER BY language ASC, dpid DESC", $args)->fetchField();
-      }
-      // Alias was found, store and return it.
-      if ($alias) {
-        $cache['map'][$domain_id][$path_language][$path] = $alias;
-      }
-      // No alias found store in no_aliases for future lookups.
-      else {
-        $cache['no_aliases'][$domain_id][$path_language][$path] = TRUE;
-      }
-
-      return $alias;
-    }
-  }
-
-  // Otherwise always FALSE.
-  return FALSE;
-}
-
-/**
- * Implements hook_form_NODE_FORM_alter().
- */
-function domain_path_form_node_form_alter(&$form, $form_state) {
-  // Custom setting for vertical tabs for domains.
-  // This is in domain 7.x.3 but not 7.x.2.
-
-  if (isset($form['domain'])) {
-    $form['domain']['#group'] = 'additional_settings';
-  }
-
-  $form['domain_path'] = array(
-    '#tree' => TRUE,
-    '#title' => t('Domain-specific paths'),
-    '#type' => 'fieldset',
-    '#group' => 'additional_settings',
-    '#access' => user_access('edit domain paths'),
-    '#attached' => array(
-      'js' => array(drupal_get_path('module', 'domain_path') . '/domain_path.js'),
-    ),
-  );
-
-  // If the user doesn't have access to the domain form element then we will
-  // create a domain id setting for the js so that it will know which path
-  // field to display.
-  if (!user_access('publish to any assigned domain') && !user_access('set domain access')) {
-    $domain_id = FALSE;
-
-    // Only set the default domain id as the js domain id when the user can
-    // publish from default domain but not when the user can publish from
-    // assigned domains.
-    if (user_access('publish from default domain') && !user_access('publish from assigned domain')) {
-      $default_domain = domain_default();
-      $domain_id = $default_domain['domain_id'];
-    }
-    // Set the js domain id to the current domain for everything else.
-    else {
-      global $_domain;
-      $domain_id = $_domain['domain_id'];
-    }
-
-    $form['domain_path']['#attached']['js'][] = array(
-      'data' => array('domainPath' => array('domainId' => $domain_id)),
-      'type' => 'setting',
-    );
-  }
-
-  $paths = array();
-  $domains = domain_domains();
-  $current = t('<none>');
-
-  $path = $nid = FALSE;
-  if (!empty($form['#node']->nid)) {
-    $current = drupal_get_path_alias('node/' . $form['#node']->nid);
-    $nid = $form['#node']->nid;
-  }
-
-  $form['domain_path']['current'] = array(
-    '#type' => 'item',
-    '#title' => t('Current alias'),
-    '#markup' => check_plain($current),
-    '#weight' => -5,
-  );
-  $show_delete = FALSE;
-  foreach ($domains as $domain_id => $domain) {
-    $path = FALSE;
-    if ($nid) {
-      $path = domain_path_lookup_path('alias', 'node/' . $nid, $domain_id);
-    }
-
-    $default = '';
-    if ($path) {
-      $show_delete = TRUE;
-    }
-    // TODO: Only exposed checked domains?
-    $form['domain_path'][$domain_id] = array(
-      '#type' => 'textfield',
-      '#title' => check_plain($domain['path']),
-      '#default_value' => $path ? $path : $default,
-      '#access' => user_access('edit domain paths'),
-    );
-  }
-  $form['domain_path']['domain_path_delete'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Delete domain-specific aliases'),
-    '#default_value' => FALSE,
-    '#access' => $show_delete,
-    '#weight' => -1,
-  );
-}
-
-/**
- * Implements hook_node_validate().
- */
-function domain_path_node_validate($node, $form, &$form_state) {
-  // No validatio on node delete, alias delete, or alias override.
-  $op = $form_state['values']['op'];
-  $delete = $form_state['values']['domain_path']['domain_path_delete'];
-  if (!empty($delete) || $op == t('Delete')) {
-    return;
-  }
-  $paths = $form_state['values']['domain_path'];
-  unset($paths['domain_path_delete']);
-  $alias = $form_state['values']['path']['alias'];
-  // Make sure we don't duplicate anything.
-  foreach ($paths as $domain_id => $path) {
-    $key = ($domain_id == -1) ? 0 : $domain_id;
-    if (!empty($path) && $path == $alias) {
-      form_set_error("domain_path][$key", t('Domain path %alias may not match the default path alias. You may leave the element blank.', array('%alias' => $path)));
-      $set_message = TRUE;
-    }
-    elseif (!empty($path)) {
-      $query = db_query("SELECT COUNT(dpid) FROM {domain_path}
-        WHERE domain_id = :domain_id
-        AND alias = :alias
-        AND language = :language
-        AND source != :source",
-        array(
-          ':domain_id' => $key,
-          ':alias' => $path,
-          ':language' => isset($node->language) ? $node->language : LANGUAGE_NONE,
-          ':source' => "node/$node->nid",
-        )
-      );
-      if ($query->fetchField() > 0) {
-        $domain = domain_lookup($domain_id);
-        form_set_error("domain_path][$key", t('Domain path %alias matches an existing domain path alias for %domain.', array('%alias' => $path, '%domain' => $domain['path'])));
-      }
-    }
-  }
-}
 
 /**
- * Implements hook_node_insert().
+ * Implements hook_field_widget_info_alter().
  */
-function domain_path_node_insert($node) {
-  if (empty($node->domain_path)) {
-    return;
-  }
-
-  // If not set to revert, then save changes.
-  if (empty($node->domain_path['domain_path_delete'])) {
-    $paths = array();
-    unset($node->domain_path['domain_path_delete']);
-    foreach ($node->domain_path as $domain_id => $alias) {
-      // Attempt to retreive the path.
-      $conditions = array(
-        'source' => "node/$node->nid",
-        'language' => $node->language,
-        'domain_id' => $domain_id,
-      );
-      $path = domain_path_path_load($conditions);
-      if (!$path) {
-        $path = array();
-      }
-
-      // Set the path alias.
-      $path['alias'] = $alias;
-
-      // Fill in missing pieces.
-      $path += array(
-        'dpid' => NULL,
-        'domain_id' => $domain_id,
-        'source' => "node/$node->nid",
-        'language' => isset($node->language) ? $node->language : LANGUAGE_NONE,
-        'entity_type' => 'node',
-        'entity_id' => $node->nid,
-      );
-
-      // Delete old alias if user erased it.
-      if (!empty($path['dpid']) && empty($path['alias'])) {
-        domain_path_path_delete($path['dpid']);
-      }
-      elseif (!empty($path['alias'])) {
-        $paths[$domain_id] = $path;
-      }
-    }
-    // Save the paths.
-    if (!empty($paths)) {
-      domain_path_save_paths($paths);
-    }
+function domain_path_field_widget_info_alter(&$widgets) {
+  $moduleHandler = \Drupal::service('module_handler');
+  if ($moduleHandler->moduleExists('pathauto')){
+    $widgets['path']['class'] = '\Drupal\domain_path\PathautoWidget';
   }
-  // Reverted delete all domain path aliases.
   else {
-    domain_path_node_delete($node);
+    $widgets['path']['class'] = '\Drupal\domain_path\PathWidget';
   }
 }
 
 /**
- * Save a domain path alias to the database.
- *
- * @param $path
- *  An associative array containing the following keys:
- *  - source: The internal system path.
- *  - domain_id: The domain ID (required if dpid is not set).
- *  - alias: The URL alias.
- *  - dpid: (optional) Unique domain path alias identifier.
- *  - language: (optional) The language of the alias.
- * @param $reset
- *  A boolean value to reset the domain_path_lookup_path static cache.
- *  (Useful if you are saving multiple paths at once.)
+ * Implements hook_field_info_alter().
  */
-function domain_path_path_save(&$path, $reset = TRUE) {
-  $path += array('language' => LANGUAGE_NONE);
-
-  // Load the stored alias, if any.
-  if (!empty($path['dpid']) && !isset($path['original'])) {
-    $path['original'] = domain_path_path_load($path['dpid']);
-  }
-
-  if (empty($path['dpid'])) {
-    drupal_write_record('domain_path', $path);
-    module_invoke_all('domain_path_insert', $path);
+function domain_path_field_info_alter(&$info) {
+  $moduleHandler = \Drupal::service('module_handler');
+  if ($moduleHandler->moduleExists('pathauto')) {
+    $info['path']['class'] = '\Drupal\domain_path\PathautoItem';
   }
   else {
-    drupal_write_record('domain_path', $path, array('dpid'));
-    module_invoke_all('domain_path_update', $path);
-  }
-
-  // Clear internal properties.
-  unset($path['original']);
-
-  if ($reset) {
-    // Rebuild the node alias.
-    drupal_static_reset('domain_path_lookup_path');
-  }
-}
-
-/**
- * A helper function to save multiple domain paths at once.
- *
- * @param $paths
- *  An array of path aliases.
- *
- * @see domain_path_path_save()
- */
-function domain_path_save_paths($paths) {
-  if (!empty($paths)) {
-    foreach ($paths as $path) {
-      domain_path_path_save($path, FALSE);
-    }
-  }
-  // Rebuild the node alias.
-  drupal_static_reset('domain_path_lookup_path');
-}
-
-/**
- * Delete a domain path alias.
- *
- * @param $criteria
- *  A number representing the dpid or an array of criteria.
- *  (Must contain domain_id if dpid is not set.)
- */
-function domain_path_path_delete($criteria) {
-  if (!is_array($criteria)) {
-    $criteria = array('dpid' => $criteria);
-  }
-  elseif (empty($criteria['domain_id'])) {
-    return;// Should we put in a watchdog error?
+    $info['path']['class'] = '\Drupal\domain_path\PathItem';
   }
-
-  $path = domain_path_path_load($criteria);
-  $query = db_delete('domain_path');
-  foreach ($criteria as $field => $value) {
-    $query->condition($field, $value);
-  }
-  $query->execute();
-  module_invoke_all('domain_path_delete', $path);
-}
-
-/**
- * Implements hook_node_update().
- */
-function domain_path_node_update($node) {
-  domain_path_node_insert($node);
-}
-
-/**
- * Implements hook_node_delete().
- */
-function domain_path_node_delete($node) {
-  db_delete('domain_path')
-    ->condition('entity_type', 'node')
-    ->condition('entity_id', $node->nid)
-    ->execute();
 }
 
 /**
- * Implements hook_field_extra_fields().
+ * Implements hook_module_implements_alter().
  */
-function domain_path_field_extra_fields() {
-  $extra = array();
-  foreach (node_type_get_names() as $name => $value) {
-    $extra['node'][$name]['form']['domain_path'] = array(
-      'label' => t('Domain paths'),
-      'description' => t('Domain-specific path settings.'),
-      'weight' => 30,
-    );
+function domain_path_module_implements_alter(&$implementations, $hook) {
+  if ($hook == 'field_widget_info_alter') {
+    // Run hook_field_widget_info_alter last so that it overrides pathauto.
+    $group = $implementations['domain_path'];
+    unset($implementations['domain_path']);
+    $implementations['domain_path'] = $group;
   }
-  return $extra;
 }
diff --git a/domain_path.services.yml b/domain_path.services.yml
new file mode 100644
index 0000000..33ed0bc
--- /dev/null
+++ b/domain_path.services.yml
@@ -0,0 +1,9 @@
+services:
+  domain_path.alias_storage:
+    class: Drupal\domain_path\AliasStorage
+    arguments: ['@database', '@module_handler', '@domain.negotiator']
+    tags:
+      - { name: backend_overridable }
+
+  path.alias_storage:
+    alias: domain_path.alias_storage
diff --git a/src/AliasStorage.php b/src/AliasStorage.php
new file mode 100644
index 0000000..0a1a40b
--- /dev/null
+++ b/src/AliasStorage.php
@@ -0,0 +1,362 @@
+<?php
+
+namespace Drupal\domain_path;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Path\AliasStorage as CoreAliasStorage;
+use Drupal\domain\DomainNegotiatorInterface;
+
+/**
+ * Overrides AliasStorage.
+ */
+class AliasStorage extends CoreAliasStorage {
+
+  /**
+   * The table for the url_alias storage.
+   */
+  const TABLE = 'domain_path';
+
+  const ALL_AFFILIATES = 0;
+
+  protected $domain_id;
+
+  protected $entity;
+
+  /**
+   * The domain negotiator.
+   *
+   * @var \Drupal\domain\DomainNegotiatorInterface
+   */
+  protected $domain_negotiator;
+
+  public function __construct(Connection $connection, ModuleHandlerInterface $module_handler, DomainNegotiatorInterface $domain_negotiator) {
+    $this->domain_negotiator = $domain_negotiator;
+    parent::__construct($connection, $module_handler);
+  }
+
+  public function setDomainId($domain_id) {
+    $this->domain_id = (int) $domain_id;
+    return $this;
+  }
+
+  public function getDomainId() {
+    // If no domain id has been set, use the currently active one.
+    if (is_null($this->domain_id)) {
+      $this->domain_id = (int) $this->domain_negotiator->getActiveDomain()->getDomainId();
+    }
+    return $this->domain_id;
+  }
+
+  public function setAllAffiliates() {
+    $this->setDomainId(static::ALL_AFFILIATES);
+    return $this;
+  }
+
+  public function setEntity(EntityInterface $entity) {
+    $this->entity = $entity;
+    return $this;
+  }
+
+  public function getEntity() {
+    return $this->entity;
+  }
+
+  public function getEntityType() {
+    $entity = $this->getEntity();
+    if (!empty($entity)) {
+      return $entity->getEntityType()->id();
+    }
+    return NULL;
+  }
+
+  public function getEntityId() {
+    $entity = $this->getEntity();
+    if (!empty($entity)) {
+      return $entity->id();
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save($source, $alias, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, $pid = NULL) {
+
+    if ($source[0] !== '/') {
+      throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $source));
+    }
+
+    if ($alias[0] !== '/') {
+      throw new \InvalidArgumentException(sprintf('Alias path %s has to start with a slash.', $alias));
+    }
+
+    $fields = array(
+      'source' => $source,
+      'alias' => $alias,
+      'langcode' => $langcode,
+      'domain_id' => $this->getDomainId(),
+      'entity_type' => $this->getEntityType(),
+      'entity_id' => $this->getEntityId(),
+    );
+
+    // Insert or update the alias.
+    if (empty($pid)) {
+      $try_again = FALSE;
+      try {
+        $query = $this->connection->insert(static::TABLE)
+          ->fields($fields);
+        $pid = $query->execute();
+      }
+      catch (\Exception $e) {
+        // If there was an exception, try to create the table.
+        if (!$try_again = $this->ensureTableExists()) {
+          // If the exception happened for other reason than the missing table,
+          // propagate the exception.
+          throw $e;
+        }
+      }
+      // Now that the table has been created, try again if necessary.
+      if ($try_again) {
+        $query = $this->connection->insert(static::TABLE)
+          ->fields($fields);
+        $pid = $query->execute();
+      }
+
+      $fields['pid'] = $pid;
+      $operation = 'insert';
+    }
+    else {
+      // Fetch the current values so that an update hook can identify what
+      // exactly changed.
+      try {
+        $original = $this->connection->query('SELECT source, alias, langcode FROM {' . static::TABLE . '} WHERE pid = :pid', array(':pid' => $pid))
+          ->fetchAssoc();
+      }
+      catch (\Exception $e) {
+        $this->catchException($e);
+        $original = FALSE;
+      }
+      $fields['pid'] = $pid;
+      $query = $this->connection->update(static::TABLE)
+        ->fields($fields)
+        ->condition('pid', $pid);
+      $pid = $query->execute();
+      $fields['original'] = $original;
+      $operation = 'update';
+    }
+    if ($pid) {
+      // @todo Switch to using an event for this instead of a hook.
+      $this->moduleHandler->invokeAll('path_' . $operation, array($fields));
+      Cache::invalidateTags(['route_match']);
+      return $fields;
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple($conditions) {
+    // parent::load but without the range restriction.
+    $select = $this->connection->select(static::TABLE);
+    foreach ($conditions as $field => $value) {
+      if ($field == 'source' || $field == 'alias') {
+        // Use LIKE for case-insensitive matching.
+        $select->condition($field, $this->connection->escapeLike($value), 'LIKE');
+      }
+      else {
+        $select->condition($field, $value);
+      }
+    }
+    try {
+      return $select
+        ->fields(static::TABLE)
+        ->orderBy('pid', 'DESC')
+        ->execute()
+        ->fetchAll();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function lookupPathAlias($path, $langcode) {
+    $source = $this->connection->escapeLike($path);
+    $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
+    // See the queries above. Use LIKE for case-insensitive matching.
+    $select = $this->connection->select(static::TABLE)
+      ->fields(static::TABLE, ['alias'])
+      ->condition('source', $source, 'LIKE');
+    if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+      array_pop($langcode_list);
+    }
+    elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+      $select->orderBy('langcode', 'DESC');
+    }
+    else {
+      $select->orderBy('langcode', 'ASC');
+    }
+    $select->orderBy('pid', 'DESC');
+    $select->condition('langcode', $langcode_list, 'IN');
+    // Check existing for the given domain only.
+    $domain_id = $this->getDomainId();
+    if (!is_null($domain_id)) {
+      $select->condition('domain_id', $domain_id, '=');
+    }
+    try {
+      return $select->execute()->fetchField();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function lookupPathSource($path, $langcode) {
+    $alias = $this->connection->escapeLike($path);
+    $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
+
+    // See the queries above. Use LIKE for case-insensitive matching.
+    $select = $this->connection->select(static::TABLE)
+      ->fields(static::TABLE, ['source'])
+      ->condition('alias', $alias, 'LIKE');
+    if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+      array_pop($langcode_list);
+    }
+    elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+      $select->orderBy('langcode', 'DESC');
+    }
+    else {
+      $select->orderBy('langcode', 'ASC');
+    }
+
+    $select->orderBy('pid', 'DESC');
+    $select->condition('langcode', $langcode_list, 'IN');
+
+    // Check existing for the given domain only.
+    $domain_id = $this->getDomainId();
+    if (!is_null($domain_id)) {
+      $select->condition('domain_id', $domain_id, '=');
+    }
+
+    try {
+      return $select->execute()->fetchField();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function aliasExists($alias, $langcode, $source = NULL) {
+    // Use LIKE and NOT LIKE for case-insensitive matching.
+    $query = $this->connection->select(static::TABLE)
+      ->condition('alias', $this->connection->escapeLike($alias), 'LIKE')
+      ->condition('langcode', $langcode);
+    if (!empty($source)) {
+      $query->condition('source', $this->connection->escapeLike($source), 'NOT LIKE');
+    }
+
+    $query->addExpression('1');
+    $query->range(0, 1);
+
+    // Check existing for the given domain only.
+    $domain_id = $this->getDomainId();
+    if (!is_null($domain_id)) {
+      $query->condition('domain_id', $domain_id, '=');
+    }
+
+    try {
+      return (bool) $query->execute()->fetchField();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function languageAliasExists() {
+    try {
+      return (bool) $this->connection->queryRange('SELECT 1 FROM {' . static::TABLE . '} WHERE langcode <> :langcode', 0, 1, array(':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED))->fetchField();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+      return FALSE;
+    }
+  }
+
+  /**
+   * Defines the schema for the {domain_url_alias} table.
+   */
+  public static function schemaDefinition() {
+    return [
+      'description' => 'Stores per-domain path data.',
+      'fields' => [
+        'pid' => [
+          'description' => 'Primary key.',
+          'type' => 'serial',
+          'not null' => TRUE,
+        ],
+        'domain_id' => [
+          'description' => 'Domain id for this alias',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ],
+        'source' => [
+          'description' => 'System path for the alias',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+        ],
+        'alias' => [
+          'description' => 'Path alias for the domain',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+        ],
+        'langcode' => [
+          'description' => 'Language for the alias.',
+          'type' => 'varchar',
+          'length' => 12,
+          'not null' => TRUE,
+          'default' => 'und',
+        ],
+        'entity_type' => [
+          'description' => 'Entity type',
+          'type' => 'varchar',
+          'length' => 80,
+          'not null' => FALSE,
+        ],
+        'entity_id' => [
+          'description' => 'Entity id',
+          'type' => 'int',
+          'not null' => FALSE,
+        ],
+      ],
+      'primary key' => ['pid'],
+      'indexes' => [
+        'alias_langcode_pid' => ['alias', 'langcode', 'pid'],
+        'source_langcode_pid' => ['source', 'langcode', 'pid'],
+      ],
+    ];
+  }
+
+}
diff --git a/src/PathItem.php b/src/PathItem.php
new file mode 100644
index 0000000..9c8adbe
--- /dev/null
+++ b/src/PathItem.php
@@ -0,0 +1,78 @@
+<?php
+namespace Drupal\domain_path;
+
+use Drupal\path\Plugin\Field\FieldType\PathItem as CorePathItem;
+
+
+
+/**
+ * Extends the default PathItem implementation.
+ */
+class PathItem extends CorePathItem {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave($update) {
+    $entity = $this->getEntity();
+    $values  = \Drupal::service('domain_access.manager')->getAccessValues($entity);
+
+    // NB: do not use $this->pid as there can multiple pids per path.
+
+    // Load all pids for this entity.
+    $source = '/' . $entity->getEntityType()->id() . '/' . $entity->id();
+    $rows = \Drupal::service('path.alias_storage')->loadMultiple(['source' => $source]);
+    $pids = [];
+    foreach ($rows as $row) {
+      $pids[$row->domain_id] = $row->pid;
+    }
+
+    $all_affiliates = $entity->get('field_domain_all_affiliates')->getValue();
+    if (!empty($all_affiliates[0]['value'])) {
+      $values['all'] = AliasStorage::ALL_AFFILIATES;
+    }
+
+    foreach ($values as $domain_id) {
+
+      // Check if its an update.
+      if (isset($pids[$domain_id])) {
+        $pid = $pids[$domain_id];
+        unset($pids[$domain_id]);
+      }
+      else {
+        $pid = NULL;
+      }
+
+      // Only save a non-empty alias.
+      if ($this->alias) {
+        if (is_null($pid)) {
+          // Create a new record.
+          \Drupal::service('path.alias_storage')
+            ->setDomainId($domain_id)
+            ->setEntity($entity)
+            ->save('/' . $entity->urlInfo()
+              ->getInternalPath(), $this->alias, $this->getLangcode());
+        }
+        else {
+          // Update the existing record.
+          \Drupal::service('path.alias_storage')
+            ->setDomainId($domain_id)
+            ->setEntity($entity)
+            ->save('/' . $entity->urlInfo()
+              ->getInternalPath(), $this->alias, $this->getLangcode(), $pid);
+        }
+      }
+
+    }
+
+    // If any pids are left, delete them.
+    if (count($pids) > 0) {
+      foreach ($pids as $pid) {
+        \Drupal::service('path.alias_storage')
+          ->delete(array('pid' => $pid));
+      }
+    }
+
+  }
+
+}
diff --git a/src/PathWidget.php b/src/PathWidget.php
new file mode 100644
index 0000000..f263659
--- /dev/null
+++ b/src/PathWidget.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\domain_path;
+
+use Drupal\path\Plugin\Field\FieldWidget\PathWidget as CorePathWidget;
+
+/**
+ * Extends the core path widget.
+ */
+class PathWidget extends CorePathWidget {
+  use PathWidgetValidatorTrait;
+}
diff --git a/src/PathWidgetValidatorTrait.php b/src/PathWidgetValidatorTrait.php
new file mode 100644
index 0000000..ea02b48
--- /dev/null
+++ b/src/PathWidgetValidatorTrait.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\domain_path;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Supplies validator methods for path widgets.
+ */
+trait PathWidgetValidatorTrait {
+  /**
+   * @inheritdoc
+   */
+  public static function validateFormElement(array &$element, FormStateInterface $form_state) {
+    // Trim the submitted value of whitespace and slashes.
+    $alias = rtrim(trim($element['alias']['#value']), " \\/");
+    if (!empty($alias)) {
+      $form_state->setValueForElement($element['alias'], $alias);
+
+
+      // If 'all affiliates' is checked, check for existence of alias on other 'all affiliates' nodes.
+      $allAffiliates = $form_state->getValue('field_domain_all_affiliates');
+      if (!empty($allAffiliates['value'])) {
+        // Validate that the submitted alias does not exist yet.
+        $is_exists = \Drupal::service('path.alias_storage')
+          ->setAllAffiliates()
+          ->aliasExists($alias, $element['langcode']['#value'], $element['source']['#value']);
+        if ($is_exists) {
+          $form_state->setError($element, t('The alias is already in use by content available to all affiliates.'));
+        }
+      }
+      else if ($domainValues = $form_state->getValue(DOMAIN_ACCESS_FIELD)) {
+        // If domains are checked, check existence of alias on each domain.
+        foreach ($domainValues as $domainValue) {
+          $domain = \Drupal::service('domain.loader')->load($domainValue['target_id']);
+          // Validate that the submitted alias does not exist yet.
+          $is_exists = \Drupal::service('path.alias_storage')
+            ->setDomainId($domain->getDomainId())
+            ->aliasExists($alias, $element['langcode']['#value'], $element['source']['#value']);
+          if ($is_exists) {
+            $form_state->setError($element, t('The alias is already in use on :domain.', [':domain' => $domain->get('name')]));
+          }
+        }
+      }
+      else {
+        // If no domains are checked and 'all affiliates' is unchecked, check current domain only.
+        $domain = \Drupal::service('domain.loader')->loadDefaultDomain();
+        $is_exists = \Drupal::service('path.alias_storage')
+          ->aliasExists($alias, $element['langcode']['#value'], $element['source']['#value']);
+        if ($is_exists) {
+          $form_state->setError($element, t('The alias is already in use on :domain (default domain).', [':domain' => $domain->get('name')]));
+        }
+      }
+
+    }
+
+    if ($alias && $alias[0] !== '/') {
+      $form_state->setError($element, t('The alias needs to start with a slash.'));
+    }
+  }
+}
diff --git a/src/PathautoItem.php b/src/PathautoItem.php
new file mode 100644
index 0000000..1418248
--- /dev/null
+++ b/src/PathautoItem.php
@@ -0,0 +1,24 @@
+<?php
+namespace Drupal\domain_path;
+
+use Drupal\pathauto\PathautoItem as BasePathautoItem;
+
+/**
+ * Extends the default PathautoItem implementation.
+ */
+class PathautoItem extends BasePathautoItem {
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave($update) {
+    // Only allow the parent implementation to act if pathauto will not create
+    // an alias.
+    if ($this->pathauto == PathautoState::SKIP) {
+      PathItem::postSave($update);
+    }
+    $this->get('pathauto')->persist();
+  }
+
+}
diff --git a/src/PathautoWidget.php b/src/PathautoWidget.php
new file mode 100644
index 0000000..dd931cf
--- /dev/null
+++ b/src/PathautoWidget.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\domain_path;
+
+use Drupal\pathauto\PathautoWidget as CorePathautoWidget;
+
+/**
+ * Extends the path auto widget.
+ */
+class PathautoWidget extends CorePathautoWidget {
+  use PathWidgetValidatorTrait;
+}
