diff --git a/config/optional/linkit.linkit_profile.default.yml b/config/optional/linkit.linkit_profile.default.yml
index 23af68b..fe06c78 100644
--- a/config/optional/linkit.linkit_profile.default.yml
+++ b/config/optional/linkit.linkit_profile.default.yml
@@ -15,4 +15,5 @@ matchers:
       result_description: 'by [node:author] | [node:created:medium]'
       bundles: {  }
       group_by_bundle: false
-      include_unpublished: false
\ No newline at end of file
+      include_unpublished: false
+      substitution_type: canonical
diff --git a/config/schema/linkit.schema.yml b/config/schema/linkit.schema.yml
index 7038dc9..3952576 100644
--- a/config/schema/linkit.schema.yml
+++ b/config/schema/linkit.schema.yml
@@ -43,6 +43,8 @@ linkit.matcher.entity:
       nullable: true
     group_by_bundle:
       type: boolean
+    substitution_type:
+      type: string
 
 linkit.matcher.entity:*:
   type: linkit.matcher.entity
diff --git a/linkit.install b/linkit.install
index 97c68d2..0b42ea9 100644
--- a/linkit.install
+++ b/linkit.install
@@ -122,3 +122,34 @@ function linkit_update_8500() {
     $config->save(TRUE);
   }
 }
+
+/**
+ * Prepare anchor attributes for substitution plugins.
+ */
+function linkit_update_8501() {
+  $config_factory = \Drupal::configFactory();
+
+  // Update all filter formats that allow the data-entity-uuid attribute to also
+  // allow the data-entity-substitution attribute.
+  foreach ($config_factory->listAll('filter.format.') as $id) {
+    $filter = $config_factory->getEditable($id);
+    if ($allowed_html = $filter->get('filters.filter_html.settings.allowed_html')) {
+      $allowed_html = str_replace('data-entity-uuid', 'data-entity-uuid data-entity-substitution', $allowed_html);
+      $filter->set('filters.filter_html.settings.allowed_html', $allowed_html);
+      $filter->save(TRUE);
+    }
+  }
+
+  // Update all "file" matchers to the "file" substitution plugin, to maintain
+  // existing behavior out of the box.
+  $config_factory = \Drupal::configFactory();
+  foreach ($config_factory->listAll('linkit.linkit_profile.') as $id) {
+    $profile = $config_factory->getEditable($id);
+    foreach ($profile->get('matchers') as $key => $matcher) {
+      $settings = $profile->get('matchers.' . $key . '.settings');
+      $settings['substitution_type'] = $matcher['id'] === 'entity:file' ? 'file' : 'canonical';
+      $profile->set('matchers.' . $key . '.settings', $settings);
+    }
+    $profile->save(TRUE);
+  }
+}
diff --git a/linkit.module b/linkit.module
index 2920c70..55cdd24 100644
--- a/linkit.module
+++ b/linkit.module
@@ -169,12 +169,13 @@ function linkit_form_editor_link_dialog_validate(array &$form, FormStateInterfac
     if (!empty($link_element)) {
       $form_state->setValue(['attributes', 'data-entity-type'], '');
       $form_state->setValue(['attributes', 'data-entity-uuid'], '');
+      $form_state->setValue(['attributes', 'data-entity-substitution'], '');
     }
     return;
   }
 
   // Parse the entity: URI into an entity type ID and entity ID.
-  list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
+  list($substitution_type, $entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 3);
 
   // Check if the given entity type exists, to prevent the entity load method
   // to throw exceptions.
