diff --git a/core/modules/path/lib/Drupal/path/Plugin/field/widget/PathWidget.php b/core/modules/path/lib/Drupal/path/Plugin/field/widget/PathWidget.php
new file mode 100644
index 0000000..d4851c9
--- /dev/null
+++ b/core/modules/path/lib/Drupal/path/Plugin/field/widget/PathWidget.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\path\Plugin\field\widget\PathWidget.
+ */
+
+namespace Drupal\path\Plugin\field\widget;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Widget\WidgetBase;
+
+/**
+ * Plugin implementation of the 'path_default' widget.
+ *
+ * @Plugin(
+ *   id = "path_default",
+ *   module = "path",
+ *   label = @Translation("Permalink"),
+ *   field_types = {
+ *     "path"
+ *   }
+ * )
+ */
+class PathWidget extends WidgetBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::formElement().
+   */
+  public function formElement(array $items, $delta, array $element, $langcode, array &$form, array &$form_state) {
+    $entity = $element['#entity'];
+
+    // @todo Consider to move this into path_field_load(). OTOH, that would
+    //   (needlessly?) load URL aliases for every loaded entity. But then again,
+    //   field data is cached, no?
+    $path = array();
+    if (!$entity->isNew()) {
+      $uri = $entity->uri();
+      $conditions = array(
+        'source' => $uri['path'],
+      );
+      if ($langcode != LANGUAGE_NOT_SPECIFIED) {
+        $conditions['langcode'] = $langcode;
+      }
+      if ($path = drupal_container()->get('path.crud')->load($conditions)) {
+        // The field is supposed to store the actual user input.
+        $path['value'] = $path['alias'];
+      }
+      else {
+        $path = $conditions;
+      }
+    }
+    $path += array(
+      'pid' => NULL,
+      'source' => NULL,
+      'value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
+      'langcode' => $langcode,
+    );
+    $language = language_load($langcode);
+
+    $element += array(
+      '#type' => 'container',
+      '#attributes' => array(
+        'class' => array('path-form'),
+      ),
+      '#attached' => array(
+        'library' => array(array('path', 'drupal.path')),
+      ),
+      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
+      '#tree' => TRUE,
+      '#element_validate' => array(array($this, 'validatePath')),
+    );
+    $element['pid'] = array(
+      '#type' => 'value',
+      '#value' => $path['pid'],
+    );
+    // Prepare the site's base URL as field prefix, taking the currently edited
+    // entity language into account. If the language has no path prefix, the
+    // site URL ends with a slash; if there is a prefix, there is no trailing
+    // slash; ensure there is one in all cases.
+    $prefix = trim(url('', array('absolute' => TRUE, 'language' => $language)), '/') . '/';
+    $element['value'] = array(
+      '#type' => 'textfield',
+      '#title' => $element['#title'],
+      '#field_prefix' => check_plain($prefix),
+      '#default_value' => $path['value'],
+      '#required' => $element['#required'],
+      '#maxlength' => 255,
+      '#description' => $element['#description'],
+    );
+    $element['source'] = array(
+      '#type' => 'value',
+      '#value' => $path['source'],
+    );
+    $element['langcode'] = array(
+      '#type' => 'value',
+      '#value' => $path['langcode'],
+    );
+    return $element;
+  }
+
+  /**
+   * Form element validation handler for PathWidget.
+   */
+  public function validatePath(&$element, &$form_state, $form) {
+    if ($element['value']['#value'] !== '') {
+      // Trim the submitted value.
+      $element['value']['#value'] = trim($element['value']['#value']);
+      form_set_value($element['value'], $element['value']['#value'], $form_state);
+
+      // Entity language needs special care. Since the language of the URL alias
+      // depends on the entity language, and the entity language may be switched
+      // right within the same form, we need to conditionally overload the
+      // originally assigned URL alias language.
+      // @todo Remove this after stopping Locale module from abusing the content
+      //   language system.
+      if (isset($form_state['values']['langcode'])) {
+        form_set_value($element['langcode'], $form_state['values']['langcode'], $form_state);
+      }
+
+      // Ensure that the submitted alias does not exist yet.
+      $query = db_select('url_alias')
+        ->condition('alias', $element['value']['#value'])
+        ->condition('langcode', $element['langcode']['#value']);
+      if (!empty($element['source']['#value'])) {
+        $query->condition('source', $element['source']['#value'], '<>');
+      }
+      $query->addExpression('1');
+      $query->range(0, 1);
+      if ($query->execute()->fetchField()) {
+        form_error($element['value'], t('The alias is already in use.'));
+      }
+    }
+  }
+
+}
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php
index 4168275..a5d6a61 100644
--- a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php
+++ b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php
@@ -12,13 +12,6 @@
  */
 class PathAliasTest extends PathTestBase {
 
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('path');
-
   public static function getInfo() {
     return array(
       'name' => 'Path alias functionality',
@@ -140,23 +133,23 @@ function testNodeAlias() {
 
     // Create alias.
     $edit = array();
-    $edit['path[alias]'] = $this->randomName(8);
+    $edit['path[und][0][value]'] = $this->randomName(8);
     $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save'));
 
     // Confirm that the alias works.
-    $this->drupalGet($edit['path[alias]']);
+    $this->drupalGet($edit['path[und][0][value]']);
     $this->assertText($node1->label(), 'Alias works.');
     $this->assertResponse(200);
 
     // Change alias to one containing "exotic" characters.
-    $previous = $edit['path[alias]'];
-    $edit['path[alias]'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
+    $previous = $edit['path[und][0][value]'];
+    $edit['path[und][0][value]'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
       "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
       "éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
     $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save'));
 
     // Confirm that the alias works.
-    $this->drupalGet($edit['path[alias]']);
+    $this->drupalGet($edit['path[und][0][value]']);
     $this->assertText($node1->label(), 'Changed alias works.');
     $this->assertResponse(200);
 
@@ -169,17 +162,17 @@ function testNodeAlias() {
     $node2 = $this->drupalCreateNode();
 
     // Set alias to second test node.
-    // Leave $edit['path[alias]'] the same.
+    // Leave $edit['path[und][0][value]'] the same.
     $this->drupalPost('node/' . $node2->nid . '/edit', $edit, t('Save'));
 
     // Confirm that the alias didn't make a duplicate.
     $this->assertText(t('The alias is already in use.'), 'Attempt to moved alias was rejected.');
 
     // Delete alias.
-    $this->drupalPost('node/' . $node1->nid . '/edit', array('path[alias]' => ''), t('Save'));
+    $this->drupalPost('node/' . $node1->nid . '/edit', array('path[und][0][value]' => ''), t('Save'));
 
     // Confirm that the alias no longer works.
-    $this->drupalGet($edit['path[alias]']);
+    $this->drupalGet($edit['path[und][0][value]']);
     $this->assertNoText($node1->label(), 'Alias was successfully deleted.');
     $this->assertResponse(404);
   }
@@ -204,13 +197,13 @@ function testDuplicateNodeAlias() {
     // Create one node with a random alias.
     $node_one = $this->drupalCreateNode();
     $edit = array();
-    $edit['path[alias]'] = $this->randomName();
+    $edit['path[und][0][value]'] = $this->randomName();
     $this->drupalPost('node/' . $node_one->nid . '/edit', $edit, t('Save'));
 
     // Now create another node and try to set the same alias.
     $node_two = $this->drupalCreateNode();
     $this->drupalPost('node/' . $node_two->nid . '/edit', $edit, t('Save'));
     $this->assertText(t('The alias is already in use.'));
-    $this->assertFieldByXPath("//input[@name='path[alias]' and contains(@class, 'error')]", $edit['path[alias]'], 'Textfield exists and has the error class.');
+    $this->assertFieldByXPath("//input[@name='path[und][0][value]' and contains(@class, 'error')]", $edit['path[und][0][value]'], 'Textfield exists and has the error class.');
   }
 }
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathFieldCRUDTest.php b/core/modules/path/lib/Drupal/path/Tests/PathFieldCRUDTest.php
new file mode 100644
index 0000000..ac54ba4
--- /dev/null
+++ b/core/modules/path/lib/Drupal/path/Tests/PathFieldCRUDTest.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\path\Tests\PathFieldCRUDTest.
+ */
+
+namespace Drupal\path\Tests;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests path field CRUD operations.
+ */
+class PathFieldCRUDTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system', 'field_sql_storage', 'path');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Path field CRUD operations',
+      'description' => 'Tests path field CRUD operations.',
+      'group' => 'Path',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $this->enableModules(array('field', 'node'));
+    $this->installSchema('system', 'url_alias');
+
+    $this->nodeType = (object) array(
+      'type' => 'page',
+      'name' => 'Basic page',
+    );
+    node_type_save($this->nodeType);
+
+    // Create a path field for the node type.
+    $this->field_name = drupal_strtolower($this->randomName());
+    $this->langcode = LANGUAGE_NOT_SPECIFIED;
+    $this->field = array(
+      'field_name' => $this->field_name,
+      'type' => 'path',
+    );
+    field_create_field($this->field);
+    $this->instance = array(
+      'field_name' => $this->field_name,
+      'entity_type' => 'node',
+      'bundle' => $this->nodeType->type,
+      'widget' => array(
+        'type' => 'path_default',
+      ),
+    );
+    field_create_instance($this->instance);
+  }
+
+  /**
+   * Tests a basic CRUD flow for path fields.
+   */
+  function testBasicCRUD() {
+    // Create and save an entity with an alias.
+    $entity = entity_create('node', array(
+      'type' => $this->nodeType->type,
+      'title' => $this->randomName(),
+    ));
+    $edit = array(
+      'value' => 'test-alias',
+    );
+    $entity->{$this->field_name}[$this->langcode][0] = $edit;
+    $entity->save();
+    $uri = $entity->uri();
+
+    // Verify that field data and the URL alias was stored.
+    $data = $entity->{$this->field_name}[$this->langcode][0];
+    $this->assertTrue($data['pid']);
+    $this->assertIdentical($data['value'], $edit['value']);
+    $this->assertIdentical($data['alias'], $edit['value']);
+    $path = $this->container->get('path.crud')->load(array('pid' => $data['pid']));
+    $this->assertIdentical($path['source'], $uri['path']);
+    $this->assertIdentical($path['alias'], $edit['value']);
+
+    // Edit the field value and update the entity.
+    $updated_value = 'updated-alias';
+    $entity->{$this->field_name}[$this->langcode][0]['value'] = $updated_value;
+    $entity->save();
+
+    // Verify that field data and the URL alias was stored.
+    $data = $entity->{$this->field_name}[$this->langcode][0];
+    $this->assertIdentical($data['pid'], $path['pid']);
+    $this->assertIdentical($data['value'], $updated_value);
+    $this->assertIdentical($data['alias'], $updated_value);
+    $path = $this->container->get('path.crud')->load(array('pid' => $data['pid']));
+    $this->assertIdentical($path['source'], $uri['path']);
+    $this->assertIdentical($path['alias'], $updated_value);
+
+    // Verify that there is only one alias.
+    $count = db_query('SELECT COUNT(*) FROM {url_alias} WHERE source = :source', array(
+      ':source' => $uri['path'],
+    ))->fetchField();
+    $this->assertEqual($count, 1);
+
+    // Delete the entity.
+    $entity->delete();
+
+    // Verify that the alias no longer exists.
+    $path = $this->container->get('path.crud')->load(array('pid' => $data['pid']));
+    $this->assertFalse($path);
+    $count = db_query('SELECT COUNT(*) FROM {url_alias} WHERE source = :source', array(
+      ':source' => $uri['path'],
+    ))->fetchField();
+    $this->assertEqual($count, 0);
+  }
+
+  /**
+   * Tests updating of field data after deletion of aliases via URL alias API.
+   */
+  function testPathDelete() {
+    // Create and save an entity with an alias.
+    $entity = entity_create('node', array(
+      'type' => $this->nodeType->type,
+      'title' => $this->randomName(),
+    ));
+    $edit = array(
+      'value' => 'test-alias',
+    );
+    $entity->{$this->field_name}[$this->langcode][0] = $edit;
+    $entity->save();
+    $uri = $entity->uri();
+
+    // Delete the alias.
+    $this->container->get('path.crud')->delete(array('source' => $uri['path']));
+
+    // Reload the entity and verify that the alias no longer exists.
+    $entity = entity_load('node', $entity->id());
+    $this->assertFalse($entity->{$this->field_name});
+  }
+
+}
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php
index 83be7b5..67161cd 100644
--- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php
+++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php
@@ -17,7 +17,7 @@ class PathLanguageTest extends PathTestBase {
    *
    * @var array
    */
-  public static $modules = array('path', 'locale', 'translation');
+  public static $modules = array('locale', 'translation_entity');
 
   public static function getInfo() {
     return array(
@@ -31,18 +31,26 @@ function setUp() {
     parent::setUp();
 
     // Create and login user.
-    $this->web_user = $this->drupalCreateUser(array('edit any page content', 'create page content', 'administer url aliases', 'create url aliases', 'administer languages', 'translate all content', 'access administration pages', 'administer content types'));
+    $this->web_user = $this->drupalCreateUser(array('edit any page content', 'create page content', 'administer url aliases', 'create url aliases', 'administer languages', 'translate any entity', 'access administration pages', 'administer content types'));
     $this->drupalLogin($this->web_user);
 
     // Enable French language.
     $edit = array();
     $edit['predefined_langcode'] = 'fr';
-
     $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
 
     // Enable URL language detection and selection.
     $edit = array('language_interface[enabled][language-url]' => 1);
     $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
+
+    // Enable entity/field translation for the body field.
+    $field = field_info_field('body');
+    $field['translatable'] = TRUE;
+    field_update_field($field);
+    // Enable entity/field translation for the default path field.
+    $field = field_info_field('path');
+    $field['translatable'] = TRUE;
+    field_update_field($field);
   }
 
   /**
@@ -52,45 +60,44 @@ function testAliasTranslation() {
     // Set 'page' content type to enable translation.
     $edit = array(
       'language_configuration[language_hidden]' => FALSE,
+      'language_configuration[translation_entity]' => TRUE,
     );
     $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
     $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.');
-    variable_set('node_type_language_translation_enabled_page', TRUE);
 
-    $english_node = $this->drupalCreateNode(array('type' => 'page'));
+    $node = $this->drupalCreateNode(array('type' => 'page'));
     $english_alias = $this->randomName();
 
     // Edit the node to set language and path.
     $edit = array();
     $edit['langcode'] = 'en';
-    $edit['path[alias]'] = $english_alias;
-    $this->drupalPost('node/' . $english_node->nid . '/edit', $edit, t('Save'));
+    $edit['path[und][0][value]'] = $english_alias;
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
 
     // Confirm that the alias works.
     $this->drupalGet($english_alias);
-    $this->assertText($english_node->label(), 'Alias works.');
+    $this->assertText($node->label(), 'Alias works.');
 
     // Translate the node into French.
-    $this->drupalGet('node/' . $english_node->nid . '/translate');
-    $this->clickLink(t('add translation'));
+    $this->drupalGet('node/' . $node->nid . '/translations');
+    $this->clickLink(t('add'));
     $edit = array();
-    $langcode = LANGUAGE_NOT_SPECIFIED;
-    $edit["title"] = $this->randomName();
-    $edit["body[$langcode][0][value]"] = $this->randomName();
+    $edit['title'] = $this->randomName();
+    $edit['body[fr][0][value]'] = $this->randomName();
     $french_alias = $this->randomName();
-    $edit['path[alias]'] = $french_alias;
+    $edit['path[fr][0][value]'] = $french_alias;
     $this->drupalPost(NULL, $edit, t('Save'));
 
     // Clear the path lookup cache.
     drupal_container()->get('path.alias_manager')->cacheClear();
 
     // Ensure the node was created.
-    $french_node = $this->drupalGetNodeByTitle($edit["title"]);
-    $this->assertTrue(($french_node), 'Node found in database.');
+    $node = $this->drupalGetNodeByTitle($edit["title"]);
+    $this->assertTrue($node, 'Node found in database.');
 
     // Confirm that the alias works.
-    $this->drupalGet('fr/' . $edit['path[alias]']);
-    $this->assertText($french_node->label(), 'Alias for French translation works.');
+    $this->drupalGet('fr/' . $edit['path[fr][0][value]']);
+    $this->assertText($node->label(), 'Alias for French translation works.');
 
     // Confirm that the alias is returned by url(). Languages are cached on
     // many levels, and we need to clear those caches.
@@ -98,8 +105,8 @@ function testAliasTranslation() {
     drupal_static_reset('language_url_outbound_alter');
     drupal_static_reset('language_url_rewrite_url');
     $languages = language_list();
-    $url = url('node/' . $french_node->nid, array('language' => $languages[$french_node->langcode]));
-    $this->assertTrue(strpos($url, $edit['path[alias]']), 'URL contains the path alias.');
+    $url = url('node/' . $node->nid, array('language' => language_load('fr')));
+    $this->assertTrue(strpos($url, $edit['path[fr][0][value]']), 'URL contains the path alias.');
 
     // Confirm that the alias works even when changing language negotiation
     // options. Enable User language detection and selection over URL one.
@@ -124,11 +131,11 @@ function testAliasTranslation() {
     // path alias for French matching the english alias. So the alias manager
     // needs to use the URL language to check whether the alias is valid.
     $this->drupalGet($english_alias);
-    $this->assertText($english_node->label(), 'Alias for English translation works.');
+    $this->assertText($node->body['en'][0]['value'], 'Alias for English translation works.');
 
     // Check that the French alias works.
     $this->drupalGet("fr/$french_alias");
-    $this->assertText($french_node->label(), 'Alias for French translation works.');
+    $this->assertText($node->body['fr'][0]['value'], 'Alias for French translation works.');
 
     // Disable URL language negotiation.
     $edit = array('language_interface[enabled][language-url]' => FALSE);
@@ -136,7 +143,7 @@ function testAliasTranslation() {
 
     // Check that the English alias still works.
     $this->drupalGet($english_alias);
-    $this->assertText($english_node->label(), 'Alias for English translation works.');
+    $this->assertText($node->body['en'][0]['value'], 'Alias for English translation works.');
 
     // Check that the French alias is not available. We check the unprefixed
     // alias because we disabled URL language negotiation above. In this
@@ -147,18 +154,18 @@ function testAliasTranslation() {
 
     // The alias manager has an internal path lookup cache. Check to see that
     // it has the appropriate contents at this point.
-    drupal_container()->get('path.alias_manager')->cacheClear();
-    $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, $french_node->langcode);
-    $this->assertEqual($french_node_path, 'node/' . $french_node->nid, 'Normal path works.');
+    drupal_lookup_path('wipe');
+    $french_node_path = drupal_lookup_path('source', $french_alias, 'fr');
+    $this->assertEqual($french_node_path, 'node/' . $node->nid, 'Normal path works.');
     // Second call should return the same path.
-    $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, $french_node->langcode);
-    $this->assertEqual($french_node_path, 'node/' . $french_node->nid, 'Normal path is the same.');
+    $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, 'fr');
+    $this->assertEqual($french_node_path, 'node/' . $node->nid, 'Normal path is the same.');
 
     // Confirm that the alias works.
-    $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, $french_node->langcode);
+    $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, 'fr');
     $this->assertEqual($french_node_alias, $french_alias, 'Alias works.');
     // Second call should return the same alias.
-    $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, $french_node->langcode);
+    $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, 'fr');
     $this->assertEqual($french_node_alias, $french_alias, 'Alias is the same.');
   }
 }
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php
index bd0d279..ab6a9bd 100644
--- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php
+++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php
@@ -17,7 +17,7 @@ class PathLanguageUiTest extends PathTestBase {
    *
    * @var array
    */
-  public static $modules = array('path', 'locale');
+  public static $modules = array('locale');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php
index 421870d..25bc7b2 100644
--- a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php
+++ b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php
@@ -30,12 +30,13 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    // Create a Tags vocabulary for the Article node type.
+    // Create a Tags vocabulary for the Basic page node type.
     $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => t('Tags'),
       'machine_name' => 'tags',
     ));
     $vocabulary->save();
+    path_add_default_field_instance('taxonomy_term', 'tags');
 
     // Create and login user.
     $web_user = $this->drupalCreateUser(array('administer url aliases', 'administer taxonomy', 'access administration pages'));
@@ -52,35 +53,35 @@ function testTermAlias() {
     $edit = array();
     $edit['name'] = $this->randomName();
     $edit['description[value]'] = $description;
-    $edit['path[alias]'] = $this->randomName();
+    $edit['path[und][0][value]'] = $this->randomName();
     $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add', $edit, t('Save'));
 
     // Confirm that the alias works.
-    $this->drupalGet($edit['path[alias]']);
+    $this->drupalGet($edit['path[und][0][value]']);
     $this->assertText($description, 'Term can be accessed on URL alias.');
 
     // Change the term's URL alias.
     $tid = db_query("SELECT tid FROM {taxonomy_term_data} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
     $edit2 = array();
-    $edit2['path[alias]'] = $this->randomName();
+    $edit2['path[und][0][value]'] = $this->randomName();
     $this->drupalPost('taxonomy/term/' . $tid . '/edit', $edit2, t('Save'));
 
     // Confirm that the changed alias works.
-    $this->drupalGet($edit2['path[alias]']);
+    $this->drupalGet($edit2['path[und][0][value]']);
     $this->assertText($description, 'Term can be accessed on changed URL alias.');
 
     // Confirm that the old alias no longer works.
-    $this->drupalGet($edit['path[alias]']);
+    $this->drupalGet($edit['path[und][0][value]']);
     $this->assertNoText($description, 'Old URL alias has been removed after altering.');
     $this->assertResponse(404, 'Old URL alias returns 404.');
 
     // Remove the term's URL alias.
     $edit3 = array();
-    $edit3['path[alias]'] = '';
+    $edit3['path[und][0][value]'] = '';
     $this->drupalPost('taxonomy/term/' . $tid . '/edit', $edit3, t('Save'));
 
     // Confirm that the alias no longer works.
-    $this->drupalGet($edit2['path[alias]']);
+    $this->drupalGet($edit2['path[und][0][value]']);
     $this->assertNoText($description, 'Old URL alias has been removed after altering.');
     $this->assertResponse(404, 'Old URL alias returns 404.');
   }
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php b/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php
index 97f3f81..9aafb56 100644
--- a/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php
+++ b/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php
@@ -24,10 +24,10 @@
   function setUp() {
     parent::setUp();
 
-    // Create Basic page and Article node types.
+    // Create Basic page node type.
     if ($this->profile != 'standard') {
       $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
-      $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+      path_add_default_field_instance('node', 'page');
     }
   }
 }
diff --git a/core/modules/path/path.install b/core/modules/path/path.install
new file mode 100644
index 0000000..df0d312
--- /dev/null
+++ b/core/modules/path/path.install
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Path module.
+ */
+
+/**
+ * Implements hook_field_schema().
+ */
+function path_field_schema($field) {
+  $columns = array(
+    'pid' => array(
+      'description' => 'A unique path alias identifier.',
+      'type' => 'int',
+      'not null' => FALSE,
+    ),
+    // This partially duplicates {url_alias}.alias, but contains the actual user
+    // input, which should be stored per field, to make it an inherent part of
+    // an entity's life-cycle; e.g., exposing changed values in field revisions.
+    'value' => array(
+      'description' => 'The user-customizable part of the URL alias.',
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => FALSE,
+    ),
+  );
+  return array(
+    'columns' => $columns,
+    'foreign keys' => array(
+      'path_url_alias' => array(
+        'table' => 'url_alias',
+        'columns' => array(
+          'pid' => 'pid',
+        ),
+      ),
+    ),
+  );
+}
diff --git a/core/modules/path/path.module b/core/modules/path/path.module
index dcbe3be..3a0a73f 100644
--- a/core/modules/path/path.module
+++ b/core/modules/path/path.module
@@ -5,9 +5,7 @@
  * Enables users to rename URLs.
  */
 
-use Drupal\node\Plugin\Core\Entity\Node;
-
-use Drupal\taxonomy\Plugin\Core\Entity\Term;
+use Drupal\Core\Entity\EntityInterface;
 
 /**
  * Implements hook_help().
@@ -39,6 +37,146 @@ function path_help($path, $arg) {
 }
 
 /**
+ * Implements hook_field_info().
+ */
+function path_field_info() {
+  $types['path'] = array(
+    'label' => t('URL alias'),
+    'description' => t('This field stores an URL alias in the database.'),
+    'default_widget' => 'path_default',
+  );
+  return $types;
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function path_field_is_empty($item, $field) {
+  return empty($item['pid']) && (!isset($item['value']) || $item['value'] === '');
+}
+
+/**
+ * Implements hook_field_insert().
+ */
+function path_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  foreach ($items as &$item) {
+    $item['value'] = trim($item['value']);
+    if (!empty($item['value'])) {
+      $uri = $entity->uri();
+      $item['source'] = $uri['path'];
+      $item['langcode'] = $langcode;
+      $item['alias'] = $item['value'];
+      // path_save() populates $item['pid'].
+      $fields = drupal_container()->get('path.crud')->save($item['source'], $item['alias'], $item['langcode']);
+      $item['pid'] = $fields['pid'];
+    }
+  }
+}
+
+/**
+ * Implements hook_field_update().
+ */
+function path_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  $path_crud = drupal_container()->get('path.crud');
+  foreach ($items as &$item) {
+    $item['value'] = trim($item['value']);
+    // Delete old alias if it was erased.
+    if ($item['value'] === '') {
+      if (!empty($item['pid'])) {
+        $path_crud->delete(array('pid' => $item['pid']));
+        $item['pid'] = NULL;
+        $item['value'] = NULL;
+      }
+    }
+    else {
+      $uri = $entity->uri();
+      // If we have a URL alias ID, try to load it.
+      if (!empty($item['pid'])) {
+        $conditions = array('pid' => $item['pid']);
+      }
+      // Otherwise, check whether there is URL alias for the entity URI.
+      else {
+        $conditions = array('source' => $uri['path'], 'langcode' => $langcode);
+      }
+      $path = $path_crud->load($conditions);
+      if ($path === FALSE) {
+        $path = $path_crud->save($uri['path'], $item['value'], $langcode);
+      }
+      else {
+        $path = $path_crud->save($uri['path'], $item['value'], $langcode, $path['pid']);
+      }
+      // Populate 'pid' column, so it is saved by the field storage.
+      $item['pid'] = $path['pid'];
+      // Provide all URL alias properties for subsequently invoked hooks.
+      $item['source'] = $uri['path'];
+      $item['langcode'] = $langcode;
+      $item['alias'] = $path['alias'];
+    }
+  }
+}
+
+/**
+ * Implements hook_field_delete().
+ */
+function path_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  foreach ($items as &$item) {
+    if (!empty($item['pid'])) {
+      drupal_container()->get('path.crud')->delete(array('pid' => $item['pid']));
+    }
+  }
+}
+
+/**
+ * Creates a default 'path' field that can be attached to any entity bundle.
+ *
+ * @return array
+ *   The field information of the default path field.
+ *
+ * @see path_add_default_field_instance()
+ * @see path_install()
+ */
+function path_add_default_field() {
+  $field = field_info_field('path');
+  if (empty($field)) {
+    $field = array(
+      'field_name' => 'path',
+      'type' => 'path',
+    );
+    $field = field_create_field($field);
+  }
+  return $field;
+}
+
+/**
+ * Adds the default path field to an entity bundle.
+ *
+ * @param string $entity_type
+ *   The name of the entity type to add the field to.
+ * @param string $bundle
+ *   The name of the bundle to add the field to.
+ *
+ * @return array
+ *   The path field instance.
+ */
+function path_add_default_field_instance($entity_type, $bundle, $label = 'Permalink') {
+  $field = path_add_default_field();
+  $instance = field_info_instance($entity_type, $field['field_name'], $bundle);
+  if (empty($instance)) {
+    $instance = array(
+      'field_name' => $field['field_name'],
+      'entity_type' => $entity_type,
+      'bundle' => $bundle,
+      'label' => $label,
+      'widget' => array(
+        'type' => 'path_default',
+      ),
+    );
+    $instance = field_create_instance($instance);
+  }
+  return $instance;
+}
+
+/**
  * Implements hook_permission().
  */
 function path_permission() {
@@ -95,225 +233,71 @@ function path_menu() {
 }
 
 /**
- * Implements hook_form_BASE_FORM_ID_alter() for node_form().
- *
- * @see path_form_element_validate()
+ * Implements hook_entity_delete().
  */
-function path_form_node_form_alter(&$form, $form_state) {
-  $node = $form_state['controller']->getEntity($form_state);
-  $path = array();
-  if (!empty($node->nid)) {
-    $conditions = array('source' => 'node/' . $node->nid);
-    if ($node->langcode != LANGUAGE_NOT_SPECIFIED) {
-      $conditions['langcode'] = $node->langcode;
-    }
-    $path = drupal_container()->get('path.crud')->load($conditions);
-    if ($path === FALSE) {
-      $path = array();
-    }
-  }
-  $path += array(
-    'pid' => NULL,
-    'source' => isset($node->nid) ? 'node/' . $node->nid : NULL,
-    'alias' => '',
-    'langcode' => isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED,
-  );
-
-  $form['path'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('URL path settings'),
-    '#collapsible' => TRUE,
-    '#collapsed' => empty($path['alias']),
-    '#group' => 'additional_settings',
-    '#attributes' => array(
-      'class' => array('path-form'),
-    ),
-    '#attached' => array(
-      'library' => array(array('path', 'drupal.path')),
-    ),
-    '#access' => user_access('create url aliases') || user_access('administer url aliases'),
-    '#weight' => 30,
-    '#tree' => TRUE,
-    '#element_validate' => array('path_form_element_validate'),
-  );
-  $form['path']['alias'] = array(
-    '#type' => 'textfield',
-    '#title' => t('URL alias'),
-    '#default_value' => $path['alias'],
-    '#maxlength' => 255,
-    '#description' => t('The alternative URL for this content. Use a relative path without a trailing slash. For example, enter "about" for the about page.'),
-  );
-  $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
-  $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
-  $form['path']['langcode'] = array('#type' => 'value', '#value' => $path['langcode']);
-}
-
-/**
- * Form element validation handler for URL alias form element.
- *
- * @see path_form_node_form_alter()
- */
-function path_form_element_validate($element, &$form_state, $complete_form) {
-  if (!empty($form_state['values']['path']['alias'])) {
-    // Trim the submitted value.
-    $alias = trim($form_state['values']['path']['alias']);
-    form_set_value($element['alias'], $alias, $form_state);
-    // Node language needs special care. Since the language of the URL alias
-    // depends on the node language, and the node language can be switched
-    // right within the same form, we need to conditionally overload the
-    // originally assigned URL alias language.
-    // @todo Remove this after converting Path module to a field, and, after
-    //   stopping Locale module from abusing the content language system.
-    if (isset($form_state['values']['langcode'])) {
-      form_set_value($element['langcode'], $form_state['values']['langcode'], $form_state);
-    }
-
-    $path = $form_state['values']['path'];
-
-    // Ensure that the submitted alias does not exist yet.
-    $query = db_select('url_alias')
-      ->condition('alias', $path['alias'])
-      ->condition('langcode', $path['langcode']);
-    if (!empty($path['source'])) {
-      $query->condition('source', $path['source'], '<>');
-    }
-    $query->addExpression('1');
-    $query->range(0, 1);
-    if ($query->execute()->fetchField()) {
-      form_error($element, t('The alias is already in use.'));
-    }
-  }
-}
-
-/**
- * Implements hook_node_insert().
- */
-function path_node_insert(Node $node) {
-  if (isset($node->path)) {
-    $alias = trim($node->path['alias']);
-    // Only save a non-empty alias.
-    if (!empty($alias)) {
-      // Ensure fields for programmatic executions.
-      $source = 'node/' . $node->nid;
-      $langcode = isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED;
-      drupal_container()->get('path.crud')->save($source, $alias, $langcode);
-    }
-  }
+function path_entity_delete(EntityInterface $entity) {
+  // Delete all aliases associated with this entity.
+  $uri = $entity->uri();
+  drupal_container()->get('path.crud')->delete(array('source' => $uri['path']));
 }
 
 /**
- * Implements hook_node_update().
+ * Implements hook_path_delete().
  */
-function path_node_update(Node $node) {
-  if (isset($node->path)) {
-    $path = $node->path;
-    $alias = trim($path['alias']);
-    // Delete old alias if user erased it.
-    if (!empty($path['pid']) && empty($path['alias'])) {
-      drupal_container()->get('path.crud')->delete(array('pid' => $path['pid']));
-    }
-    // Only save a non-empty alias.
-    if (!empty($path['alias'])) {
-      // Ensure fields for programmatic executions.
-      $source = 'node/' . $node->nid;
-      $langcode = isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED;
-      drupal_container()->get('path.crud')->save($source, $alias, $langcode, $path['pid']);
+function path_path_delete($path) {
+  // EntityFieldQuery does not support field queries across entity types, so
+  // iterate over all path field instances individually and update their field
+  // values accordingly.
+  // Please note that the entirety of the following code will update a *single*
+  // entity only, but Drupal core no longer provides a simple way for retrieving
+  // all entities that have a certain field value.
+  $entity_types = array();
+  foreach (field_info_field_map() as $field_name => $info) {
+    if ($info['type'] == 'path') {
+      foreach ($info['bundles'] as $entity_type => $bundles) {
+        $entity_types[$entity_type][$field_name] = $bundles;
+      }
     }
   }
-}
-
-/**
- * Implements hook_node_predelete().
- */
-function path_node_predelete(Node $node) {
-  // Delete all aliases associated with this node.
-  drupal_container()->get('path.crud')->delete(array('source' => 'node/' . $node->nid));
-}
+  foreach ($entity_types as $entity_type => $field_info) {
+    $query = entity_query($entity_type);
+    // Disable entity access checks, since field values need to be updated
+    // regardless of the permissions of the current user (if any).
+    $query->accessCheck(FALSE);
 
-/**
- * Implements hook_form_FORM_ID_alter() for taxonomy_term_form().
- */
-function path_form_taxonomy_term_form_alter(&$form, $form_state) {
-  // Make sure this does not show up on the delete confirmation form.
-  if (empty($form_state['confirm_delete'])) {
-    $term = $form_state['controller']->getEntity($form_state);
-    $path = (isset($term->tid) ? drupal_container()->get('path.crud')->load(array('source' => 'taxonomy/term/' . $term->tid)) : array());
-    if ($path === FALSE) {
-      $path = array();
+    $group = $query->orConditionGroup();
+    foreach ($field_info as $field_name => $bundles) {
+      $group->condition($field_name . '.pid', $path['pid']);
     }
-    $path += array(
-      'pid' => NULL,
-      'source' => isset($term->tid) ? 'taxonomy/term/' . $term->tid : NULL,
-      'alias' => '',
-      'langcode' => LANGUAGE_NOT_SPECIFIED,
-    );
-    $form['path'] = array(
-      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
-      '#tree' => TRUE,
-      '#element_validate' => array('path_form_element_validate'),
-    );
-    $form['path']['alias'] = array(
-      '#type' => 'textfield',
-      '#title' => t('URL alias'),
-      '#default_value' => $path['alias'],
-      '#maxlength' => 255,
-      '#weight' => 0,
-      '#description' => t("Optionally specify an alternative URL by which this term can be accessed. Use a relative path and don't add a trailing slash or the URL alias won't work."),
-    );
-    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
-    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
-    $form['path']['langcode'] = array('#type' => 'value', '#value' => $path['langcode']);
-  }
-}
-
-/**
- * Implements hook_taxonomy_term_insert().
- */
-function path_taxonomy_term_insert(Term $term) {
-  if (isset($term->path)) {
-    $path = $term->path;
-    $path['alias'] = trim($path['alias']);
-    // Only save a non-empty alias.
-    if (!empty($path['alias'])) {
-      // Ensure fields for programmatic executions.
-      $path['source'] = 'taxonomy/term/' . $term->tid;
-      $path['langcode'] = LANGUAGE_NOT_SPECIFIED;
-      drupal_container()->get('path.crud')->save($path['source'], $path['alias'], $path['langcode']);
+    $query->condition($group);
+    $ids = $query->execute();
+    if (!$ids) {
+      continue;
     }
-  }
-}
 
-/**
- * Implements hook_taxonomy_term_update().
- */
-function path_taxonomy_term_update(Term $term) {
-  if (isset($term->path)) {
-    $path = $term->path;
-    $path['alias'] = trim($path['alias']);
-    // Delete old alias if user erased it.
-    if (!empty($path['pid']) && empty($path['alias'])) {
-      drupal_container()->get('path.crud')->delete(array('pid' => $path['pid']));
-    }
-    // Only save a non-empty alias.
-    if (!empty($path['alias'])) {
-      $pid = (!empty($path['pid']) ? $path['pid'] : NULL);
-      // Ensure fields for programmatic executions.
-      $path['source'] = 'taxonomy/term/' . $term->tid;
-      $path['langcode'] = LANGUAGE_NOT_SPECIFIED;
-      drupal_container()->get('path.crud')->save($path['source'], $path['alias'], $path['langcode'], $pid);
+    foreach (entity_load_multiple($entity_type, $ids) as $id => $entity) {
+      $changed = FALSE;
+      foreach ($field_info as $field_name => $bundles) {
+        // @todo Update for EntityNG, once core entities are converted.
+        if (isset($entity->{$field_name})) {
+          foreach ($entity->{$field_name} as $langcode => &$items) {
+            foreach ($items as $delta => $item) {
+              if (isset($item['pid']) && $item['pid'] == $path['pid']) {
+                unset($items[$delta]);
+                $changed = TRUE;
+              }
+            }
+          }
+        }
+      }
+      if ($changed) {
+        $entity->save();
+      }
     }
   }
 }
 
 /**
- * Implements hook_taxonomy_term_delete().
- */
-function path_taxonomy_term_delete(Term $term) {
-  // Delete all aliases associated with this term.
-  drupal_container()->get('path.crud')->delete(array('source' => 'taxonomy/term/' . $term->tid));
-}
-
-/**
  * Implements hook_library_info().
  */
 function path_library_info() {
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index c063a6d..c7f52b7 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -216,6 +216,7 @@ function standard_install() {
     $type = node_type_set_defaults($type);
     node_type_save($type);
     node_add_body_field($type);
+    path_add_default_field_instance('node', $type['type']);
   }
 
   // Insert default pre-defined RDF mapping into the database.
@@ -274,6 +275,7 @@ function standard_install() {
     'help' => $help,
   ));
   taxonomy_vocabulary_save($vocabulary);
+  path_add_default_field_instance('taxonomy_term', 'tags');
 
   $field = array(
     'field_name' => 'field_' . $vocabulary->machine_name,
