diff --git a/tests/src/Functional/TokenCurrentPageTest.php b/tests/src/Functional/TokenCurrentPageTest.php
index f517014..a750ace 100644
--- a/tests/src/Functional/TokenCurrentPageTest.php
+++ b/tests/src/Functional/TokenCurrentPageTest.php
@@ -2,7 +2,9 @@
 
 namespace Drupal\Tests\token\Functional;
 
+use Drupal\block\Entity\Block;
 use Drupal\Core\Url;
+use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
 
 /**
  * Test the [current-page:*] tokens.
@@ -11,10 +13,12 @@ use Drupal\Core\Url;
  */
 class TokenCurrentPageTest extends TokenTestBase {
 
+  use TaxonomyTestTrait;
+
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['node'];
+  protected static $modules = ['node', 'taxonomy', 'block'];
 
   function testCurrentPageTokens() {
     // Cache clear is necessary because the frontpage was already cached by an
@@ -33,6 +37,8 @@ class TokenCurrentPageTest extends TokenTestBase {
       '[current-page:page-number]' => 1,
       '[current-page:query:foo]' => NULL,
       '[current-page:query:bar]' => NULL,
+      '[current-page:node:nid]' => NULL,
+      '[current-page:taxonomy_term:tid]' => NULL,
       // Deprecated tokens
       '[current-page:arg:0]' => 'user',
       '[current-page:arg:1]' => 'login',
@@ -57,6 +63,8 @@ class TokenCurrentPageTest extends TokenTestBase {
       '[current-page:page-number]' => 1,
       '[current-page:query:foo]' => 'bar',
       '[current-page:query:bar]' => NULL,
+      '[current-page:node:nid]' => $node->id(),
+      '[current-page:taxonomy_term:tid]' => NULL,
       // Deprecated tokens
       '[current-page:arg:0]' => 'node',
       '[current-page:arg:1]' => 1,
@@ -64,4 +72,95 @@ class TokenCurrentPageTest extends TokenTestBase {
     ];
     $this->assertPageTokens("/node/{$node->id()}", $tokens, [], ['url_options' => ['query' => ['foo' => 'bar']]]);
   }
+
+  /*
+   * Test tokens like [current-page:node:nid].
+   */
+  public function testCurrentPageObjectTokens() {
+    // We are especially interested in testing caching.
+    // Imitate strategy of UrlTest::testBlockUrlTokenReplacement().
+    $this->drupalCreateContentType(['type' => 'page']);
+    // Put the first node in a variable for later manipulation.
+    $node1 = $this->drupalCreateNode(['title' => 'Node the First']);
+    $this->drupalCreateNode(['title' => 'Node the Second']);
+    $vocab = $this->createVocabulary();
+    $this->createTerm($vocab, ['name' => 'Term the First']);
+    $this->createTerm($vocab, ['name' => 'Term the Second']);
+    // Place a standard block and use a token in the label.
+    $edit = [
+      'id' => 'token_url_test_block',
+      'label' => 'label',
+      'label_display' => TRUE,
+    ];
+    $this->placeBlock('system_powered_by_block', $edit);
+    $block = Block::load('token_url_test_block');
+
+    $tests = [];
+    // Chained node token.
+    $tests[] = [
+      'token' => 'prefix_[current-page:node:title]_suffix',
+      'node1' => 'prefix_Node the First_suffix',
+      'node2' => 'prefix_Node the Second_suffix',
+      'term1' => 'prefix_[current-page:node:title]_suffix',
+      'term2' => 'prefix_[current-page:node:title]_suffix',
+      'node1_new_title' => 'New Title',
+      'node1_new_expected' => 'prefix_New Title_suffix',
+    ];
+    // Chained taxonomy_term token.
+    $tests[] = [
+      'token' => 'prefix_[current-page:taxonomy_term:tid]_suffix',
+      'node1' => 'prefix_[current-page:taxonomy_term:tid]_suffix',
+      'node2' => 'prefix_[current-page:taxonomy_term:tid]_suffix',
+      'term1' => 'prefix_1_suffix',
+      'term2' => 'prefix_2_suffix',
+    ];
+    // Show that 'term' token does not work.
+    // The current-page:object token does not use the token mapper service.
+    $tests[] = [
+      'token' => 'prefix_[current-page:term:tid]_suffix',
+      'node1' => 'prefix_[current-page:term:tid]_suffix',
+      'node2' => 'prefix_[current-page:term:tid]_suffix',
+      'term1' => 'prefix_[current-page:term:tid]_suffix',
+      'term2' => 'prefix_[current-page:term:tid]_suffix',
+    ];
+    // Unchained node token.
+    $tests[] = [
+      'token' => 'prefix_[current-page:node]_suffix',
+      'node1' => 'prefix_Node the First_suffix',
+      'node2' => 'prefix_Node the Second_suffix',
+      'term1' => 'prefix_[current-page:node]_suffix',
+      'term2' => 'prefix_[current-page:node]_suffix',
+      'node1_new_title' => 'Updated Title',
+      'node1_new_expected' => 'prefix_Updated Title_suffix',
+    ];
+
+    $assert_session = $this->assertSession();
+    foreach ($tests as $test) {
+      // Set the block label.
+      $block->getPlugin()->setConfigurationValue('label', $test['token']);
+      $block->save();
+
+      // Then visit each entity, testing cache context.
+      $this->drupalGet('node/1');
+      $assert_session->elementContains('css', '#block-token-url-test-block', $test['node1']);
+
+      $this->drupalGet('node/2');
+      $assert_session->elementContains('css', '#block-token-url-test-block', $test['node2']);
+
+      $this->drupalGet('taxonomy/term/1');
+      $assert_session->elementContains('css', '#block-token-url-test-block', $test['term1']);
+
+      $this->drupalGet('taxonomy/term/2');
+      $assert_session->elementContains('css', '#block-token-url-test-block', $test['term2']);
+
+      if (isset($test['node1_new_title'])) {
+        // Update node1 and revisit, testing cache tags.
+        $node1->set('title', $test['node1_new_title'])->save();
+        $this->drupalGet('node/1');
+        $assert_session->elementContains('css', '#block-token-url-test-block', $test['node1_new_expected']);
+        // Change to to original title.
+        $node1->set('title', 'Node the First')->save();
+      }
+    }
+  }
 }
diff --git a/token.tokens.inc b/token.tokens.inc
index eeab905..914f372 100755
--- a/token.tokens.inc
+++ b/token.tokens.inc
@@ -344,6 +344,20 @@ function token_token_info() {
     'type' => 'language',
   ];
 
+  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+    // Do not generate tokens if the entity doesn't define a token type or is
+    // not a content entity.
+    if (!$entity_type->get('token_type') || (!$entity_type instanceof ContentEntityTypeInterface)) {
+      continue;
+    }
+
+    $info['tokens']['current-page'][$entity_type_id] = [
+      'name' => t('The current %type', ['%type' => $entity_type->getLabel()]),
+      'description' => t("The current page object if that's a %type", ['%type' => $entity_type->getLabel()]),
+      'type' => \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id),
+    ];
+  }
+
   // URL tokens.
   $info['types']['url'] = [
     'name' => t('URL'),
@@ -885,6 +899,53 @@ function token_tokens($type, array $tokens, array $data, array $options, Bubblea
           }
           $replacements[$original] = (int) $page + 1;
           break;
+
+        default:
+          // This is the case that handles the current-page:object token.
+          // Things like [current-page:node:field_foo].
+
+          // Parse token to determine entity type.
+          $entity_type_manager = \Drupal::entityTypeManager();
+          $parts = explode(':', $name);
+          $entity_type = $parts[0];
+          if (!$entity_type_manager->hasDefinition($entity_type)) {
+            break;
+          }
+
+          // Entity type is valid. Add url.path cache context. We need to
+          // do this regardless of whether or not the entity in question
+          // exists for the current page.
+          $bubbleable_metadata->addCacheContexts(['url.path']);
+
+          // Load entity if it exists.
+          $entity_id = \Drupal::routeMatch()->getRawParameter($entity_type);
+          if ($entity_id) {
+            $entity = $entity_type_manager->getStorage($entity_type)->load($entity_id);
+          }
+          if (!isset($entity)) {
+            break;
+          }
+
+          // No child properties, so load the entity label.
+          // For example [current-page:node].
+          if ($name == $entity_type) {
+            $label = $entity->label();
+            $replacements[$original] = $label;
+            $bubbleable_metadata->addCacheableDependency($entity);
+          }
+          // Load child properties via recursive tokens.
+          // For example [current-page:node:nid].
+          else {
+            $entity_tokens = \Drupal::token()->findWithPrefix($tokens, $entity_type);
+            $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type);
+            $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
+            if ($entity) {
+              $options['langcode'] = $langcode;
+            }
+            $replacements += \Drupal::token()->generate($token_type, $entity_tokens, [$token_type => $entity], $options, $bubbleable_metadata);
+          }
+
+          break;
       }
       // [current-page:interface-language:*] chained tokens.
       if ($language_interface_tokens = \Drupal::token()->findWithPrefix($tokens, 'interface-language')) {