@@ -192,6 +193,7 @@ function linkit_form_editor_link_dialog_validate(array &$form, FormStateInterfac
   if (!empty($entity)) {
     $form_state->setValue(['attributes', 'data-entity-type'], $entity->getEntityTypeId());
     $form_state->setValue(['attributes', 'data-entity-uuid'], $entity->uuid());
+    $form_state->setValue(['attributes', 'data-entity-substitution'], $substitution_type);
   }
   else {
     $form_state->setError($form['attributes']['href'], t('Invalid URI'));
diff --git a/linkit.services.yml b/linkit.services.yml
index 2c8904f..529c589 100644
--- a/linkit.services.yml
+++ b/linkit.services.yml
@@ -2,6 +2,8 @@ services:
   plugin.manager.linkit.matcher:
     class: Drupal\linkit\MatcherManager
     parent: default_plugin_manager
-
+  plugin.manager.linkit.substitution:
+    class: Drupal\linkit\SubstitutionManager
+    parent: default_plugin_manager
   linkit.suggestion_manager:
     class: Drupal\linkit\SuggestionManager
diff --git a/src/Plugin/Filter/LinkitFilter.php b/src/Plugin/Filter/LinkitFilter.php
index 02636cc..fdf19f0 100644
--- a/src/Plugin/Filter/LinkitFilter.php
+++ b/src/Plugin/Filter/LinkitFilter.php
@@ -8,6 +8,7 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\filter\FilterProcessResult;
 use Drupal\filter\Plugin\FilterBase;
+use Drupal\linkit\SubstitutionManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -38,6 +39,13 @@ class LinkitFilter extends FilterBase implements ContainerFactoryPluginInterface
   protected $entityRepository;
 
   /**
+   * The substitution manager.
+   *
+   * @var \Drupal\linkit\SubstitutionManagerInterface
+   */
+  protected $substitutionManager;
+
+  /**
    * Constructs a LinkitFilter object.
    *
    * @param array $configuration
@@ -49,10 +57,11 @@ class LinkitFilter extends FilterBase implements ContainerFactoryPluginInterface
    * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
    *   The entity repository service.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityRepositoryInterface $entity_repository) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityRepositoryInterface $entity_repository, SubstitutionManagerInterface $substitution_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->entityRepository = $entity_repository;
+    $this->substitutionManager = $substitution_manager;
   }
 
   /**
@@ -63,7 +72,8 @@ class LinkitFilter extends FilterBase implements ContainerFactoryPluginInterface
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('entity.repository')
+      $container->get('entity.repository'),
+      $container->get('plugin.manager.linkit.substitution')
     );
   }
 
@@ -83,24 +93,25 @@ class LinkitFilter extends FilterBase implements ContainerFactoryPluginInterface
           // Load the appropriate translation of the linked entity.
           $entity_type = $element->getAttribute('data-entity-type');
           $uuid = $element->getAttribute('data-entity-uuid');
+
+          // Make the substitution optional, for backwards compatibility,
+          // maintaining the previous hard-coded direct file link assumptions,
+          // for content created before the substitution feature.
+          if (!$substitution_type = $element->getAttribute('data-entity-substitution')) {
+            $substitution_type = $entity_type === 'file' ? 'file' : SubstitutionManagerInterface::DEFAULT_SUBSTITUTION;
+          }
+
           $entity = $this->entityRepository->loadEntityByUuid($entity_type, $uuid);
           if ($entity) {
+
             $entity = $this->entityRepository->getTranslationFromContext($entity, $langcode);
 
-            // Set the appropriate href attribute.
-            // The file entity has not declared any "links" in its entity
-            // definition. We therefor have to use the file entity specific
-            // getFileUri() instead.
-            if ($entity_type === 'file') {
-              /** @var \Drupal\file\Entity\File $entity */
-              $url = file_create_url($entity->getFileUri());
-              $element->setAttribute('href', $url);
-            }
-            else {
-              $url = $entity->toUrl('canonical')->toString(TRUE);
-              $element->setAttribute('href', $url->getGeneratedUrl());
-            }
+            /** @var \Drupal\Core\GeneratedUrl $url */
+            $url = $this->substitutionManager
+              ->createInstance($substitution_type)
+              ->getUrl($entity);
 
+            $element->setAttribute('href', $url->getGeneratedUrl());
             $access = $entity->access('view', NULL, TRUE);
 
             // Set the appropriate title attribute.
diff --git a/src/Plugin/Linkit/Matcher/EntityMatcher.php b/src/Plugin/Linkit/Matcher/EntityMatcher.php
index 4c656f1..812a076 100644
--- a/src/Plugin/Linkit/Matcher/EntityMatcher.php
+++ b/src/Plugin/Linkit/Matcher/EntityMatcher.php
@@ -13,6 +13,8 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\linkit\ConfigurableMatcherBase;
 use Drupal\linkit\MatcherTokensTrait;
+use Drupal\linkit\SubstitutionManager;
+use Drupal\linkit\SubstitutionManagerInterface;
 use Drupal\linkit\Suggestion\DescriptionSuggestion;
 use Drupal\linkit\Suggestion\SuggestionCollection;
 use Drupal\linkit\Utility\LinkitXss;
@@ -81,9 +83,16 @@ class EntityMatcher extends ConfigurableMatcherBase {
   protected $targetType;
 
   /**
+   * The substitution manager.
+   *
+   * @var \Drupal\linkit\SubstitutionManagerInterface
+   */
+  protected $substitutionManager;
+
+  /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user, SubstitutionManagerInterface $substitution_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     if (empty($plugin_definition['target_entity'])) {
@@ -96,6 +105,7 @@ class EntityMatcher extends ConfigurableMatcherBase {
     $this->moduleHandler = $module_handler;
     $this->currentUser = $current_user;
     $this->targetType = $plugin_definition['target_entity'];
+    $this->substitutionManager = $substitution_manager;
   }
 
   /**
@@ -111,7 +121,8 @@ class EntityMatcher extends ConfigurableMatcherBase {
       $container->get('entity_type.bundle.info'),
       $container->get('entity.repository'),
       $container->get('module_handler'),
-      $container->get('current_user')
+      $container->get('current_user'),
+      $container->get('plugin.manager.linkit.substitution')
     );
   }
 
@@ -160,6 +171,7 @@ class EntityMatcher extends ConfigurableMatcherBase {
       'result_description' => '',
       'bundles' => [],
       'group_by_bundle' => FALSE,
+      'substitution_type' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION,
     ];
   }
 
@@ -205,6 +217,17 @@ class EntityMatcher extends ConfigurableMatcherBase {
       ];
     }
 
+    $substitution_options = $this->substitutionManager->getApplicablePluginsOptionList($this->targetType);
+    $form['substitution_type'] = [
+      '#title' => $this->t('Substitution Type'),
+      '#type' => 'select',
+      '#default_value' => $this->configuration['substitution_type'],
+      '#options' => $substitution_options,
+      '#description' => $this->t('Configure how the selected entity should be transformed into a URL for insertion.'),
+      '#weight' => -49,
+      '#access' => count($substitution_options) !== 1,
+    ];
+
     return $form;
   }
 
@@ -221,6 +244,7 @@ class EntityMatcher extends ConfigurableMatcherBase {
     $this->configuration['result_description'] = $form_state->getValue('result_description');
     $this->configuration['bundles'] = $form_state->getValue('bundles');
     $this->configuration['group_by_bundle'] = $form_state->getValue('group_by_bundle');
+    $this->configuration['substitution_type'] = $form_state->getValue('substitution_type');
   }
 
   /**
@@ -344,7 +368,7 @@ class EntityMatcher extends ConfigurableMatcherBase {
    * @see \Drupal\Core\Url::fromEntityUri()
    */
   protected function buildPath(EntityInterface $entity) {
-    return 'entity:' . $entity->getEntityTypeId() . '/' . $entity->id();
+    return 'entity:' . $this->configuration['substitution_type'] . '/' . $entity->getEntityTypeId() . '/' . $entity->id();
   }
 
   /**
diff --git a/tests/src/FunctionalJavascript/LinkitDialogTest.php b/tests/src/FunctionalJavascript/LinkitDialogTest.php
index df0f528..a77a60d 100644
--- a/tests/src/FunctionalJavascript/LinkitDialogTest.php
+++ b/tests/src/FunctionalJavascript/LinkitDialogTest.php
@@ -170,7 +170,7 @@ class LinkitDialogTest extends JavascriptTestBase {
     $page->find('xpath', '(//li[contains(@class, "linkit-result") and contains(@class, "ui-menu-item")])[1]')->click();
 
     // Make sure the href field is populated with the node uri.
-    $this->assertEquals('entity:' . $this->demoEntity->getEntityTypeId() . '/' . $this->demoEntity->id(), $input_field->getValue(), 'The href field is populated with the node uri');
+    $this->assertEquals('entity:canonical/' . $this->demoEntity->getEntityTypeId() . '/' . $this->demoEntity->id(), $input_field->getValue(), 'The href field is populated with the node uri');
 
     // Make sure the link information is populated.
     $javascript = "(function (){ return jQuery('.linkit-link-information > span').text(); })()";
diff --git a/tests/src/Kernel/LinkitEditorLinkDialogTest.php b/tests/src/Kernel/LinkitEditorLinkDialogTest.php
index 1a02b19..774fb35 100644
--- a/tests/src/Kernel/LinkitEditorLinkDialogTest.php
+++ b/tests/src/Kernel/LinkitEditorLinkDialogTest.php
@@ -125,7 +125,7 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
     $this->assertEquals('', $form['attributes']['href']['#default_value'], 'The href attribute is empty.');
     $this->assertEquals('', $form['attributes']['link-information']['#context']['link_target'], 'Link information is empty.');
 
-    $form_state->setValue(['attributes', 'href'], 'entity:missing_entity/1');
+    $form_state->setValue(['attributes', 'href'], 'entity:canonical/missing_entity/1');
     $form_builder->submitForm($form_object, $form_state);
     $this->assertNotEmpty($form_state->getErrors(), 'Got validation errors for none existing entity type.');
 
@@ -135,7 +135,7 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
     $this->assertEquals('', $form_state->getValue(['attributes', 'data-entity-type']));
     $this->assertEquals('', $form_state->getValue(['attributes', 'data-entity-uuid']));
 
-    $form_state->setValue(['attributes', 'href'], 'entity:entity_test/1');
+    $form_state->setValue(['attributes', 'href'], 'entity:canonical/entity_test/1');
     $form_builder->submitForm($form_object, $form_state);
     $this->assertEmpty($form_state->getErrors(), 'Got no validation errors for correct URI.');
     $this->assertEquals($entity->getEntityTypeId(), $form_state->getValue(['attributes', 'data-entity-type']), 'Attribute "data-entity-type" exists and has the correct value.');
diff --git a/tests/src/Kernel/Matchers/AssertResultUriTrait.php b/tests/src/Kernel/Matchers/AssertResultUriTrait.php
index 1ccc656..9398c57 100644
--- a/tests/src/Kernel/Matchers/AssertResultUriTrait.php
+++ b/tests/src/Kernel/Matchers/AssertResultUriTrait.php
@@ -19,7 +19,7 @@ trait AssertResultUriTrait {
    */
   public function assertResultUri($entity_type, SuggestionCollection $suggestions) {
     foreach ($suggestions->getSuggestions() as $suggestion) {
-      $this->assertTrue(preg_match("/^entity:" . $entity_type . "\\/\\w+$/i", $suggestion->getPath()), 'Result URI correct formatted.');
+      $this->assertTrue(preg_match("/^entity:canonical\/" . $entity_type . "\\/\\w+$/i", $suggestion->getPath()), 'Result URI correct formatted.');
     }
   }
 
