diff --git a/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php b/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php
index d11af84848..85e5ff0068 100644
--- a/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php
@@ -103,6 +103,7 @@ protected function getExpectedDocument() {
           'bundle' => 'menu_link_content',
           'link' => [
             'uri' => 'https://nl.wikipedia.org/wiki/Llama',
+            'url' => 'https://nl.wikipedia.org/wiki/Llama',
             'title' => NULL,
             'options' => [],
           ],
diff --git a/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php b/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php
index 582a123890..6fca59e5fd 100644
--- a/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php
@@ -81,6 +81,8 @@ protected function createEntity() {
    */
   protected function getExpectedDocument() {
     $self_url = Url::fromUri('base:/jsonapi/shortcut/default/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl();
+    $uri = 'internal:/user/logout';
+    $url = Url::fromUri($uri);
     return [
       'jsonapi' => [
         'meta' => [
@@ -102,7 +104,8 @@ protected function getExpectedDocument() {
         'attributes' => [
           'title' => 'Comments',
           'link' => [
-            'uri' => 'internal:/user/logout',
+            'uri' => $uri,
+            'url' => $url->toString(),
             'title' => NULL,
             'options' => [],
           ],
diff --git a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
index 84556f8be0..c034bffe1f 100644
--- a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
+++ b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
@@ -3,6 +3,7 @@
 namespace Drupal\link\Plugin\Field\FieldType;
 
 use Drupal\Component\Utility\Random;
+use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
@@ -43,6 +44,13 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
     $properties['uri'] = DataDefinition::create('uri')
       ->setLabel(t('URI'));
 
+    $properties['url'] = DataDefinition::create('link_url')
+      ->setLabel(t('URL'))
+      ->setDescription(t('The processed URL for this link that can e.g. be used in in href attributes.'))
+      ->setComputed(TRUE)
+      ->setInternal(FALSE)
+      ->setReadOnly(TRUE);
+
     $properties['title'] = DataDefinition::create('string')
       ->setLabel(t('Link text'));
 
@@ -174,6 +182,31 @@ public function getUrl() {
     return Url::fromUri($this->uri, (array) $this->options);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($property_name, $notify = TRUE) {
+    // Make sure that the link item values can be kept in sync with computed
+    // property url.
+    if ($property_name === 'url') {
+      $property = $this->get('url');
+      if ($url = $property->getValue()) {
+        $parsed = UrlHelper::parse($url);
+        // If the path is not an external URL then add 'internal:' prefix to
+        // make it a valid uri.
+        if (strpos($parsed['path'], ':') === FALSE) {
+          $parsed['path'] = 'internal:' . $parsed['path'];
+        }
+        $this->writePropertyValue('uri', $parsed['path']);
+        // Only set the options if we have query parameters or a fragment.
+        if (!empty($parsed['query']) || !empty($parsed['fragment'])) {
+          $this->writePropertyValue('options', ['query' => $parsed['query'], 'fragment' => $parsed['fragment']]);
+        }
+      }
+    }
+    parent::onChange($property_name, $notify);
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -189,6 +222,12 @@ public function setValue($values, $notify = TRUE) {
       ];
     }
     parent::setValue($values, $notify);
+    // Support setting the field item with only url property, but make sure
+    // values stay in sync if only url property is passed.
+    // NULL is a valid value, so we use array_key_exists().
+    if (is_array($values) && array_key_exists('url', $values) && !array_key_exists('uri', $values)) {
+      $this->onChange('url', FALSE);
+    }
   }
 
 }
diff --git a/core/modules/link/tests/src/Kernel/LinkItemTest.php b/core/modules/link/tests/src/Kernel/LinkItemTest.php
index bd2ff0b67c..df44b1b008 100644
--- a/core/modules/link/tests/src/Kernel/LinkItemTest.php
+++ b/core/modules/link/tests/src/Kernel/LinkItemTest.php
@@ -86,6 +86,7 @@ public function testLinkItem() {
       ],
       'external' => TRUE,
     ], $entity->field_test->first()->getUrl()->getOptions());
+    $this->assertEquals($url, $entity->field_test->url);
     $entity->name->value = $this->randomMachineName();
     $entity->save();
 
@@ -94,6 +95,7 @@ public function testLinkItem() {
     $entity = EntityTest::load($id);
     $this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test);
     $this->assertInstanceOf(FieldItemInterface::class, $entity->field_test[0]);
+    $this->assertEqual($url, $entity->field_test->url);
     $this->assertEqual($parsed_url['path'], $entity->field_test->uri);
     $this->assertEqual($parsed_url['path'], $entity->field_test[0]->uri);
     $this->assertEqual($title, $entity->field_test->title);
@@ -118,23 +120,26 @@ public function testLinkItem() {
     $entity->field_test->uri = $new_url;
     $entity->field_test->title = $new_title;
     $entity->field_test->first()->get('options')->set('query', NULL);
+    $entity->field_test->first()->get('options')->set('query', []);
     $entity->field_test->first()->get('options')->set('attributes', ['class' => $new_class]);
     $this->assertEqual($new_url, $entity->field_test->uri);
     $this->assertEqual($new_title, $entity->field_test->title);
     $this->assertEqual($new_class, $entity->field_test->options['attributes']['class']);
-    $this->assertNull($entity->field_test->options['query']);
+    $this->assertEmpty($entity->field_test->options['query']);
 
     // Read changed entity and assert changed values.
     $entity->save();
     $entity = EntityTest::load($id);
     $this->assertEqual($new_url, $entity->field_test->uri);
+    $this->assertEqual($entity->field_test->url, $new_url);
     $this->assertEqual($new_title, $entity->field_test->title);
     $this->assertEqual($new_class, $entity->field_test->options['attributes']['class']);
 
-    // Check that if we only set uri the default values for title and options
-    // are also initialized.
+    // Check that if we only set uri the default values for url, title, and
+    // options are also initialized.
     $entity->field_test = ['uri' => 'internal:/node/add'];
     $this->assertEqual('internal:/node/add', $entity->field_test->uri);
+    $this->assertEqual($entity->field_test->url, '/node/add');
     $this->assertNull($entity->field_test->title);
     $this->assertSame([], $entity->field_test->options);
 
@@ -145,6 +150,7 @@ public function testLinkItem() {
       'options' => ['query' => NULL],
     ];
     $this->assertEqual('internal:/node/add', $entity->field_test->uri);
+    $this->assertEqual($entity->field_test->url, '/node/add');
     $this->assertNull($entity->field_test->title);
     $this->assertNull($entity->field_test->options['query']);
 
@@ -172,6 +178,38 @@ public function testLinkItem() {
     $entity->field_test_external->generateSampleItems();
     $entity->field_test_internal->generateSampleItems();
     $this->entityValidateAndSave($entity);
+
+    // Test setting up computed property also sets up other values.
+    $entity = EntityTest::create();
+    $url = 'https://www.drupal.org?test_param=test_value#top';
+    $parsed_url = UrlHelper::parse($url);
+    $entity->field_test->url = $url;
+    $this->assertEquals($parsed_url['path'], $entity->field_test->uri);
+    $this->assertEquals([
+        'query' => $parsed_url['query'],
+        'fragment' => $parsed_url['fragment'],
+        'external' => TRUE,
+      ], $entity->field_test->first()->getUrl()->getOptions());
+    $entity->name->value = $this->randomMachineName();
+    $entity->save();
+
+    // Verify that the field value is changed.
+    $id = $entity->id();
+    $entity = EntityTest::load($id);
+    $this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test);
+    $this->assertInstanceOf(FieldItemInterface::class, $entity->field_test[0]);
+    $this->assertEquals($url, $entity->field_test->url);
+    $this->assertEquals($entity->field_test->uri, $parsed_url['path']);
+    $this->assertEquals($entity->field_test[0]->uri, $parsed_url['path']);
+    $this->assertEquals($entity->field_test->options['query'], $parsed_url['query']);
+    $this->assertEquals($entity->field_test->options['fragment'], $parsed_url['fragment']);
+
+    // Check that if we only set url the default values for uri, title, and
+    // options are also initialized.
+    $entity->field_test = ['url' => '/node/add'];
+    $this->assertEqual($entity->field_test->uri, 'internal:/node/add');
+    $this->assertEqual($entity->field_test->url, '/node/add');
+    $this->assertNull($entity->field_test->title);
   }
 
 }
diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentResourceTestBase.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentResourceTestBase.php
index 448dfd2394..f70a497217 100644
--- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentResourceTestBase.php
+++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentResourceTestBase.php
@@ -58,6 +58,7 @@ protected function createEntity() {
       'description' => 'Llama Gabilondo',
       'link' => [
         'uri' => 'https://nl.wikipedia.org/wiki/Llama',
+        'url' => 'https://nl.wikipedia.org/wiki/Llama#a-fragment',
         'options' => [
           'fragment' => 'a-fragment',
           'attributes' => [
@@ -86,6 +87,7 @@ protected function getNormalizedPostEntity() {
       'link' => [
         [
           'uri' => 'http://www.urbandictionary.com/define.php?term=drama%20llama',
+          'url' => 'http://www.urbandictionary.com/define.php?term=drama%20llama#a-fragment',
           'options' => [
             'fragment' => 'a-fragment',
             'attributes' => [
@@ -130,6 +132,7 @@ protected function getExpectedNormalizedEntity() {
       'link' => [
         [
           'uri' => 'https://nl.wikipedia.org/wiki/Llama',
+          'url' => 'https://nl.wikipedia.org/wiki/Llama#a-fragment',
           'title' => NULL,
           'options' => [
             'fragment' => 'a-fragment',
diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutResourceTestBase.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutResourceTestBase.php
index c64293bd4d..e90d053e9f 100644
--- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutResourceTestBase.php
+++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutResourceTestBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\shortcut\Functional\Rest;
 
+use Drupal\Core\Url;
 use Drupal\shortcut\Entity\Shortcut;
 use Drupal\shortcut\Entity\ShortcutSet;
 use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
@@ -72,6 +73,11 @@ protected function createEntity() {
    * {@inheritdoc}
    */
   protected function getExpectedNormalizedEntity() {
+    $uri = 'internal:/admin/content/comment';
+    $uri_options = [
+      'fragment' => 'new',
+    ];
+    $url = Url::fromUri($uri, $uri_options);
     return [
       'uuid' => [
         [
@@ -97,11 +103,10 @@ protected function getExpectedNormalizedEntity() {
       ],
       'link' => [
         [
-          'uri' => 'internal:/admin/content/comment',
+          'uri' => $uri,
+          'url' => $url->toString(),
           'title' => NULL,
-          'options' => [
-            'fragment' => 'new',
-          ],
+          'options' => $uri_options,
         ],
       ],
       'weight' => [
