diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Query.php b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
index 109efb8382af18b36e7f3ca3ec430cfbd77e41fc..1a589a5af9fffbe1326af1a7681388ef024756ab 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Query.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
@@ -78,6 +78,9 @@ public function condition($property, $value = NULL, $operator = NULL, $langcode
    * {@inheritdoc}
    */
   public function execute() {
+    // Invoke entity query alter hooks.
+    $this->alter();
+
     // Load the relevant config records.
     $configs = $this->loadRecords();
 
diff --git a/core/lib/Drupal/Core/Entity/Query/QueryBase.php b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
index 585a8fdf9946f461cb9eeee0ab0351090d86d569..d4dd99e5bd29ef60af3a27166d4a4d05854ef79b 100644
--- a/core/lib/Drupal/Core/Entity/Query/QueryBase.php
+++ b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
@@ -510,4 +510,29 @@ public static function getClass(array $namespaces, $short_class_name) {
     }
   }
 
+  /**
+   * Invoke hooks to allow modules to alter the entity query.
+   *
+   * Modules may alter all queries or only those having a particular tag.
+   * Alteration happens before the query is prepared for execution, so that
+   * the alterations then get prepared in the same way.
+   *
+   * @return $this
+   *   Returns the called object.
+   */
+  protected function alter(): QueryInterface {
+    $hooks = ['entity_query', 'entity_query_' . $this->getEntityTypeId()];
+    if ($this->alterTags) {
+      foreach ($this->alterTags as $tag => $value) {
+        // Tags and entity type ids may well contain single underscores, and
+        // 'tag' is a possible entity type id. Therefore use double underscores
+        // to avoid collisions.
+        $hooks[] = 'entity_query_tag__' . $tag;
+        $hooks[] = 'entity_query_tag__' . $this->getEntityTypeId() . '__' . $tag;
+      }
+    }
+    \Drupal::moduleHandler()->alter($hooks, $this);
+    return $this;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
index 61cdd98874ed3ab000975ce88977b73c7a4d0899..65de824756c93ff5371d06e9376b758727104ee1 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
@@ -77,6 +77,7 @@ public function __construct(EntityTypeInterface $entity_type, $conjunction, Conn
    */
   public function execute() {
     return $this
+      ->alter()
       ->prepare()
       ->compile()
       ->addSort()
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index f3c6e1ae9c4fd1ee4989db66020fed8253fd0861..816817190fc65bcd6e466cdaf44c6567c511fb87 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -2289,6 +2289,65 @@ function hook_entity_extra_field_info_alter(&$info) {
   }
 }
 
+/**
+ * Alter an entity query.
+ *
+ * @param \Drupal\Core\Entity\Query\QueryInterface $query
+ *   The entity query.
+ *
+ * @see hook_entity_query_ENTITY_TYPE_alter()
+ * @see hook_entity_query_tag__TAG_alter()
+ * @see \Drupal\Core\Entity\Query\QueryInterface
+ */
+function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query): void {
+  if ($query->hasTag('entity_reference')) {
+    $entityType = \Drupal::entityTypeManager()->getDefinition($query->getEntityTypeId());
+    $query->sort($entityType->getKey('id'), 'desc');
+  }
+}
+
+/**
+ * Alter an entity query for a specific entity type.
+ *
+ * @param \Drupal\Core\Entity\Query\QueryInterface $query
+ *   The entity query.
+ *
+ * @see hook_entity_query_alter()
+ * @see \Drupal\Core\Entity\Query\QueryInterface
+ */
+function hook_entity_query_ENTITY_TYPE_alter(\Drupal\Core\Entity\Query\QueryInterface $query): void {
+  $query->condition('id', '1', '<>');
+}
+
+/**
+ * Alter an entity query that has a specific tag.
+ *
+ * @param \Drupal\Core\Entity\Query\QueryInterface $query
+ *   The entity query.
+ *
+ * @see hook_entity_query_alter()
+ * @see hook_entity_query_tag__ENTITY_TYPE__TAG_alter()
+ * @see \Drupal\Core\Entity\Query\QueryInterface
+ */
+function hook_entity_query_tag__TAG_alter(\Drupal\Core\Entity\Query\QueryInterface $query): void {
+  $entityType = \Drupal::entityTypeManager()->getDefinition($query->getEntityTypeId());
+  $query->sort($entityType->getKey('id'), 'desc');
+}
+
+/**
+ * Alter an entity query for a specific entity type that has a specific tag.
+ *
+ * @param \Drupal\Core\Entity\Query\QueryInterface $query
+ *   The entity query.
+ *
+ * @see hook_entity_query_ENTITY_TYPE_alter()
+ * @see hook_entity_query_tag__TAG_alter()
+ * @see \Drupal\Core\Entity\Query\QueryInterface
+ */
+function hook_entity_query_tag__ENTITY_TYPE__TAG_alter(\Drupal\Core\Entity\Query\QueryInterface $query): void {
+  $query->condition('id', '1', '<>');
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index ac06237e3577ec9ce57534db58b458f8f24c8c04..af5d99cbd28fc34f755551351fc76fad102e9961 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -7,6 +7,8 @@
 
 require_once dirname(__FILE__) . '/config_test.hooks.inc';
 
+use Drupal\Core\Entity\Query\QueryInterface;
+
 /**
  * Implements hook_cache_flush().
  */
@@ -41,3 +43,15 @@ function config_test_entity_type_alter(array &$entity_types) {
     $entity_types['config_test']->set('lookup_keys', ['uuid', 'style']);
   }
 }
+
+/**
+ * Implements hook_entity_query_tag__ENTITY_TYPE__TAG_alter().
+ *
+ * Entity type is 'config_query_test' and tag is
+ * 'config_entity_query_alter_hook_test'.
+ *
+ * @see Drupal\KernelTests\Core\Entity\ConfigEntityQueryTest::testAlterHook
+ */
+function config_test_entity_query_tag__config_query_test__config_entity_query_alter_hook_test_alter(QueryInterface $query): void {
+  $query->condition('id', '7', '<>');
+}
diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module
index 34d590bbd2ca2ffb4c9a7ec1016621bc3a645ec2..cbc870be54e042068816b7e5f6a57a7377278f92 100644
--- a/core/modules/field/tests/modules/field_test/field_test.module
+++ b/core/modules/field/tests/modules/field_test/field_test.module
@@ -13,6 +13,7 @@
  */
 
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -231,3 +232,46 @@ function field_test_entity_reference_selection_alter(array &$definitions): void
     unset($definitions['broken']);
   }
 }
