diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 1b3e34f00a..56d4e082c6 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -9,6 +9,8 @@
  */
 
 use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Url;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Crypt;
@@ -725,6 +727,10 @@ function template_preprocess_links(&$variables) {
         '#url' => $link['url'],
         '#ajax' => $link['ajax'],
       ];
+      if (isset($link['access']) && $link['access'] instanceof AccessResultInterface) {
+        $link_element['#access'] = !$link['access']->isAllowed();
+        CacheableMetadata::createFromObject($link['access'])->applyTo($link_element);
+      }
 
       // Handle links and ensure that the active class is added on the LIs, but
       // only if the 'set_active_class' option is not empty. Links templates
diff --git a/core/lib/Drupal/Core/Entity/EntityListBuilder.php b/core/lib/Drupal/Core/Entity/EntityListBuilder.php
index 7bbb3ae342..a77020a322 100644
--- a/core/lib/Drupal/Core/Entity/EntityListBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityListBuilder.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Access\AccessResultAllowed;
 use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\Core\Routing\RedirectDestinationTrait;
 use Drupal\Core\Url;
@@ -114,6 +115,13 @@ public function getOperations(EntityInterface $entity) {
     $this->moduleHandler->alter('entity_operation', $operations, $entity);
     uasort($operations, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
 
+    foreach ($operations as &$operation) {
+      if (!isset($operation['access'])) {
+        $operation['access'] = new AccessResultAllowed();
+        $operation['access']->cachePerUser();
+      }
+    }
+
     return $operations;
   }
 
@@ -129,18 +137,20 @@ public function getOperations(EntityInterface $entity) {
    */
   protected function getDefaultOperations(EntityInterface $entity) {
     $operations = [];
-    if ($entity->access('update') && $entity->hasLinkTemplate('edit-form')) {
+    if ($entity->hasLinkTemplate('edit-form')) {
       $operations['edit'] = [
         'title' => $this->t('Edit'),
         'weight' => 10,
         'url' => $this->ensureDestination($entity->toUrl('edit-form')),
+        'access' => $entity->access('update', NULL, TRUE),
       ];
     }
-    if ($entity->access('delete') && $entity->hasLinkTemplate('delete-form')) {
+    if ($entity->hasLinkTemplate('delete-form')) {
       $operations['delete'] = [
         'title' => $this->t('Delete'),
         'weight' => 100,
         'url' => $this->ensureDestination($entity->toUrl('delete-form')),
+        'access' => $entity->access('delete', NULL, TRUE),
       ];
     }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php
index eb16874bde..291a1ac4cb 100644
--- a/core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php
@@ -40,6 +40,7 @@ public function load();
    *   - title: The localized title of the operation.
    *   - url: An instance of \Drupal\Core\Url for the operation URL.
    *   - weight: The weight of this operation.
+   *   - access: Access result object with cache information.
    */
   public function getOperations(EntityInterface $entity);
 
diff --git a/core/modules/config/tests/src/Functional/ConfigEntityListTest.php b/core/modules/config/tests/src/Functional/ConfigEntityListTest.php
index d7aaf05509..30883cc4de 100644
--- a/core/modules/config/tests/src/Functional/ConfigEntityListTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigEntityListTest.php
@@ -57,11 +57,14 @@ public function testList() {
     $this->assertInstanceOf(ConfigTest::class, $entity);
 
     // Test getOperations() method.
+    $update_access = $entity->access('update', NULL, TRUE);
+    $delete_access = $entity->access('delete', NULL, TRUE);
     $expected_operations = [
       'edit' => [
         'title' => t('Edit'),
         'weight' => 10,
         'url' => $entity->toUrl()->setOption('query', $this->getRedirectDestination()->getAsArray()),
+        'access' => $update_access,
       ],
       'disable' => [
         'title' => t('Disable'),
@@ -72,6 +75,7 @@ public function testList() {
         'title' => t('Delete'),
         'weight' => 100,
         'url' => $entity->toUrl('delete-form')->setOption('query', $this->getRedirectDestination()->getAsArray()),
+        'access' => $delete_access,
       ],
     ];
 
@@ -137,11 +141,13 @@ public function testList() {
         'title' => t('Edit'),
         'weight' => 10,
         'url' => $entity->toUrl()->setOption('query', $this->getRedirectDestination()->getAsArray()),
+        'access' => $update_access,
       ],
       'delete' => [
         'title' => t('Delete'),
         'weight' => 100,
         'url' => $entity->toUrl('delete-form')->setOption('query', $this->getRedirectDestination()->getAsArray()),
+        'access' => $delete_access,
       ],
     ];
 
diff --git a/core/modules/views/src/Plugin/views/field/EntityOperations.php b/core/modules/views/src/Plugin/views/field/EntityOperations.php
index e08cd8cade..a6c7d5ad05 100644
--- a/core/modules/views/src/Plugin/views/field/EntityOperations.php
+++ b/core/modules/views/src/Plugin/views/field/EntityOperations.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\views\Plugin\views\field;
 
+use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityRepositoryInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
@@ -129,8 +131,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function render(ResultRow $values) {
     $entity = $this->getEntityTranslation($this->getEntity($values), $values);
     $operations = $this->entityTypeManager->getListBuilder($entity->getEntityTypeId())->getOperations($entity);
-    if ($this->options['destination']) {
-      foreach ($operations as &$operation) {
+    $cacheability = new CacheableMetadata();
+    foreach ($operations as $k => &$operation) {
+      if (isset($operation['access']) && $operation['access'] instanceof AccessResultInterface) {
+        $cacheability->addCacheableDependency($operation['access']);
+        if (!$operation['access']->isAllowed()) {
+          unset($operations[$k]);
+          continue;
+        }
+      }
+      if ($this->options['destination']) {
         if (!isset($operation['query'])) {
           $operation['query'] = [];
         }
@@ -141,6 +151,7 @@ public function render(ResultRow $values) {
       '#type' => 'operations',
       '#links' => $operations,
     ];
+    $cacheability->applyTo($build);
 
     return $build;
   }
diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
index b5daf4ab1d..a7b883123d 100644
--- a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\views\field;
 
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityRepositoryInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Tests\UnitTestCase;
@@ -103,9 +105,11 @@ public function testRenderWithDestination() {
       ->method('getEntityTypeId')
       ->will($this->returnValue($entity_type_id));
 
+    $cache = (new CacheableMetadata())->addCacheTags([$this->randomMachineName()]);
     $operations = [
       'foo' => [
         'title' => $this->randomMachineName(),
+        'access' => AccessResult::allowed()->addCacheableDependency($cache),
       ],
     ];
     $list_builder = $this->createMock('\Drupal\Core\Entity\EntityListBuilderInterface');
@@ -129,6 +133,7 @@ public function testRenderWithDestination() {
       '#links' => $operations,
     ];
     $expected_build['#links']['foo']['query'] = ['destination' => 'foobar'];
+    $cache->applyTo($expected_build);
     $build = $this->plugin->render($result);
     $this->assertSame($expected_build, $build);
   }
@@ -145,9 +150,11 @@ public function testRenderWithoutDestination() {
       ->method('getEntityTypeId')
       ->will($this->returnValue($entity_type_id));
 
+    $cache = (new CacheableMetadata())->addCacheTags([$this->randomMachineName()]);
     $operations = [
       'foo' => [
         'title' => $this->randomMachineName(),
+        'access' => AccessResult::allowed()->addCacheableDependency($cache),
       ],
     ];
     $list_builder = $this->createMock('\Drupal\Core\Entity\EntityListBuilderInterface');
@@ -170,6 +177,7 @@ public function testRenderWithoutDestination() {
       '#type' => 'operations',
       '#links' => $operations,
     ];
+    $cache->applyTo($expected_build);
     $build = $this->plugin->render($result);
     $this->assertSame($expected_build, $build);
   }
