diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index f3c02ed..a0aa34c 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -252,6 +252,40 @@ class EntityType extends Plugin {
   public $menu_path_wildcard;
 
   /**
+   * Link templates using the URI template syntax.
+   *
+   * Links are an array of standard link relations to the URI template that
+   * should be used for them.  Where possible, link relationships should use
+   * established IANA relationships rather than custom relationships.
+   *
+   * Every entity type should, at minimum, define "canonical", which is the
+   * pattern for URIs to that entity.  Even if the entity will have no HTML
+   * page exposed to users it should still have a canonical URI in order to
+   * be compatible with web services.  Entities that will be user-editable
+   * via an HTML page must also define an "edit-form" relationship.
+   *
+   * By default, the following placeholders are supported:
+   *   - entityType: The machine name of the entity.
+   *   - bundle: The bundle machine name of the entity.
+   *   - id: The unique ID of the entity.
+   *   - uuid: The UUID of the entity.
+   *   - [entityType]: The entity type itself will also be a valid token for
+   *                   the ID of the entity. This is generally preferred over
+   *                   "id" for better self-documentation.
+   *
+   * Specific entity types may also expand upon this list by overriding the
+   * uriPlaceholderReplacements() method.
+   *
+   * @link http://www.iana.org/assignments/link-relations/link-relations.xml
+   * @link http://tools.ietf.org/html/rfc6570
+   *
+   * @var array
+   */
+  public $links = array(
+    'canonical' => '/entity/{entityType}/{id}',
+  );
+
+  /**
    * Specifies whether a module exposing permissions for the current entity type
    * should use entity-type level granularity, bundle level granularity or just
    * skip this entity. The allowed values are respectively "entity_type",
@@ -260,5 +294,4 @@ class EntityType extends Plugin {
    * @var string|bool (optional)
    */
   public $permission_granularity = 'entity_type';
-
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
index 9c48753..62f2a87 100644
--- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
+++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
@@ -202,6 +202,13 @@ public function __unset($name) {
   }
 
   /**
+   * Implements the magic method for __call().
+   */
+  public function __call($method, $args) {
+    return call_user_func_array(array($this->decorated, $method), $args);
+  }
+
+  /**
    * Implements the magic method for clone().
    */
   function __clone() {
@@ -352,8 +359,8 @@ public function label($langcode = NULL) {
   /**
    * Forwards the call to the decorated entity.
    */
-  public function uri() {
-    return $this->decorated->uri();
+  public function uri($rel = 'canonical') {
+    return $this->decorated->uri($rel);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index 70ac362..593523f 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -75,6 +75,13 @@ class EntityNG extends Entity {
   protected $fieldDefinitions;
 
   /**
+   * Local cache for uri placeholder substitution values.
+   *
+   * @var array
+   */
+  protected $uriPlaceholderReplacements;
+
+  /**
    * Overrides Entity::__construct().
    */
   public function __construct(array $values, $entity_type, $bundle = FALSE) {
@@ -137,6 +144,66 @@ public function uuid() {
   }
 
   /**
+   * Implements \Drupal\Core\Entity\EntityInterface::uri().
+   */
+  public function uri($rel = 'canonical') {
+    $entity_info = $this->entityInfo();
+
+    $link_templates = isset($entity_info['links']) ? $entity_info['links'] : array();
+
+    if (isset($link_templates[$rel])) {
+      $template = $link_templates[$rel];
+      $replacements = $this->uriPlaceholderReplacements();
+      $uri['path'] = str_replace(array_keys($replacements), array_values($replacements), $template);
+
+      // Pass the entity data to url() so that alter functions do not need to
+      // look up this entity again.
+      $uri['options']['entity_type'] = $this->entityType;
+      $uri['options']['entity'] = $this;
+      return $uri;
+    }
+
+    // For a canonical link (ie, link to self), look up the stack for default
+    // logic.  Other relationship types are not supported by parent classes.
+    if ($rel == 'canonical') {
+      return parent::uri();
+    }
+  }
+
+  /**
+   * Returns a list of uri relationships supported by this entity.
+   *
+   * @return array
+   *   An array of link relationships supported by this entity.
+   */
+  public function uriRelationships() {
+    $entity_info = $this->entityInfo();
+    return isset($entity_info['links']) ? array_keys($entity_info['links']) : array();
+  }
+
+  /**
+   * Returns an array of placeholders for this entity.
+   *
+   * Individual entity classes may override this method to add additional
+   * placeholders if desired.  If so, they should be sure to replicate the
+   * property caching logic.
+   *
+   * @return array
+   */
+  protected function uriPlaceholderReplacements() {
+    if (empty($this->uriPlaceholderReplacements)) {
+      $this->uriPlaceholderReplacements = array(
+        '{entityType}' => $this->entityType(),
+        '{bundle}' => $this->bundle(),
+        '{id}' => $this->id(),
+        '{uuid}' => $this->uuid(),
+        '{' . $this->entityType() . '}' => $this->id(),
+      );
+    }
+    return $this->uriPlaceholderReplacements;
+  }
+
+  /**
    * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
    */
   public function get($property_name) {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index c4d8109..bc3dc3b 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -42,6 +42,11 @@
  *   bundle_keys = {
  *     "bundle" = "type"
  *   },
+ *   links = {
+ *     "canonical" = "/node/{node}",
+ *     "edit-form" = "/node/{node}/edit",
+ *     "version-history" = "/node/{node}/revisions"
+ *   },
  *   permission_granularity = "bundle"
  * )
  */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index f663387..ee8b9db 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -2199,10 +2199,15 @@ function node_page_view(EntityInterface $node) {
   // of the active trail, and the link name becomes the page title.
   // Thus, we must explicitly set the page title to be the node title.
   drupal_set_title($node->label());
-  $uri = $node->uri();
-  // Set the node path as the canonical URL to prevent duplicate content.
-  drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
-  // Set the non-aliased path as a default shortlink.
+
+  foreach ($node->uriRelationships() as $rel) {
+    $uri = $node->uri($rel);
+    // Set the node path as the canonical URL to prevent duplicate content.
+    drupal_add_html_head_link(array('rel' => $rel, 'href' => url($uri['path'], $uri['options'])), TRUE);
+  }
+
+  $uri = $node->uri('canonical');
+  // Set the non-aliased canonical path as a default shortlink.
   drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
   return node_show($node);
 }