+
+/**
+ * Implements hook_entity_query_alter().
+ *
+ * @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
+ */
+function field_test_entity_query_alter(QueryInterface $query): void {
+  if ($query->hasTag('entity_query_alter_hook_test')) {
+    $query->condition('id', '5', '<>');
+  }
+}
+
+/**
+ * Implements hook_entity_query_ENTITY_TYPE_alter() for 'entity_test_mulrev'.
+ *
+ * @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
+ */
+function field_test_entity_query_entity_test_mulrev_alter(QueryInterface $query): void {
+  if ($query->hasTag('entity_query_entity_test_mulrev_alter_hook_test')) {
+    $query->condition('id', '7', '<>');
+  }
+}
+
+/**
+ * Implements hook_entity_query_tag__TAG_alter() for 'entity_query_alter_tag_test'.
+ *
+ * @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
+ */
+function field_test_entity_query_tag__entity_query_alter_tag_test_alter(QueryInterface $query): void {
+  $query->condition('id', '13', '<>');
+}
+
+/**
+ * Implements hook_entity_query_tag__ENTITY_TYPE__TAG_alter().
+ *
+ * Entity type is 'entity_test_mulrev' and tag is
+ * 'entity_query_entity_test_mulrev_alter_tag_test'.
+ *
+ * @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
+ */
+function field_test_entity_query_tag__entity_test_mulrev__entity_query_entity_test_mulrev_alter_tag_test_alter(QueryInterface $query): void {
+  $query->condition('id', '15', '<>');
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityQueryTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityQueryTest.php
index fe051636930af34f48f2b3fd29361532ae9a9521..cc01678df88f238b64c7b8786a5f6a08893701b7 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityQueryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityQueryTest.php
@@ -759,6 +759,24 @@ public function testLookupKeys() {
     $this->assertNull($key_value->get('style:test'));
   }
 
+  /**
+   * Test the entity query alter hooks are invoked.
+   *
+   * @see config_test_entity_query_tag__config_query_test__config_entity_query_alter_hook_test_alter()
+   */
+  public function testAlterHook(): void {
+    // Run a test without any condition.
+    $this->queryResults = $this->entityStorage->getQuery()
+      ->execute();
+    $this->assertResults(['1', '2', '3', '4', '5', '6', '7']);
+
+    // config_test alter hook removes the entity with id '7'.
+    $this->queryResults = $this->entityStorage->getQuery()
+      ->addTag('config_entity_query_alter_hook_test')
+      ->execute();
+    $this->assertResults(['1', '2', '3', '4', '5', '6']);
+  }
+
   /**
    * Asserts the results as expected regardless of order.
    *
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php
index ee944a5b50d7f3d4a17fa27d7388d903ca58959c..19b9245591460db6d85884d59cbbe764ced25444 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php
@@ -1294,6 +1294,60 @@ public function testWithTwoEntityReferenceFieldsToSameEntityType() {
     $this->assertEquals($entity->id(), reset($result));
   }
 
+  /**
+   * Test the entity query alter hooks are invoked.
+   *
+   * Hook functions in field_test.module add additional conditions to the query
+   * removing entities with specific ids.
+   */
+  public function testAlterHook(): void {
+    $basicQuery = $this->storage
+      ->getQuery()
+      ->accessCheck(FALSE)
+      ->exists($this->greetings, 'tr')
+      ->condition($this->figures . ".color", 'red')
+      ->sort('id');
+
+    // Verify assumptions about the unaltered result.
+    $query = clone $basicQuery;
+    $this->queryResults = $query->execute();
+    $this->assertResult(5, 7, 13, 15);
+
+    // field_test_entity_query_alter() removes the entity with id '5'.
+    $query = clone $basicQuery;
+    $this->queryResults = $query
+      // Add a tag that no hook function matches.
+      ->addTag('entity_query_alter_hook_test')
+      ->execute();
+    $this->assertResult(7, 13, 15);
+
+    // field_test_entity_query_entity_test_mulrev_alter() removes the
+    // entity with id '7'.
+    $query = clone $basicQuery;
+    $this->queryResults = $query
+      // Add a tag that no hook function matches.
+      ->addTag('entity_query_entity_test_mulrev_alter_hook_test')
+      ->execute();
+    $this->assertResult(5, 13, 15);
+
+    // field_test_entity_query_tag__entity_query_alter_tag_test_alter() removes
+    // the entity with id '13'.
+    $query = clone $basicQuery;
+    $this->queryResults = $query
+      ->addTag('entity_query_alter_tag_test')
+      ->execute();
+    $this->assertResult(5, 7, 15);
+
+    // field_test_entity_query_tag__entity_test_mulrev__entity_query_
+    // entity_test_mulrev_alter_tag_test_alter()
+    // removes the entity with id '15'.
+    $query = clone $basicQuery;
+    $this->queryResults = $query
+      ->addTag('entity_query_entity_test_mulrev_alter_tag_test')
+      ->execute();
+    $this->assertResult(5, 7, 13);
+  }
+
   /**
    * Tests entity queries with condition on the revision metadata keys.
    */
diff --git a/core/tests/Drupal/Tests/Core/Entity/Query/Sql/QueryTest.php b/core/tests/Drupal/Tests/Core/Entity/Query/Sql/QueryTest.php
index 5da95031017f1990ec680eeb5a9d2b1fca68fd55..0da918c29f79800ebe53d77d6cdbd9294c986c66 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Query/Sql/QueryTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Query/Sql/QueryTest.php
@@ -5,9 +5,11 @@
 namespace Drupal\Tests\Core\Entity\Query\Sql;
 
 use Drupal\Core\Entity\EntityType;
+use Drupal\Core\Extension\ModuleHandler;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Entity\Query\QueryException;
 use Drupal\Core\Entity\Query\Sql\Query;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * @coversDefaultClass \Drupal\Core\Entity\Query\Sql\Query
@@ -33,6 +35,13 @@ protected function setUp(): void {
     $namespaces = ['Drupal\Core\Entity\Query\Sql'];
 
     $this->query = new Query($entity_type, $conjunction, $connection, $namespaces);
+
+    $container = $this->createMock(ContainerInterface::class);
+    $container->expects($this->any())
+      ->method('get')
+      ->with('module_handler')
+      ->will($this->returnValue($this->createMock(ModuleHandler::class)));
+    \Drupal::setContainer($container);
   }
 
   /**