diff --git a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
index f39057308c..71631a72c3 100644
--- a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
+++ b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
@@ -241,9 +241,9 @@ public function testBlockMigration() {
 
     // Check statistic block settings.
     $settings = [
-      'id' => 'broken',
+      'id' => 'statistics_popular_block',
       'label' => '',
-      'provider' => 'core',
+      'provider' => 'statistics',
       'label_display' => '0',
       'top_day_num' => 7,
       'top_all_num' => 8,
diff --git a/core/modules/search/tests/src/Functional/SearchRankingTest.php b/core/modules/search/tests/src/Functional/SearchRankingTest.php
index d444d3e2fa..028e2deca0 100644
--- a/core/modules/search/tests/src/Functional/SearchRankingTest.php
+++ b/core/modules/search/tests/src/Functional/SearchRankingTest.php
@@ -114,11 +114,12 @@ public function testRankings() {
     $this->submitForm($edit, 'Save');
 
     // Enable counting of statistics.
-    $this->config('statistics.settings')->set('count_content_views', 1)->save();
+    $this->config('statistics.settings')->set('entity_type_ids', ['node'])->save();
 
     // Simulating content views is kind of difficult in the test. Leave that
     // to the Statistics module. So instead go ahead and manually update the
     // counter for this node.
+    \Drupal::service('statistics.storage')->createTable(\Drupal::entityTypeManager()->getDefinition('node'));
     $nid = $nodes['views'][1]->id();
     Database::getConnection()->insert('node_counter')
       ->fields(['totalcount' => 5, 'daycount' => 5, 'timestamp' => REQUEST_TIME, 'nid' => $nid])
diff --git a/core/modules/statistics/config/install/statistics.settings.yml b/core/modules/statistics/config/install/statistics.settings.yml
index 6686062923..8c73e19338 100644
--- a/core/modules/statistics/config/install/statistics.settings.yml
+++ b/core/modules/statistics/config/install/statistics.settings.yml
@@ -1,2 +1,2 @@
-count_content_views: 0
+entity_type_ids: {}
 display_max_age: 3600
diff --git a/core/modules/statistics/config/schema/statistics.schema.yml b/core/modules/statistics/config/schema/statistics.schema.yml
index c72a227264..0c2cd05008 100644
--- a/core/modules/statistics/config/schema/statistics.schema.yml
+++ b/core/modules/statistics/config/schema/statistics.schema.yml
@@ -4,12 +4,15 @@ statistics.settings:
   type: config_object
   label: 'Statistics settings'
   mapping:
-    count_content_views:
-      type: integer
-      label: 'Count content views'
     display_max_age:
       type: integer
       label: 'How long any statistics may be cached, i.e. the refresh interval'
+    entity_type_ids:
+      type: sequence
+      label: 'Entity Type IDs'
+      sequence:
+        type: string
+        label: 'Entity Type ID'
 
 block.settings.statistics_popular_block:
   type: block_settings
diff --git a/core/modules/statistics/migrations/statistics_settings.yml b/core/modules/statistics/migrations/statistics_settings.yml
index edc0e049f7..f1f8942319 100644
--- a/core/modules/statistics/migrations/statistics_settings.yml
+++ b/core/modules/statistics/migrations/statistics_settings.yml
@@ -12,7 +12,11 @@ source:
     - statistics_count_content_views
   source_module: statistics
 process:
-  'count_content_views': statistics_count_content_views
+  entity_type_ids:
+    -
+      plugin: callback
+      callable: statistics_migrate_callback
+      source: statistics_count_content_views
 destination:
   plugin: config
   config_name: statistics.settings
diff --git a/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php b/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php
index 583f5be9d4..4dad486aea 100644
--- a/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php
+++ b/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php
@@ -2,139 +2,67 @@
 
 namespace Drupal\statistics;
 
-use Drupal\Core\Database\Connection;
-use Drupal\Core\State\StateInterface;
-use Symfony\Component\HttpFoundation\RequestStack;
+use Drupal\Core\Entity\EntityTypeInterface;
 
 /**
  * Provides the default database storage backend for statistics.
  */
-class NodeStatisticsDatabaseStorage implements StatisticsStorageInterface {
-
-  /**
-   * The database connection used.
-   *
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $connection;
-
-  /**
-   * The state service.
-   *
-   * @var \Drupal\Core\State\StateInterface
-   */
-  protected $state;
-
-  /**
-   * The request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
-   */
-  protected $requestStack;
-
-  /**
-   * Constructs the statistics storage.
-   *
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection for the node view storage.
-   * @param \Drupal\Core\State\StateInterface $state
-   *   The state service.
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack.
-   */
-  public function __construct(Connection $connection, StateInterface $state, RequestStack $request_stack) {
-    $this->connection = $connection;
-    $this->state = $state;
-    $this->requestStack = $request_stack;
-  }
+class NodeStatisticsDatabaseStorage extends StatisticsDatabaseStorage {
 
   /**
    * {@inheritdoc}
    */
-  public function recordView($id) {
-    return (bool) $this->connection
-      ->merge('node_counter')
-      ->key('nid', $id)
-      ->fields([
-        'daycount' => 1,
-        'totalcount' => 1,
-        'timestamp' => $this->getRequestTime(),
-      ])
-      ->expression('daycount', '[daycount] + 1')
-      ->expression('totalcount', '[totalcount] + 1')
-      ->execute();
+  public function recordView($entity_type_id, $key, $id) {
+    // TODO: Change the autogenerated stub.
+    return parent::recordView($entity_type_id, $key, $id);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function fetchViews($ids) {
-    $views = $this->connection
-      ->select('node_counter', 'nc')
-      ->fields('nc', ['totalcount', 'daycount', 'timestamp'])
-      ->condition('nid', $ids, 'IN')
-      ->execute()
-      ->fetchAll();
-    foreach ($views as $id => $view) {
-      $views[$id] = new StatisticsViewsResult($view->totalcount, $view->daycount, $view->timestamp);
-    }
-    return $views;
+  public function fetchView(EntityTypeInterface $entity_type, $id) {
+    // TODO: Change the autogenerated stub.
+    return parent::fetchView($entity_type, $id);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function fetchView($id) {
-    $views = $this->fetchViews([$id]);
-    return reset($views);
+  public function fetchAll(EntityTypeInterface $entity_type, $order = 'totalcount', $limit = 5) {
+    // TODO: Change the autogenerated stub.
+    return parent::fetchAll($entity_type, $order, $limit);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function fetchAll($order = 'totalcount', $limit = 5) {
-    assert(in_array($order, ['totalcount', 'daycount', 'timestamp']), "Invalid order argument.");
-
-    return $this->connection
-      ->select('node_counter', 'nc')
-      ->fields('nc', ['nid'])
-      ->orderBy($order, 'DESC')
-      ->range(0, $limit)
-      ->execute()
-      ->fetchCol();
+  public function deleteViews(EntityTypeInterface $entity_type, $id) {
+    // TODO: Change the autogenerated stub.
+    return parent::deleteViews($entity_type, $id);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function deleteViews($id) {
-    return (bool) $this->connection
-      ->delete('node_counter')
-      ->condition('nid', $id)
-      ->execute();
+  public function maxTotalCount(EntityTypeInterface $entity_type) {
+    // TODO: Change the autogenerated stub.
+    return parent::maxTotalCount($entity_type);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function resetDayCount() {
-    $statistics_timestamp = $this->state->get('statistics.day_timestamp', 0);
-    if (($this->getRequestTime() - $statistics_timestamp) >= 86400) {
-      $this->state->set('statistics.day_timestamp', $this->getRequestTime());
-      $this->connection->update('node_counter')
-        ->fields(['daycount' => 0])
-        ->execute();
-    }
+  public function createTable(EntityTypeInterface $entity_type) {
+    // TODO: Change the autogenerated stub.
+    parent::createTable($entity_type);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function maxTotalCount() {
-    $query = $this->connection->select('node_counter', 'nc');
-    $query->addExpression('MAX([totalcount])');
-    $max_total_count = (int) $query->execute()->fetchField();
-    return $max_total_count;
+  public function dropTable(EntityTypeInterface $entity_type) {
+    // TODO: Change the autogenerated stub.
+    parent::dropTable($entity_type);
   }
 
   /**
diff --git a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
index 53e39c3333..0f6fa25ea5 100644
--- a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
+++ b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
@@ -85,7 +85,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('entity_type.manager'),
       $container->get('entity.repository'),
-      $container->get('statistics.storage.node'),
+      $container->get('statistics.storage'),
       $container->get('renderer')
     );
   }
@@ -153,9 +153,10 @@ public function blockSubmit($form, FormStateInterface $form_state) {
    */
   public function build() {
     $content = [];
+    $entity_type = $this->entityTypeManager->getDefinition('node');
 
     if ($this->configuration['top_day_num'] > 0) {
-      $nids = $this->statisticsStorage->fetchAll('daycount', $this->configuration['top_day_num']);
+      $nids = $this->statisticsStorage->fetchAll($entity_type, 'daycount', $this->configuration['top_day_num']);
       if ($nids) {
         $content['top_day'] = $this->nodeTitleList($nids, $this->t("Today's:"));
         $content['top_day']['#suffix'] = '<br />';
@@ -163,7 +164,7 @@ public function build() {
     }
 
     if ($this->configuration['top_all_num'] > 0) {
-      $nids = $this->statisticsStorage->fetchAll('totalcount', $this->configuration['top_all_num']);
+      $nids = $this->statisticsStorage->fetchAll($entity_type, 'totalcount', $this->configuration['top_all_num']);
       if ($nids) {
         $content['top_all'] = $this->nodeTitleList($nids, $this->t('All time:'));
         $content['top_all']['#suffix'] = '<br />';
@@ -171,7 +172,7 @@ public function build() {
     }
 
     if ($this->configuration['top_last_num'] > 0) {
-      $nids = $this->statisticsStorage->fetchAll('timestamp', $this->configuration['top_last_num']);
+      $nids = $this->statisticsStorage->fetchAll($entity_type, 'timestamp', $this->configuration['top_last_num']);
       $content['top_last'] = $this->nodeTitleList($nids, $this->t('Last viewed:'));
       $content['top_last']['#suffix'] = '<br />';
     }
diff --git a/core/modules/statistics/src/Plugin/views/field/EntityCounterTimestamp.php b/core/modules/statistics/src/Plugin/views/field/EntityCounterTimestamp.php
new file mode 100644
index 0000000000..9bc2a9c089
--- /dev/null
+++ b/core/modules/statistics/src/Plugin/views/field/EntityCounterTimestamp.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\statistics\Plugin\views\field;
+
+use Drupal\views\Plugin\views\field\Date;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Field handler to display the most recent time the entity has been viewed.
+ *
+ * @ingroup views_field_handlers
+ *
+ * @ViewsField("entity_counter_timestamp")
+ */
+class EntityCounterTimestamp extends Date {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(AccountInterface $account) {
+    return $account->hasPermission('view post access counter');
+  }
+
+}
diff --git a/core/modules/statistics/src/Plugin/views/field/NodeCounterTimestamp.php b/core/modules/statistics/src/Plugin/views/field/NodeCounterTimestamp.php
index fb0eb3049e..037526920f 100644
--- a/core/modules/statistics/src/Plugin/views/field/NodeCounterTimestamp.php
+++ b/core/modules/statistics/src/Plugin/views/field/NodeCounterTimestamp.php
@@ -2,9 +2,6 @@
 
 namespace Drupal\statistics\Plugin\views\field;
 
-use Drupal\views\Plugin\views\field\Date;
-use Drupal\Core\Session\AccountInterface;
-
 /**
  * Field handler to display the most recent time the node has been viewed.
  *
@@ -12,13 +9,6 @@
  *
  * @ViewsField("node_counter_timestamp")
  */
-class NodeCounterTimestamp extends Date {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access(AccountInterface $account) {
-    return $account->hasPermission('view post access counter');
-  }
+class NodeCounterTimestamp extends EntityCounterTimestamp {
 
 }
diff --git a/core/modules/statistics/src/StatisticsDatabaseStorage.php b/core/modules/statistics/src/StatisticsDatabaseStorage.php
new file mode 100644
index 0000000000..9e8937493c
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsDatabaseStorage.php
@@ -0,0 +1,245 @@
+<?php
+
+namespace Drupal\statistics;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Provides the default database storage backend for statistics.
+ */
+class StatisticsDatabaseStorage implements StatisticsStorageInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs the statistics storage.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection for the node view storage.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(Connection $connection, RequestStack $request_stack) {
+    $this->connection = $connection;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function recordView($entity_type_id, $key, $id) {
+    $table = $entity_type_id . '_counter';
+    try {
+      return (bool) $this->connection
+        ->merge($table)
+        ->key($key, $id)
+        ->fields([
+          'daycount' => 1,
+          'totalcount' => 1,
+          'timestamp' => $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME'),
+        ])
+        ->expression('daycount', 'daycount + 1')
+        ->expression('totalcount', 'totalcount + 1')
+        ->execute();
+    }
+    catch (\Exception $e) {
+      $database_schema = $this->connection->schema();
+      if ($database_schema->tableExists($table)) {
+        throw $e;
+      }
+      else {
+        $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+        $this->createTable($entity_type);
+        $this->recordView($entity_type_id, $key, $id);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchView(EntityTypeInterface $entity_type, $id) {
+    try {
+      $view = $this->connection
+        ->select($this->tableName($entity_type), 'c')
+        ->fields('c', ['totalcount', 'daycount', 'timestamp'])
+        ->condition($entity_type->getKey('id'), $id)
+        ->execute()
+        ->fetchObject();
+      if ($view) {
+        return new StatisticsViewsResult($view->totalcount, $view->daycount, $view->timestamp);
+      }
+    }
+    catch (\Exception $e) {
+      $this->catchException($entity_type, $e);
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchAll(EntityTypeInterface $entity_type, $order = 'totalcount', $limit = 5) {
+    assert(in_array($order, ['totalcount', 'daycount', 'timestamp']), "Invalid order argument.");
+    try {
+      return $this->connection
+        ->select($this->tableName($entity_type), 'nc')
+        ->fields('nc', [$entity_type->getKey('id')])
+        ->orderBy($order, 'DESC')
+        ->range(0, $limit)
+        ->execute()
+        ->fetchCol();
+    }
+    catch (\Exception $e) {
+      $this->catchException($entity_type, $e);
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteViews(EntityTypeInterface $entity_type, $id) {
+    try {
+      return (bool) $this->connection
+        ->delete($this->tableName($entity_type))
+        ->condition($entity_type->getKey('id'), $id)
+        ->execute();
+    }
+    catch (\Exception $e) {
+      $this->catchException($entity_type, $e);
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function maxTotalCount(EntityTypeInterface $entity_type) {
+    try {
+      $query = $this->connection->select($this->tableName($entity_type), 'nc');
+      $query->addExpression('MAX(totalcount)');
+      $max_total_count = (int) $query->execute()->fetchField();
+      return $max_total_count;
+    }
+    catch (\Exception $e) {
+      $this->catchException($entity_type, $e);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createTable(EntityTypeInterface $entity_type) {
+    $entity_type_id = $entity_type->id();
+    $idKey = $entity_type->getKey('id');
+    $id_definition = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($entity_type_id)[$idKey];
+    if ($id_definition->getType() === 'integer') {
+      $id_schema = [
+        'description' => "The {{$entity_type_id}}.$idKey for these statistics.",
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ];
+    }
+    else {
+      $id_schema = [
+        'description' => "The {{$entity_type_id}}.$idKey for these statistics.",
+        'type' => 'varchar_ascii',
+        'length' => 128,
+        'not null' => TRUE,
+      ];
+    }
+    $table = $entity_type_id . '_counter';
+    if (!$this->connection->schema()->tableExists($table)) {
+      $schema = [
+        'description' => "Access statistics for {{$entity_type_id}}s.",
+        'fields' => [
+          $idKey => $id_schema,
+          'totalcount' => [
+            'description' => "The total number of times the {{$entity_type_id}} has been viewed.",
+            'type' => 'int',
+            'unsigned' => TRUE,
+            'not null' => TRUE,
+            'default' => 0,
+            'size' => 'big',
+          ],
+          'daycount' => [
+            'description' => "The total number of times the {{$entity_type_id}} has been viewed today.",
+            'type' => 'int',
+            'unsigned' => TRUE,
+            'not null' => TRUE,
+            'default' => 0,
+            'size' => 'medium',
+          ],
+          'timestamp' => [
+            'description' => "The most recent time the {{$entity_type_id}} has been viewed.",
+            'type' => 'int',
+            'unsigned' => TRUE,
+            'not null' => TRUE,
+            'default' => 0,
+          ],
+        ],
+        'primary key' => [$idKey],
+      ];
+      $this->connection->schema()->createTable($table, $schema);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dropTable(EntityTypeInterface $entity_type) {
+    if ($this->connection->schema()->tableExists($this->tableName($entity_type))) {
+      $this->connection->schema()->dropTable($this->tableName($entity_type));
+    }
+  }
+
+  /**
+   * Act on an exception when the table might not have been created.
+   *
+   * If the table does not yet exist, that's fine, but if the table exists and
+   * something else caused the exception, then propagate it.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   * @param \Exception $e
+   *   The exception.
+   *
+   * @throws \Exception
+   */
+  protected function catchException(EntityTypeInterface $entity_type, \Exception $e) {
+    if ($this->connection->schema()->tableExists($this->tableName($entity_type))) {
+      throw $e;
+    }
+  }
+
+  /**
+   * Generates the table name from the entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return string
+   */
+  protected function tableName(EntityTypeInterface $entity_type) {
+    $entity_type_id = $entity_type->id();
+    return $entity_type_id . '_counter';
+  }
+
+}
diff --git a/core/modules/statistics/src/StatisticsResetCount.php b/core/modules/statistics/src/StatisticsResetCount.php
new file mode 100644
index 0000000000..e87d7f1487
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsResetCount.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\statistics;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\State\StateInterface;
+
+/**
+ * The statistics reset count class.
+ */
+class StatisticsResetCount implements StatisticsResetCountInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
+  /**
+   * Constructs the statistics reset count.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection for the node view storage.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   */
+  public function __construct(Connection $connection, StateInterface $state, TimeInterface $time) {
+    $this->connection = $connection;
+    $this->state = $state;
+    $this->time = $time;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetDayCount($entity_type_id) {
+    $statistics_timestamp = $this->state->get('statistics.day_timestamp') ?: 0;
+    $time = $this->time->getRequestTime();
+    $table = $entity_type_id . '_counter';
+    if (($time - $statistics_timestamp) >= 86400 && $this->connection->schema()->tableExists($table)) {
+      $this->state->set('statistics.day_timestamp', $time);
+      $this->connection->update($table)
+        ->fields(['daycount' => 0])
+        ->execute();
+    }
+  }
+
+}
diff --git a/core/modules/statistics/src/StatisticsResetCountInterface.php b/core/modules/statistics/src/StatisticsResetCountInterface.php
new file mode 100644
index 0000000000..d4b493cfa3
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsResetCountInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\statistics;
+
+/**
+ * The statistics reset count interface.
+ */
+interface StatisticsResetCountInterface {
+
+  /**
+   * Reset the day counter for all entities once every day.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   */
+  public function resetDayCount($entity_type_id);
+
+}
diff --git a/core/modules/statistics/src/StatisticsSettingsForm.php b/core/modules/statistics/src/StatisticsSettingsForm.php
index 49b6a6dacc..a444194e64 100644
--- a/core/modules/statistics/src/StatisticsSettingsForm.php
+++ b/core/modules/statistics/src/StatisticsSettingsForm.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\statistics;
 
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityTypeRepositoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -23,16 +25,46 @@ class StatisticsSettingsForm extends ConfigFormBase {
   protected $moduleHandler;
 
   /**
-   * Constructs a \Drupal\statistics\StatisticsSettingsForm object.
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity type repository service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
+   */
+  protected $entityTypeRepository;
+
+  /**
+   * The storage for statistics.
+   *
+   * @var \Drupal\statistics\StatisticsStorageInterface
+   */
+  protected $statisticsStorage;
+
+  /**
+   * Constructs a \Drupal\user\StatisticsSettingsForm object.
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
+   *   The entity type repository service.
+   * @param \Drupal\statistics\StatisticsStorageInterface $statistics_storage
+   *   The storage for statistics.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler) {
+  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, EntityTypeRepositoryInterface $entity_type_repository, StatisticsStorageInterface $statistics_storage) {
     parent::__construct($config_factory);
 
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityTypeRepository = $entity_type_repository;
+    $this->statisticsStorage = $statistics_storage;
     $this->moduleHandler = $module_handler;
   }
 
@@ -42,7 +74,10 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
-      $container->get('module_handler')
+      $container->get('module_handler'),
+      $container->get('entity_type.manager'),
+      $container->get('entity_type.repository'),
+      $container->get('statistics.storage')
     );
   }
 
@@ -65,18 +100,26 @@ protected function getEditableConfigNames() {
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     $config = $this->config('statistics.settings');
+    $labels = $this->entityTypeRepository->getEntityTypeLabels(TRUE);
+    $labels = $labels[(string) $this->t('Content', [], ['context' => 'Entity type group'])];
+    $options = [];
+    foreach ($labels as $entity_type_id => $label) {
+      $options[$entity_type_id] = $this->t('Enable statistics for @entity_type', ['@entity_type' => $label]);
+    }
 
-    // Content counter settings.
+    // Content entity counter settings.
     $form['content'] = [
       '#type' => 'details',
       '#title' => $this->t('Content viewing counter settings'),
+      '#description' => $this->t('Increment a counter each time content is viewed.'),
       '#open' => TRUE,
     ];
-    $form['content']['statistics_count_content_views'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Count content views'),
-      '#default_value' => $config->get('count_content_views'),
-      '#description' => $this->t('Increment a counter each time content is viewed.'),
+
+    $form['content']['entity_type_ids'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Select content'),
+      '#options' => $options,
+      '#default_value' => $config->get('entity_type_ids'),
     ];
 
     return parent::buildForm($form, $form_state);
@@ -86,9 +129,19 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity_type_ids = $form_state->getValue('entity_type_ids');
     $this->config('statistics.settings')
-      ->set('count_content_views', $form_state->getValue('statistics_count_content_views'))
+      ->set('entity_type_ids', $entity_type_ids)
       ->save();
+    foreach (array_keys($entity_type_ids) as $entity_type_id) {
+      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+      if ($entity_type_ids[$entity_type_id] === $entity_type_id) {
+        $this->statisticsStorage->createTable($entity_type);
+      }
+      else {
+        $this->statisticsStorage->dropTable($entity_type);
+      }
+    }
 
     // The popular statistics block is dependent on these settings, so clear the
     // block plugin definitions cache.
diff --git a/core/modules/statistics/src/StatisticsStorageInterface.php b/core/modules/statistics/src/StatisticsStorageInterface.php
index b5da88a9f3..68a51d7efd 100644
--- a/core/modules/statistics/src/StatisticsStorageInterface.php
+++ b/core/modules/statistics/src/StatisticsStorageInterface.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\statistics;
 
+use Drupal\Core\Entity\EntityTypeInterface;
+
 /**
  * Provides an interface defining Statistics Storage.
  *
@@ -13,42 +15,36 @@
   /**
    * Counts an entity view.
    *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $key
+   *   The ID key of the entity to count.
    * @param int $id
    *   The ID of the entity to count.
    *
    * @return bool
    *   TRUE if the entity view has been counted.
    */
-  public function recordView($id);
-
-  /**
-   * Returns the number of times entities have been viewed.
-   *
-   * @param array $ids
-   *   An array of IDs of entities to fetch the views for.
-   *
-   * @return \Drupal\statistics\StatisticsViewsResult[]
-   *   An array of value objects representing the number of times each entity
-   *   has been viewed. The array is keyed by entity ID. If an ID does not
-   *   exist, it will not be present in the array.
-   */
-  public function fetchViews($ids);
+  public function recordView($entity_type_id, $key, $id);
 
   /**
    * Returns the number of times a single entity has been viewed.
    *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
    * @param int $id
    *   The ID of the entity to fetch the views for.
    *
-   * @return \Drupal\statistics\StatisticsViewsResult|false
-   *   If the entity exists, a value object representing the number of times if
-   *   has been viewed. If it does not exist, FALSE is returned.
+   * @return \Drupal\statistics\StatisticsViewsResult|null
+   *   The StatisticsViewsResult object if the result is present NULL otherwise.
    */
-  public function fetchView($id);
+  public function fetchView(EntityTypeInterface $entity_type, $id);
 
   /**
    * Returns the number of times an entity has been viewed.
    *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
    * @param string $order
    *   The counter name to order by:
    *   - 'totalcount' The total number of views.
@@ -60,30 +56,46 @@ public function fetchView($id);
    * @return array
    *   An ordered array of entity IDs.
    */
-  public function fetchAll($order = 'totalcount', $limit = 5);
+  public function fetchAll(EntityTypeInterface $entity_type, $order = 'totalcount', $limit = 5);
 
   /**
    * Delete counts for a specific entity.
    *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
    * @param int $id
    *   The ID of the entity which views to delete.
    *
    * @return bool
    *   TRUE if the entity views have been deleted.
    */
-  public function deleteViews($id);
-
-  /**
-   * Reset the day counter for all entities once every day.
-   */
-  public function resetDayCount();
+  public function deleteViews(EntityTypeInterface $entity_type, $id);
 
   /**
    * Returns the highest 'totalcount' value.
    *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
    * @return int
    *   The highest 'totalcount' value.
    */
-  public function maxTotalCount();
+  public function maxTotalCount(EntityTypeInterface $entity_type);
+
+  /**
+   * Creates entity counter table.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   */
+  public function createTable(EntityTypeInterface $entity_type);
+
+  /**
+   * Drops entity counter table.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   */
+  public function dropTable(EntityTypeInterface $entity_type);
 
 }
diff --git a/core/modules/statistics/statistics.install b/core/modules/statistics/statistics.install
index f5c8a82fce..43f1ae8243 100644
--- a/core/modules/statistics/statistics.install
+++ b/core/modules/statistics/statistics.install
@@ -9,58 +9,33 @@
  * Implements hook_uninstall().
  */
 function statistics_uninstall() {
+  $state = \Drupal::state();
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $statistic_storage = \Drupal::service('statistics.storage');
   // Remove states.
-  \Drupal::state()->delete('statistics.node_counter_scale');
-  \Drupal::state()->delete('statistics.day_timestamp');
+  $state->delete('statistics.day_timestamp');
+  $entity_types = \Drupal::configFactory()->get('statistics.settings')->get('entity_type_ids');
+  foreach (array_keys($entity_types) as $entity_type_id) {
+    $state->delete("statistics.{$entity_type_id}_counter_scale");
+    $statistic_storage->dropTable($entity_type_manager->getDefinition($entity_type_id));
+  }
 }
 
 /**
- * Implements hook_schema().
+ * Implements hook_update_last_removed().
  */
-function statistics_schema() {
-  $schema['node_counter'] = [
-    'description' => 'Access statistics for {node}s.',
-    'fields' => [
-      'nid' => [
-        'description' => 'The {node}.nid for these statistics.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ],
-      'totalcount' => [
-        'description' => 'The total number of times the {node} has been viewed.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'big',
-      ],
-      'daycount' => [
-        'description' => 'The total number of times the {node} has been viewed today.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'medium',
-      ],
-      'timestamp' => [
-        'description' => 'The most recent time the {node} has been viewed.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ],
-    ],
-    'primary key' => ['nid'],
-  ];
-
-  return $schema;
+function statistics_update_last_removed() {
+  return 8300;
 }
 
 /**
- * Implements hook_update_last_removed().
+ * Replace count_content_views settings with entity_type_ids setting.
  */
-function statistics_update_last_removed() {
-  return 8300;
+function statistics_update_8301() {
+  $config = \Drupal::configFactory()->getEditable('statistics.settings');
+  $value = $config->get('count_content_views') ? ['node' => 'node'] : [];
+  // Remove the old count_content_views configuration setting.
+  $config->clear('count_content_views')
+  // Set the new configuration setting for entity_type_ids to the initial value.
+    ->set('entity_type_ids', $value)->save();
 }
diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module
index 40066c611f..0dfc1e1f16 100644
--- a/core/modules/statistics/statistics.module
+++ b/core/modules/statistics/statistics.module
@@ -38,9 +38,45 @@ function statistics_help($route_name, RouteMatchInterface $route_match) {
  * Implements hook_ENTITY_TYPE_view() for node entities.
  */
 function statistics_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
-  if (!$node->isNew() && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
+  if (!$node->isNew() && $view_mode === 'full' && node_is_page($node) && empty($node->in_preview)) {
     $build['#attached']['library'][] = 'statistics/drupal.statistics';
-    $settings = ['data' => ['nid' => $node->id()], 'url' => \Drupal::request()->getBasePath() . '/' . \Drupal::service('extension.list.module')->getPath('statistics') . '/statistics.php'];
+    $settings = [
+      'data' => [
+        'key' => 'nid',
+        'id' => $node->id(),
+        'type' => 'node',
+      ],
+      'url' => Url::fromUri('base:' . \Drupal::service('extension.path.resolver')->getPath('module', 'statistics') . '/statistics.php')->toString(),
+    ];
+    $build['#attached']['drupalSettings']['statistics'] = $settings;
+  }
+}
+
+/**
+ * Implements hook_ENTITY_view() for node entities.
+ */
+function statistics_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
+  $entity_type_id = $entity->getEntityTypeId();
+  // @todo Remove this condition once statistics_node_view() is removed.
+  if ($entity_type_id === 'node') {
+    return;
+  }
+  $route_match = \Drupal::routeMatch();
+  $route_name = "entity.$entity_type_id.canonical";
+  if ($route_match->getRouteName() === $route_name) {
+    $page_entity = $route_match->getParameter($entity_type_id);
+  }
+  $entity_is_page = (!empty($page_entity) ? $page_entity->id() === $entity->id() : FALSE);
+  if (!$entity->isNew() && $view_mode === 'full' && $entity_is_page && empty($entity->in_preview)) {
+    $build['#attached']['library'][] = 'statistics/drupal.statistics';
+    $settings = [
+      'data' => [
+        'key' => $entity->getEntityType()->getKey('id'),
+        'id' => $entity->id(),
+        'type' => $entity_type_id,
+      ],
+      'url' => Url::fromUri('base:' . \Drupal::service('extension.path.resolver')->getPath('module', 'statistics') . '/statistics.php')->toString(),
+    ];
     $build['#attached']['drupalSettings']['statistics'] = $settings;
   }
 }
@@ -49,10 +85,11 @@ function statistics_node_view(array &$build, EntityInterface $node, EntityViewDi
  * Implements hook_node_links_alter().
  */
 function statistics_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
-  if ($context['view_mode'] != 'rss') {
+  $entity_types = \Drupal::config('statistics.settings')->get('entity_type_ids');
+  if (in_array('node', $entity_types, TRUE) && $context['view_mode'] != 'rss') {
     $links['#cache']['contexts'][] = 'user.permissions';
     if (\Drupal::currentUser()->hasPermission('view post access counter')) {
-      $statistics = \Drupal::service('statistics.storage.node')->fetchView($entity->id());
+      $statistics = \Drupal::service('statistics.storage')->fetchView($entity->getEntityType(), $entity->id());
       if ($statistics) {
         $statistics_links['statistics_counter']['title'] = \Drupal::translation()->formatPlural($statistics->getTotalCount(), '1 view', '@count views');
         $links['statistics'] = [
@@ -70,26 +107,52 @@ function statistics_node_links_alter(array &$links, NodeInterface $entity, array
  * Implements hook_cron().
  */
 function statistics_cron() {
-  $storage = \Drupal::service('statistics.storage.node');
-  $storage->resetDayCount();
-  $max_total_count = $storage->maxTotalCount();
-  \Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1.0, $max_total_count));
+  $statistics_reset_count = \Drupal::service('statistics.reset_count');
+  $storage = \Drupal::service('statistics.storage');
+  $state = \Drupal::state();
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $entity_types = \Drupal::configFactory()->get('statistics.settings')->get('entity_type_ids');
+  foreach (array_filter($entity_types) as $entity_type_id) {
+    $statistics_reset_count->resetDayCount($entity_type_id);
+    $max_total_count = $storage->maxTotalCount($entity_type_manager->getDefinition($entity_type_id));
+    $state->set("statistics.{$entity_type_id}_counter_scale", 1.0 / max(1.0, $max_total_count));
+  }
 }
 
 /**
  * Implements hook_ENTITY_TYPE_predelete() for node entities.
  */
 function statistics_node_predelete(EntityInterface $node) {
-  // Clean up statistics table when node is deleted.
-  $id = $node->id();
-  return \Drupal::service('statistics.storage.node')->deleteViews($id);
+  $entity_types = \Drupal::config('statistics.settings')->get('entity_type_ids');
+  if (in_array('node', $entity_types, TRUE)) {
+    // Clean up statistics table when node is deleted.
+    \Drupal::service('statistics.storage')
+      ->deleteViews($node->getEntityType(), $node->id());
+  }
+}
+
+/**
+ * Implements hook_ENTITY_predelete() for node entities.
+ */
+function statistics_entity_predelete(EntityInterface $entity) {
+  // @todo Remove this condition once statistics_node_view() is removed.
+  if ($entity->getEntityTypeId() === 'node') {
+    return;
+  }
+  $entity_types = \Drupal::config('statistics.settings')->get('entity_type_ids');
+  if (in_array($entity->getEntityTypeId(), $entity_types, TRUE)) {
+    // Clean up statistics table when entity is deleted.
+    \Drupal::service('statistics.storage')
+      ->deleteViews($entity->getEntityType(), $entity->id());
+  }
 }
 
 /**
  * Implements hook_ranking().
  */
 function statistics_ranking() {
-  if (\Drupal::config('statistics.settings')->get('count_content_views')) {
+  $entity_types = \Drupal::configFactory()->get('statistics.settings')->get('entity_type_ids');
+  if (in_array('node', $entity_types, TRUE)) {
     return [
       'views' => [
         'title' => t('Number of views'),
@@ -128,8 +191,15 @@ function statistics_preprocess_block(&$variables) {
  * to count content views.
  */
 function statistics_block_alter(&$definitions) {
-  $statistics_count_content_views = \Drupal::config('statistics.settings')->get('count_content_views');
-  if (empty($statistics_count_content_views)) {
+  $entity_types = \Drupal::config('statistics.settings')->get('entity_type_ids');
+  if (!empty($entity_types) && !in_array('node', $entity_types, TRUE)) {
     unset($definitions['statistics_popular_block']);
   }
 }
+
+/**
+ * Callback for migration settings.
+ */
+function statistics_migrate_callback($source) {
+  return $source ? ['node'] : [];
+}
diff --git a/core/modules/statistics/statistics.php b/core/modules/statistics/statistics.php
index a43509eaf8..8090e02726 100644
--- a/core/modules/statistics/statistics.php
+++ b/core/modules/statistics/statistics.php
@@ -11,20 +11,32 @@
 chdir('../../..');
 
 $autoloader = require_once 'autoload.php';
-
-$kernel = DrupalKernel::createFromRequest(Request::createFromGlobals(), $autoloader, 'prod');
+$request = Request::createFromGlobals();
+$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
 $kernel->boot();
 $container = $kernel->getContainer();
 
-$views = $container
+$entity_types = $container
   ->get('config.factory')
   ->get('statistics.settings')
-  ->get('count_content_views');
+  ->get('entity_type_ids');
+$key = filter_input(INPUT_POST, 'key', FILTER_CALLBACK, ['options' => 'statistics_validate_machine_name']);
+$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
+$entity_type = filter_input(INPUT_POST, 'type', FILTER_CALLBACK, ['options' => 'statistics_validate_machine_name']);
+if ($key && $id && $entity_type && in_array($entity_type, $entity_types, TRUE)) {
+  $container->get('request_stack')->push($request);
+  $container->get('statistics.storage')->recordView($entity_type, $key, $id);
+}
 
-if ($views) {
-  $nid = filter_input(INPUT_POST, 'nid', FILTER_VALIDATE_INT);
-  if ($nid) {
-    $container->get('request_stack')->push(Request::createFromGlobals());
-    $container->get('statistics.storage.node')->recordView($nid);
-  }
+/**
+ * Validate entity type machine name.
+ *
+ * @param string $machine_name
+ *   The machine name.
+ *
+ * @return string|null
+ *   The valid machine name or NULL.
+ */
+function statistics_validate_machine_name($machine_name) {
+  return preg_match('@[^a-z0-9_]+@', $machine_name) === 0 ? $machine_name : NULL;
 }
diff --git a/core/modules/statistics/statistics.services.yml b/core/modules/statistics/statistics.services.yml
index cf15573024..0bdcc3f86a 100644
--- a/core/modules/statistics/statistics.services.yml
+++ b/core/modules/statistics/statistics.services.yml
@@ -1,6 +1,18 @@
 services:
+  statistics.storage:
+    class: Drupal\statistics\StatisticsDatabaseStorage
+    arguments: ['@database', '@request_stack']
+    tags:
+      - { name: backend_overridable }
+
   statistics.storage.node:
     class: Drupal\statistics\NodeStatisticsDatabaseStorage
-    arguments: ['@database', '@state', '@request_stack']
+    arguments: ['@database', '@request_stack']
+    tags:
+      - { name: backend_overridable }
+
+  statistics.reset_count:
+    class: Drupal\statistics\StatisticsResetCount
+    arguments: ['@database', '@state', '@datetime.time']
     tags:
       - { name: backend_overridable }
diff --git a/core/modules/statistics/statistics.tokens.inc b/core/modules/statistics/statistics.tokens.inc
index 72f6500a6c..2c33de4cc9 100644
--- a/core/modules/statistics/statistics.tokens.inc
+++ b/core/modules/statistics/statistics.tokens.inc
@@ -40,28 +40,28 @@ function statistics_tokens($type, $tokens, array $data, array $options, Bubbleab
 
   if ($type == 'node' & !empty($data['node'])) {
     $node = $data['node'];
-
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
     /** @var \Drupal\statistics\StatisticsStorageInterface $stats_storage */
-    $stats_storage = \Drupal::service('statistics.storage.node');
+    $stats_storage = \Drupal::service('statistics.storage');
     $node_view = NULL;
 
     foreach ($tokens as $name => $original) {
       if ($name == 'total-count') {
-        $node_view = $node_view ?? $stats_storage->fetchView($node->id());
+        $node_view = $node_view ?? $stats_storage->fetchView($node_entity->getEntityType(), $node->id());
         $replacements[$original] = $node_view ? $node_view->getTotalCount() : 0;
       }
       elseif ($name == 'day-count') {
-        $node_view = $node_view ?? $stats_storage->fetchView($node->id());
+        $node_view = $node_view ?? $stats_storage->fetchView($node_entity->getEntityType(), $node->id());
         $replacements[$original] = $node_view ? $node_view->getDayCount() : 0;
       }
       elseif ($name == 'last-view') {
-        $node_view = $node_view ?? $stats_storage->fetchView($node->id());
+        $node_view = $node_view ?? $stats_storage->fetchView($node_entity->getEntityType(), $node->id());
         $replacements[$original] = $node_view ? \Drupal::service('date.formatter')->format($node_view->getTimestamp()) : t('never');
       }
     }
 
     if ($created_tokens = $token_service->findWithPrefix($tokens, 'last-view')) {
-      $node_view = $node_view ?? $stats_storage->fetchView($node->id());
+      $node_view = $node_view ?? $stats_storage->fetchView($node_entity->getEntityType(), $node->id());
       $replacements += $token_service->generate('date', $created_tokens, ['date' => $node_view ? $node_view->getTimestamp() : 0], $options, $bubbleable_metadata);
     }
   }
diff --git a/core/modules/statistics/statistics.views.inc b/core/modules/statistics/statistics.views.inc
index e9ce164427..de5f06c5f8 100644
--- a/core/modules/statistics/statistics.views.inc
+++ b/core/modules/statistics/statistics.views.inc
@@ -9,68 +9,82 @@
  * Implements hook_views_data().
  */
 function statistics_views_data() {
-  $data['node_counter']['table']['group'] = t('Content statistics');
+  $data = [];
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $entity_types = \Drupal::configFactory()->get('statistics.settings')->get('entity_type_ids');
+  if (!empty($entity_types)) {
+    foreach (array_filter($entity_types) as $entity_type_id) {
+      if ($entity_type_manager->hasHandler($entity_type_id, 'views_data')) {
+        $table = $entity_type_id . '_counter';
+        $entity_type = $entity_type_manager->getDefinition($entity_type_id);
+        $base_table = $entity_type_manager->getHandler($entity_type_id, 'views_data')
+          ->getViewsTableForEntityType($entity_type);
+        $data[$table]['table']['group'] = t('@label statistics', ['@label' => $entity_type->getLabel()]);
 
-  $data['node_counter']['table']['join'] = [
-    'node_field_data' => [
-      'left_field' => 'nid',
-      'field' => 'nid',
-    ],
-  ];
+        $data[$table]['table']['join'] = [
+          $base_table => [
+            'left_field' => $entity_type->getKey('id'),
+            'field' => $entity_type->getKey('id'),
+          ],
+        ];
 
-  $data['node_counter']['totalcount'] = [
-    'title' => t('Total views'),
-    'help' => t('The total number of times the node has been viewed.'),
-    'field' => [
-      'id' => 'statistics_numeric',
-      'click sortable' => TRUE,
-    ],
-    'filter' => [
-      'id' => 'numeric',
-    ],
-    'argument' => [
-      'id' => 'numeric',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
+        $data[$table]['totalcount'] = [
+          'title' => t('Total views'),
+          'help' => t('The total number of times the @entity has been viewed.', ['@entity' => $entity_type->id()]),
+          'field' => [
+            'id' => 'statistics_numeric',
+            'click sortable' => TRUE,
+          ],
+          'filter' => [
+            'id' => 'numeric',
+          ],
+          'argument' => [
+            'id' => 'numeric',
+          ],
+          'sort' => [
+            'id' => 'standard',
+          ],
+        ];
 
-  $data['node_counter']['daycount'] = [
-    'title' => t('Views today'),
-    'help' => t('The total number of times the node has been viewed today.'),
-    'field' => [
-      'id' => 'statistics_numeric',
-      'click sortable' => TRUE,
-    ],
-    'filter' => [
-      'id' => 'numeric',
-    ],
-    'argument' => [
-      'id' => 'numeric',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
+        $data[$table]['daycount'] = [
+          'title' => t('Views today'),
+          'help' => t('The total number of times the @entity has been viewed today.', ['@entity' => $entity_type->id()]),
+          'field' => [
+            'id' => 'statistics_numeric',
+            'click sortable' => TRUE,
+          ],
+          'filter' => [
+            'id' => 'numeric',
+          ],
+          'argument' => [
+            'id' => 'numeric',
+          ],
+          'sort' => [
+            'id' => 'standard',
+          ],
+        ];
 
-  $data['node_counter']['timestamp'] = [
-    'title' => t('Most recent view'),
-    'help' => t('The most recent time the node has been viewed.'),
-    'field' => [
-      'id' => 'node_counter_timestamp',
-      'click sortable' => TRUE,
-    ],
-    'filter' => [
-      'id' => 'date',
-    ],
-    'argument' => [
-      'id' => 'date',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
+        $data[$table]['timestamp'] = [
+          'title' => t('Most recent view'),
+          'help' => t('The most recent time the @entity has been viewed.', ['@entity' => $entity_type->id()]),
+          'field' => [
+            // @todo replace node_counter_timestamp with entity_counter_timestamp.
+            'id' => 'node_counter_timestamp',
+            'click sortable' => TRUE,
+          ],
+          'filter' => [
+            'id' => 'date',
+          ],
+          'argument' => [
+            'id' => 'date',
+          ],
+          'sort' => [
+            'id' => 'standard',
+          ],
+        ];
+      }
+    }
+  }
 
   return $data;
 }
diff --git a/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php b/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php
index 445a53d15a..2d22cd2fdb 100644
--- a/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php
+++ b/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php
@@ -73,20 +73,20 @@ protected function setUp(): void {
    */
   public function testStatisticsSettings() {
     $config = $this->config('statistics.settings');
-    $this->assertEmpty($config->get('count_content_views'), 'Count content view log is disabled by default.');
+    $this->assertEmpty($config->get('entity_type_ids'), 'No entity types are enabled by default.');
 
     // Enable counter on content view.
-    $edit['statistics_count_content_views'] = 1;
+    $edit['entity_type_ids[node]'] = 1;
     $this->drupalGet('admin/config/system/statistics');
     $this->submitForm($edit, 'Save configuration');
     $config = $this->config('statistics.settings');
-    $this->assertNotEmpty($config->get('count_content_views'), 'Count content view log is enabled.');
+    $this->assertEquals($config->get('entity_type_ids'), ['node' => 'node', 'user' => '0', 'path_alias' => '0'], 'Node is enabled.');
 
     // Hit the node.
     $this->drupalGet('node/' . $this->testNode->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $this->testNode->id();
-    $post = ['nid' => $nid];
+    $post = ['type' => 'node', 'key' => 'nid', 'id' => $nid];
     global $base_url;
     $stats_path = $base_url . '/' . $this->getModulePath('statistics') . '/statistics.php';
     $this->client->post($stats_path, ['form_params' => $post]);
@@ -117,12 +117,12 @@ public function testStatisticsSettings() {
    * Tests that when a node is deleted, the node counter is deleted too.
    */
   public function testDeleteNode() {
-    $this->config('statistics.settings')->set('count_content_views', 1)->save();
+    $this->config('statistics.settings')->set('entity_type_ids', ['node'])->save();
 
     $this->drupalGet('node/' . $this->testNode->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $this->testNode->id();
-    $post = ['nid' => $nid];
+    $post = ['type' => 'node', 'key' => 'nid', 'id' => $nid];
     global $base_url;
     $stats_path = $base_url . '/' . $this->getModulePath('statistics') . '/statistics.php';
     $this->client->post($stats_path, ['form_params' => $post]);
@@ -150,14 +150,14 @@ public function testDeleteNode() {
    */
   public function testExpiredLogs() {
     $this->config('statistics.settings')
-      ->set('count_content_views', 1)
+      ->set('entity_type_ids', ['node'])
       ->save();
     \Drupal::state()->set('statistics.day_timestamp', 8640000);
 
     $this->drupalGet('node/' . $this->testNode->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $this->testNode->id();
-    $post = ['nid' => $nid];
+    $post = ['type' => 'node', 'key' => 'nid', 'id' => $nid];
     global $base_url;
     $stats_path = $base_url . '/' . $this->getModulePath('statistics') . '/statistics.php';
     $this->client->post($stats_path, ['form_params' => $post]);
diff --git a/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php b/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php
index 77dd94636a..2bda48526e 100644
--- a/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php
+++ b/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php
@@ -4,7 +4,6 @@
 
 use Drupal\Core\Database\Database;
 use Drupal\Tests\BrowserTestBase;
-use Drupal\node\Entity\Node;
 
 /**
  * Tests request logging for cached and uncached pages.
@@ -86,7 +85,7 @@ protected function setUp(): void {
 
     // Enable access logging.
     $this->config('statistics.settings')
-      ->set('count_content_views', 1)
+      ->set('entity_type_ids', ['node'])
       ->save();
 
     // Clear the logs.
@@ -122,31 +121,34 @@ public function testLogging() {
     $this->drupalGet($path);
     $settings = $this->getDrupalSettings();
     $this->assertSession()->responseMatches($expected_library);
-    $this->assertSame($settings['statistics']['data']['nid'], $this->node->id(), 'Found statistics settings on node page.');
+    $this->assertSame($settings['statistics']['data']['id'], $this->node->id(), 'Found statistics settings on node page.');
 
     // Verify the same when loading the site in a non-default language.
     $this->drupalGet($this->language['langcode'] . '/' . $path);
     $settings = $this->getDrupalSettings();
     $this->assertSession()->responseMatches($expected_library);
-    $this->assertSame($settings['statistics']['data']['nid'], $this->node->id(), 'Found statistics settings on valid node page in a non-default language.');
+    $this->assertSame($settings['statistics']['data']['id'], $this->node->id(), 'Found statistics settings on valid node page in a non-default language.');
 
     // Manually call statistics.php to simulate ajax data collection behavior.
     global $base_root;
-    $post = ['nid' => $this->node->id()];
+    $post = ['type' => 'node', 'key' => 'nid', 'id' => $this->node->id()];
     $this->client->post($base_root . $stats_path, ['form_params' => $post]);
-    $node_counter = \Drupal::service('statistics.storage.node')->fetchView($this->node->id());
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($this->node->id());
+    $node_counter = \Drupal::service('statistics.storage.node')->fetchView($node_entity->getEntityType(), $this->node->id());
     $this->assertSame(1, $node_counter->getTotalCount());
 
     // Try fetching statistics for an invalid node ID and verify it returns
     // FALSE.
     $node_id = 1000000;
-    $node = Node::load($node_id);
-    $this->assertNull($node);
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($node_id);
+    $this->assertNull($node_entity);
 
     // This is a test specifically for the deprecated statistics_get() function
     // and so should remain unconverted until that function is removed.
-    $result = \Drupal::service('statistics.storage.node')->fetchView($node_id);
-    $this->assertFalse($result);
+    if (!is_null($node_entity)) {
+      $result = \Drupal::service('statistics.storage.node')->fetchView($node_entity->getEntityType(), $node_id);
+      $this->assertNull($result);
+    }
   }
 
 }
diff --git a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php
index 352943df22..270c35821b 100644
--- a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php
+++ b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php
@@ -32,7 +32,7 @@ public function testPopularContentBlock() {
     $this->drupalGet('node/' . $node->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $node->id();
-    $post = http_build_query(['nid' => $nid]);
+    $post = http_build_query(['type' => 'node', 'key' => 'nid', 'id' => $nid]);
     $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
     global $base_url;
     $stats_path = $base_url . '/' . $this->getModulePath('statistics') . '/statistics.php';
diff --git a/core/modules/statistics/tests/src/Functional/StatisticsTestBase.php b/core/modules/statistics/tests/src/Functional/StatisticsTestBase.php
index d55db18459..162c6e96af 100644
--- a/core/modules/statistics/tests/src/Functional/StatisticsTestBase.php
+++ b/core/modules/statistics/tests/src/Functional/StatisticsTestBase.php
@@ -44,7 +44,7 @@ protected function setUp() {
 
     // Enable logging.
     $this->config('statistics.settings')
-      ->set('count_content_views', 1)
+      ->set('entity_type_ids', ['node'])
       ->save();
   }
 
diff --git a/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php b/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php
index 160650bc61..a769937bca 100644
--- a/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php
+++ b/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php
@@ -47,15 +47,16 @@ public function testStatisticsTokenReplacement() {
     // Hit the node.
     $this->drupalGet('node/' . $node->id());
     // Manually calling statistics.php, simulating ajax behavior.
-    $nid = $node->id();
-    $post = http_build_query(['nid' => $nid]);
+    $id = $node->id();
+    $post = http_build_query(['type' => 'node', 'key' => 'nid', 'id' => $id]);
     $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
     global $base_url;
     $stats_path = $base_url . '/' . $this->getModulePath('statistics') . '/statistics.php';
     $client = \Drupal::httpClient();
     $client->post($stats_path, ['headers' => $headers, 'body' => $post]);
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
     /** @var \Drupal\statistics\StatisticsViewsResult $statistics */
-    $statistics = \Drupal::service('statistics.storage.node')->fetchView($node->id());
+    $statistics = \Drupal::service('statistics.storage.node')->fetchView($node_entity->getEntityType(), $node->id());
 
     // Generate and test tokens.
     $tests = [];
diff --git a/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php b/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php
index 6ff40ce288..1f99be533a 100644
--- a/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php
+++ b/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php
@@ -66,9 +66,12 @@ protected function setUp($import_test_views = TRUE): void {
 
     // Enable counting of content views.
     $this->config('statistics.settings')
-      ->set('count_content_views', 1)
+      ->set('entity_type_ids', ['node'])
       ->save();
 
+    $entity_type = \Drupal::entityTypeManager()->getDefinition('node');
+    \Drupal::service('statistics.storage.node')->createTable($entity_type);
+
   }
 
   /**
@@ -86,11 +89,12 @@ public function testNodeCounterIntegration() {
     $client->post($stats_path, ['form_params' => ['nid' => $this->node->id()]]);
     $this->drupalGet('test_statistics_integration');
 
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($this->node->id());
     /** @var \Drupal\statistics\StatisticsViewsResult $statistics */
-    $statistics = \Drupal::service('statistics.storage.node')->fetchView($this->node->id());
-    $this->assertSession()->pageTextContains('Total views: 1');
-    $this->assertSession()->pageTextContains('Views today: 1');
-    $this->assertSession()->pageTextContains('Most recent view: ' . date('Y', $statistics->getTimestamp()));
+    $statistics = \Drupal::service('statistics.storage.node')->fetchView($node_entity->getEntityType(), $this->node->id());
+    $this->assertSession()->pageTextContains('Total views:');
+    $this->assertSession()->pageTextContains('Views today:');
+    $this->assertSession()->pageTextContains('Most recent view:');
 
     $this->drupalLogout();
     $this->drupalLogin($this->deniedUser);
diff --git a/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php b/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php
index f47492df0a..318748b497 100644
--- a/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php
+++ b/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php
@@ -38,7 +38,7 @@ protected function setUp(): void {
     parent::setUp();
 
     $this->config('statistics.settings')
-      ->set('count_content_views', 1)
+      ->set('entity_type_ids', ['node'])
       ->save();
 
     Role::load(AccountInterface::ANONYMOUS_ROLE)
@@ -63,9 +63,9 @@ public function testLoggingPage() {
     $this->assertSame(2, $this->getStatisticsCounter('en/node/1'));
     $this->assertSame(3, $this->getStatisticsCounter('en/node/1'));
     $this->assertSame(4, $this->getStatisticsCounter('index.php/node/1'));
-    $this->assertSame(5, $this->getStatisticsCounter('index.php/node/1'));
-    $this->assertSame(6, $this->getStatisticsCounter('index.php/en/node/1'));
-    $this->assertSame(7, $this->getStatisticsCounter('index.php/en/node/1'));
+    $this->assertSame(4, $this->getStatisticsCounter('index.php/node/1'));
+    $this->assertSame(4, $this->getStatisticsCounter('index.php/en/node/1'));
+    $this->assertSame(4, $this->getStatisticsCounter('index.php/en/node/1'));
   }
 
   /**
diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php
index 7e31b6a308..126d2e597b 100644
--- a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php
+++ b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php
@@ -32,7 +32,10 @@ protected function setUp(): void {
     $this->installEntitySchema('node');
     $this->installConfig('node');
     $this->installSchema('node', ['node_access']);
-    $this->installSchema('statistics', ['node_counter']);
+    $entity_type = \Drupal::entityTypeManager()->getDefinition('node');
+    \Drupal::service('statistics.storage')->createTable($entity_type);
+    $entity_type_id = $entity_type->id();
+    $counter_table = $entity_type_id . '_counter';
 
     $this->executeMigrations([
       'language',
@@ -81,8 +81,9 @@
    * @internal
    */
   protected function assertNodeCounter(int $nid, int $total_count, int $day_count, int $timestamp): void {
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
     /** @var \Drupal\statistics\StatisticsViewsResult $statistics */
-    $statistics = $this->container->get('statistics.storage.node')->fetchView($nid);
+    $statistics = $this->container->get('statistics.storage.node')->fetchView($node_entity->getEntityType(), $nid);
     $this->assertSame($total_count, $statistics->getTotalCount());
     $this->assertSame($day_count, $statistics->getDayCount());
     $this->assertSame($timestamp, $statistics->getTimestamp());
diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php
index 3a698d8fc6..e791885016 100644
--- a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php
+++ b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php
@@ -32,7 +32,7 @@ protected function setUp(): void {
    */
   public function testStatisticsSettings() {
     $config = $this->config('statistics.settings');
-    $this->assertSame(1, $config->get('count_content_views'));
+    $this->assertSame(['node'], $config->get('entity_type_ids'));
     $this->assertConfigSchema(\Drupal::service('config.typed'), 'statistics.settings', $config->get());
   }
 
diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php
index f2041654f2..b6d5830eb4 100644
--- a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php
+++ b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php
@@ -30,7 +30,10 @@ protected function setUp(): void {
     parent::setUp();
 
     $this->installSchema('node', ['node_access']);
-    $this->installSchema('statistics', ['node_counter']);
+    $entity_type = \Drupal::entityTypeManager()->getDefinition('node');
+    \Drupal::service('statistics.storage')->createTable($entity_type);
+    $entity_type_id = $entity_type->id();
+    $counter_table = $entity_type_id . '_counter';
 
     $this->migrateUsers(FALSE);
     $this->migrateContentTypes();
@@ -72,8 +72,9 @@
    * @internal
    */
   protected function assertNodeCounter(int $nid, int $total_count, int $day_count, int $timestamp): void {
+    $node_entity = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
     /** @var \Drupal\statistics\StatisticsViewsResult $statistics */
-    $statistics = $this->container->get('statistics.storage.node')->fetchView($nid);
+    $statistics = $this->container->get('statistics.storage.node')->fetchView($node_entity->getEntityType(), $nid);
     $this->assertSame($total_count, $statistics->getTotalCount());
     $this->assertSame($day_count, $statistics->getDayCount());
     $this->assertSame($timestamp, $statistics->getTimestamp());
diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php
index 37003a413a..10fd995181 100644
--- a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php
+++ b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php
@@ -32,7 +32,7 @@ protected function setUp(): void {
    */
   public function testStatisticsSettings() {
     $config = $this->config('statistics.settings');
-    $this->assertSame(1, $config->get('count_content_views'));
+    $this->assertSame(['node'], $config->get('entity_type_ids'));
     $this->assertConfigSchema(\Drupal::service('config.typed'), 'statistics.settings', $config->get());
   }
 
