 jsonapi.link_relation_types.yml                |  5 +++++
 jsonapi.module                                 | 13 +++++++++++++
 src/Routing/Routes.php                         |  3 +++
 tests/src/Functional/JsonApiFunctionalTest.php |  5 +++++
 tests/src/Unit/Routing/RoutesTest.php          | 25 ++++++++++++++-----------
 5 files changed, 40 insertions(+), 11 deletions(-)

diff --git a/jsonapi.link_relation_types.yml b/jsonapi.link_relation_types.yml
new file mode 100644
index 0000000..820a875
--- /dev/null
+++ b/jsonapi.link_relation_types.yml
@@ -0,0 +1,5 @@
+# JSON API's extension relation types.
+# See https://tools.ietf.org/html/rfc5988#section-4.2.
+jsonapi:
+  uri: http://jsonapi.org/
+  description: A JSON API representation of this resource.
diff --git a/jsonapi.module b/jsonapi.module
index 997d981..3fe2c72 100644
--- a/jsonapi.module
+++ b/jsonapi.module
@@ -31,6 +31,19 @@ function jsonapi_help($route_name, RouteMatchInterface $route_match) {
 }
 
 /**
+ * Implements hook_entity_type_alter().
+ */
+function jsonapi_entity_type_alter(array &$entity_types) {
+  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  foreach ($entity_types as $entity_type) {
+    $jsonapi_path = $entity_type->getBundleEntityType()
+      ? sprintf('/jsonapi/%s/{%s}/{%s}', $entity_type->id(), $entity_type->getBundleEntityType(), $entity_type->id())
+      : sprintf('/jsonapi/%s/%s/{%s}', $entity_type->id(), $entity_type->id(), $entity_type->id());
+    $entity_type->setLinkTemplate('jsonapi', $jsonapi_path);
+  }
+}
+
+/**
  * Implements hook_entity_base_field_info().
  *
  * @todo This should probably live in core, but for now we will keep it as a
diff --git a/src/Routing/Routes.php b/src/Routing/Routes.php
index d54a5ca..76932df 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -139,6 +139,9 @@ class Routes implements ContainerInjectionInterface {
         ->setMethods(['GET', 'PATCH', 'DELETE']);
       $route_individual->addOptions($options);
       $collection->add($build_route_name('individual'), $route_individual);
+      // Enable generating this URL by doing $entity->toUrl('jsonapi').
+      // @see jsonapi.link_relation_types.yml
+      $collection->add(sprintf('entity.%s.jsonapi', $resource_type->getEntityTypeId()), $route_individual);
 
       // Related resource, like /jsonapi/file/photo/123/comments.
       $route_related = (new Route(sprintf('%s/{%s}/{related}', $route_base_path, $resource_type->getEntityTypeId()), $defaults))
diff --git a/tests/src/Functional/JsonApiFunctionalTest.php b/tests/src/Functional/JsonApiFunctionalTest.php
index 2839cbc..52d42df 100644
--- a/tests/src/Functional/JsonApiFunctionalTest.php
+++ b/tests/src/Functional/JsonApiFunctionalTest.php
@@ -20,6 +20,11 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
     // Unpublish the last entity, so we can check access.
     $this->nodes[60]->setUnpublished()->save();
 
+    // 0. Discoverability: verify
+    $this->drupalGet('/user/1');
+    var_dump($this->drupalGetHeader('Link'));
+    return;
+
     // 1. Load all articles (1st page).
     $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article'));
     $this->assertSession()->statusCodeEquals(200);
diff --git a/tests/src/Unit/Routing/RoutesTest.php b/tests/src/Unit/Routing/RoutesTest.php
index 5159919..975552f 100644
--- a/tests/src/Unit/Routing/RoutesTest.php
+++ b/tests/src/Unit/Routing/RoutesTest.php
@@ -50,8 +50,8 @@ class RoutesTest extends UnitTestCase {
     // Get the route collection and start making assertions.
     $routes = $this->routes['ok']->routes();
 
-    // Make sure that there are 4 routes for each resource.
-    $this->assertEquals(4, $routes->count());
+    // Make sure that there are 5 routes for each resource.
+    $this->assertEquals(5, $routes->count());
 
     $iterator = $routes->getIterator();
     // Check the collection route.
@@ -75,15 +75,18 @@ class RoutesTest extends UnitTestCase {
 
     // Check the individual route.
     /** @var \Symfony\Component\Routing\Route $route */
-    $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.individual');
-    $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity_type_1}', $route->getPath());
-    $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
-    $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
-    $this->assertEquals(['GET', 'PATCH', 'DELETE'], $route->getMethods());
-    $this->assertSame(Routes::FRONT_CONTROLLER, $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
-    $this->assertSame('Drupal\jsonapi\Resource\JsonApiDocumentTopLevel', $route->getOption('serialization_class'));
-    $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth'));
-    $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
+    $official_route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.individual');
+    $alias_route = $iterator->offsetGet('entity.entity_type_1.jsonapi');
+    foreach ([$official_route, $alias_route] as $route) {
+      $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity_type_1}', $route->getPath());
+      $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
+      $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
+      $this->assertEquals(['GET', 'PATCH', 'DELETE'], $route->getMethods());
+      $this->assertSame(Routes::FRONT_CONTROLLER, $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
+      $this->assertSame('Drupal\jsonapi\Resource\JsonApiDocumentTopLevel', $route->getOption('serialization_class'));
+      $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth'));
+      $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
+    }
   }
 
   /**
