diff --git a/tests/src/Kernel/FieldTest.php b/tests/src/Kernel/FieldTest.php
index d7d6e774f382b7d62f722c37c8f094def87eed46..66c218251bfabbfaa4b210dd3761a6fb5e5765e3 100644
--- a/tests/src/Kernel/FieldTest.php
+++ b/tests/src/Kernel/FieldTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\token\Kernel;
 
 use Drupal\Component\Utility\Unicode;
+use Drupal\contact\Entity\ContactForm;
 use Drupal\Core\Entity\Entity\EntityViewMode;
 use Drupal\Core\Render\Markup;
 use Drupal\field\Entity\FieldConfig;
@@ -10,6 +11,7 @@ use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\filter\Entity\FilterFormat;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
+use Drupal\contact\Entity\Message;
 
 /**
  * Tests field tokens.
@@ -28,7 +30,7 @@ class FieldTest extends KernelTestBase {
    *
    * @var array
    */
-  public static $modules = ['node', 'text', 'field', 'filter'];
+  public static $modules = ['node', 'text', 'field', 'filter', 'contact'];
 
   /**
    * {@inheritdoc}
@@ -194,4 +196,49 @@ class FieldTest extends KernelTestBase {
       'test_field' => Markup::create(substr($value, 0, 50)),
     ]);
   }
+
+  /**
+   * Test that tokens are properly created for an entity's base fields.
+   */
+  public function testBaseFieldTokens() {
+    // Create a new contact_message entity and verify that tokens are generated
+    // for its base fields. The contact_message entity type is used because it
+    // provides no tokens by default.
+    $contact_form = ContactForm::create([
+      'id' => 'form_id',
+    ]);
+    $contact_form->save();
+
+    $entity = Message::create([
+      'contact_form' => 'form_id',
+      'uuid' => '123',
+      'langcode' => 'en',
+      'name' => 'Test name',
+      'mail' => 'Test mail',
+      'subject' => 'Test subject',
+      'message' => 'Test message',
+      'copy' => FALSE,
+    ]);
+    $entity->save();
+
+    $this->assertTokens('contact_message', ['contact_message' => $entity], [
+      'uuid' => Markup::create('123'),
+      'langcode' => Markup::create('English'),
+      'name' => Markup::create('Test name'),
+      'mail' => Markup::create('Test mail'),
+      'subject' => Markup::create('Test subject'),
+      'message' => Markup::create('Test message'),
+      'copy' => 'Off',
+    ]);
+
+    // Test the metadata of one of the tokens.
+    $tokenService = \Drupal::service('token');
+    $token_info = $tokenService->getTokenInfo('contact_message', 'subject');
+    $this->assertEquals($token_info['name'], 'Subject');
+    $this->assertEquals($token_info['description'], 'Text (plain) field.');
+    $this->assertEquals($token_info['module'], 'token');
+
+    // Verify that node entity type doesn't have a uid token.
+    $this->assertNull($tokenService->getTokenInfo('node', 'uid'));
+  }
 }
diff --git a/token.tokens.inc b/token.tokens.inc
index 75f486b91619bf99156919515855d1a5fee3a6ae..f38955a4bd66d9be3456054979d56c6af2e2960d 100644
--- a/token.tokens.inc
+++ b/token.tokens.inc
@@ -5,6 +5,7 @@
  * Token callbacks for the token module.
  */
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Render\Element;
 use Drupal\Component\Utility\Crypt;
@@ -69,7 +70,12 @@ function token_token_info_alter(&$info) {
 
     $token_type = $entity_info->get('token_type');
     if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
-      continue;
+      // Define tokens for entity type's without their own integration.
+      $info['types'][$entity_info->id()] = [
+        'name' => $entity_info->getLabel(),
+        'needs-data' => $entity_info->id(),
+        'module' => 'token',
+      ];
     }
 
     // Add [entity:url] tokens if they do not already exist.
@@ -1179,16 +1185,23 @@ function field_token_info_alter(&$info) {
 
     $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
     foreach ($fields as $field_name => $field) {
-      if (!($field instanceof FieldStorageConfigInterface)) {
+      // Ensure the token type exists.
+      if (!isset($info['types'][$token_type])) {
         continue;
       }
-      // If a token already exists for this field, then don't add it.
-      if (isset($info['tokens'][$token_type][$field_name])) {
+
+      // Ensure the token implements FieldStorageConfigInterface or is defined
+      // in token module.
+      $provider = '';
+      if (isset($info['types'][$token_type]['module'])) {
+        $provider = $info['types'][$token_type]['module'];
+      }
+      if (!($field instanceof FieldStorageConfigInterface) && $provider != 'token') {
         continue;
       }
 
-      // Ensure the tokens exist.
-      if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
+      // If a token already exists for this field, then don't add it.
+      if (isset($info['tokens'][$token_type][$field_name])) {
         continue;
       }
 
@@ -1224,7 +1237,9 @@ function field_token_info_alter(&$info) {
  *
  * Therefore it looks up in all bundles to find the most used instance.
  *
- * Based on field_views_field_label().
+ * Based on views_entity_field_label().
+ *
+ * @todo Resync this method with views_entity_field_label().
  */
 function _token_field_label($entity_type, $field_name) {
   $labels = [];
@@ -1233,7 +1248,7 @@ function _token_field_label($entity_type, $field_name) {
     $bundle_instances = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
     if (isset($bundle_instances[$field_name])) {
       $instance = $bundle_instances[$field_name];
-      $label = $instance->getLabel();
+      $label = (string) $instance->getLabel();
       $labels[$label] = isset($labels[$label]) ? ++$labels[$label] : 1;
     }
   }
@@ -1280,11 +1295,10 @@ function field_tokens($type, $tokens, array $data = array(), array $options = ar
       // token.
       if ($entity->hasField($name) && _token_module($data['token_type'], $name) == 'token') {
 
-        // Do not continue if the field is empty or not a configurable field.
-        if ($entity->get($name)->isEmpty() || !($entity->getFieldDefinition($name) instanceof FieldConfigInterface)) {
+        // Do not continue if the field is empty.
+        if ($entity->get($name)->isEmpty()) {
           continue;
         }
-
         $display_options = 'token';
         if (!$token_view_display) {
           // We don't have the token view display and should fall back on