diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
index 7f10d6f51f..bae8174211 100644
--- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
+++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
@@ -2,9 +2,13 @@
 
 namespace Drupal\layout_builder\Entity;
 
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Plugin\Context\EntityContext;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\field\Entity\FieldConfig;
@@ -240,7 +244,17 @@ public function buildMultiple(array $entities) {
 
     /** @var \Drupal\Core\Entity\EntityInterface $entity */
     foreach ($entities as $id => $entity) {
-      $sections = $this->getRuntimeSections($entity);
+      $build_list[$id]['_layout_builder'] = [];
+
+      $cacheability = new CacheableMetadata();
+      $contexts = $this->getContextsForEntity($entity);
+      $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
+      // The render array is built based on decisions made by @SectionStorage
+      // plugins and therefore it needs to depend on the accumulated
+      // cacheability of those decisions.
+      $cacheability->applyTo($build_list[$id]['_layout_builder']);
+
+      $sections = $storage ? $storage->getSections() : [];
       if ($sections) {
         foreach ($build_list[$id] as $name => $build_part) {
           $field_definition = $this->getFieldDefinition($name);
@@ -249,13 +263,12 @@ public function buildMultiple(array $entities) {
           }
         }
 
-        // Bypass ::getContexts() in order to use the runtime entity, not a
-        // sample entity.
-        $contexts = $this->contextRepository()->getAvailableContexts();
+        // @todo Remove in https://www.drupal.org/project/drupal/issues/3018782.
         $label = new TranslatableMarkup('@entity being viewed', [
           '@entity' => $entity->getEntityType()->getSingularLabel(),
         ]);
         $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
+
         foreach ($sections as $delta => $section) {
           $build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts);
         }
@@ -265,21 +278,43 @@ public function buildMultiple(array $entities) {
     return $build_list;
   }
 
+  /**
+   * Gets the available contexts for a given entity.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   An array of context objects for a given entity.
+   */
+  protected function getContextsForEntity(FieldableEntityInterface $entity) {
+    return [
+      'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()),
+      'entity' => EntityContext::fromEntity($entity),
+      'display' => EntityContext::fromEntity($this),
+    ] + $this->contextRepository()->getAvailableContexts();
+  }
+
   /**
    * Gets the runtime sections for a given entity.
    *
    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
    *   The entity.
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface|null $cacheability
+   *   (optional) Refinable cacheability object, which will be populated based
+   *   on the cacheability of each section storage candidate.
    *
    * @return \Drupal\layout_builder\Section[]
    *   The sections.
+   *
+   * @todo Deprecate this method in https://www.drupal.org/node/2986403.
    */
-  protected function getRuntimeSections(FieldableEntityInterface $entity) {
-    if ($this->isOverridable() && !$entity->get(OverridesSectionStorage::FIELD_NAME)->isEmpty()) {
-      return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections();
+  protected function getRuntimeSections(FieldableEntityInterface $entity, RefinableCacheableDependencyInterface &$cacheability = NULL) {
+    if (!$cacheability) {
+      $cacheability = new CacheableMetadata();
     }
-
-    return $this->getSections();
+    $storage = $this->sectionStorageManager()->findByContext($this->getContextsForEntity($entity), $cacheability);
+    return $storage ? $storage->getSections() : [];
   }
 
   /**
@@ -399,4 +434,14 @@ protected function getDefaultSection() {
     return $this->getSection(0);
   }
 
+  /**
+   * Gets the section storage manager.
+   *
+   * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
+   *   The section storage manager.
+   */
+  private function sectionStorageManager() {
+    return \Drupal::service('plugin.manager.layout_builder.section_storage');
+  }
+
 }
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
index f51a4653c2..b18b17767d 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -21,8 +22,15 @@
 /**
  * Defines the 'defaults' section storage type.
  *
+ * DefaultsSectionStorage uses a positive weight because:
+ * - It must be picked after
+ *   \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage.
+ * - The default weight is 0, so other custom implementations will also take
+ *   precedence unless otherwise specified.
+ *
  * @SectionStorage(
  *   id = "defaults",
+ *   weight = 20,
  *   context_definitions = {
  *     "display" = @ContextDefinition("entity:entity_view_display"),
  *   },
@@ -444,4 +452,12 @@ public function access($operation, AccountInterface $account = NULL, $return_as_
     return $return_as_object ? $result : $result->isAllowed();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    // Defaults are always applicable.
+    return TRUE;
+  }
+
 }
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
index af2aca668d..138eb69f83 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder\Plugin\SectionStorage;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -19,8 +20,15 @@
 /**
  * Defines the 'overrides' section storage type.
  *
+ * OverridesSectionStorage uses a negative weight because:
+ * - It must be picked before
+ *   \Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage.
+ * - The default weight is 0, so custom implementations will not take
+ *   precedence unless otherwise specified.
+ *
  * @SectionStorage(
  *   id = "overrides",
+ *   weight = -20,
  *   context_definitions = {
  *     "entity" = @ContextDefinition("entity"),
  *     "view_mode" = @ContextDefinition("string", required = FALSE),
@@ -335,4 +343,14 @@ public function access($operation, AccountInterface $account = NULL, $return_as_
     return $return_as_object ? $result : $result->isAllowed();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    $default_section_storage = $this->getDefaultSectionStorage();
+    $cacheability->addCacheableDependency($default_section_storage)->addCacheableDependency($this);
+    // Check that overrides are enabled and have at least one section.
+    return $default_section_storage->isOverridable() && count($this);
+  }
+
 }
diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php
index 0bf52bf43d..fd14bac830 100644
--- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php
+++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\Context\ContextHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
@@ -92,12 +93,16 @@ public function load($type, array $contexts = []) {
   /**
    * {@inheritdoc}
    */
-  public function findByContext($operation, array $contexts) {
+  public function findByContext(array $contexts, RefinableCacheableDependencyInterface $cacheability) {
     $storage_types = array_keys($this->contextHandler->filterPluginDefinitionsByContexts($contexts, $this->getDefinitions()));
 
+    // Add the manager as a cacheable dependency in order to vary by changes to
+    // the plugin definitions.
+    $cacheability->addCacheableDependency($this);
+
     foreach ($storage_types as $type) {
       $plugin = $this->load($type, $contexts);
-      if ($plugin && $plugin->access($operation)) {
+      if ($plugin && $plugin->isApplicable($cacheability)) {
         return $plugin;
       }
     }
diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php
index 4e09e2721a..240ddd951c 100644
--- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php
+++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder\SectionStorage;
 
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 
 /**
  * Provides the interface for a plugin manager of section storage types.
@@ -30,15 +31,24 @@ public function load($type, array $contexts = []);
   /**
    * Finds the section storage to load based on available contexts.
    *
-   * @param string $operation
-   *   The access operation. See \Drupal\Core\Access\AccessibleInterface.
+   * After calling this method the $cacheability parameter will reflect the
+   * cacheability information used to determine the correct section storage.
+   * This must be applied to any output that uses the result of this method.
+   *
    * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
    *   The contexts which should be used to determine which storage to return.
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
+   *   Refinable cacheability object, which will be populated based on the
+   *   cacheability of each section storage candidate. This is typically created
+   *   directly before this method call and must be applied to a render array
+   *   after this method call.
    *
    * @return \Drupal\layout_builder\SectionStorageInterface|null
    *   The section storage if one matched all contexts, or NULL otherwise.
+   *
+   * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface
    */
-  public function findByContext($operation, array $contexts);
+  public function findByContext(array $contexts, RefinableCacheableDependencyInterface $cacheability);
 
   /**
    * Loads a section storage with no associated section list.
diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php
index 6dfd4793a3..7e172dcade 100644
--- a/core/modules/layout_builder/src/SectionStorageInterface.php
+++ b/core/modules/layout_builder/src/SectionStorageInterface.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Access\AccessibleInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Symfony\Component\Routing\RouteCollection;
 
@@ -164,4 +165,23 @@ public function label();
    */
   public function save();
 
+  /**
+   * Determines if this section storage is applicable for the current contexts.
+   *
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
+   *   Refinable cacheability object, typically provided by the section storage
+   *   manager. When implementing this method, populate $cacheability with any
+   *   information that affects whether this storage is applicable.
+   *
+   * @return bool
+   *   TRUE if this section storage is applicable, FALSE otherwise.
+   *
+   * @internal
+   *   This method is intended to be called by
+   *   \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext().
+   *
+   * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability);
+
 }
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml
new file mode 100644
index 0000000000..f9ed711707
--- /dev/null
+++ b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Layout Builder overrides test'
+type: module
+description: 'Support module for testing overriding layout building.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php
index c92bb2d1ed..7e15336519 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder_test\Plugin\SectionStorage;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\Context\Context;
@@ -213,4 +214,11 @@ public function extractIdFromRoute($value, $definition, $name, array $defaults)
     return $value ?: $defaults['id'];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    return TRUE;
+  }
+
 }
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestStateBasedSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestStateBasedSectionStorage.php
new file mode 100644
index 0000000000..d51cdc72c0
--- /dev/null
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestStateBasedSectionStorage.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\layout_builder_test\Plugin\SectionStorage;
+
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\layout_builder\Plugin\SectionStorage\SectionStorageBase;
+use Drupal\layout_builder\Section;
+use Drupal\layout_builder\SectionComponent;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides a test section storage that is controlled by state.
+ *
+ * @SectionStorage(
+ *   id = "layout_builder_test_state",
+ * )
+ */
+class TestStateBasedSectionStorage extends SectionStorageBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSections() {
+    // Return a custom section.
+    $section = new Section('layout_onecol');
+    $section->appendComponent(new SectionComponent('fake-uuid', 'content', [
+      'id' => 'system_powered_by_block',
+      'label' => 'Test block title',
+      'label_display' => 'visible',
+    ]));
+    return [$section];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    $cacheability->mergeCacheMaxAge(0);
+    return \Drupal::state()->get('layout_builder_test_state', FALSE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSectionList() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStorageId() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSectionListFromId($id) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRoutes(RouteCollection $collection) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRedirectUrl() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLayoutBuilderUrl($rel = 'view') {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractIdFromRoute($value, $definition, $name, array $defaults) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deriveContextsFromRoute($value, $definition, $name, array $defaults) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {}
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php
new file mode 100644
index 0000000000..3d0d00d131
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests the UI aspects of section storage.
+ *
+ * @group layout_builder
+ */
+class LayoutBuilderSectionStorageTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'layout_builder',
+    'node',
+    'layout_builder_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // @todo The Layout Builder UI relies on local tasks; fix in
+    //   https://www.drupal.org/project/drupal/issues/2917777.
+    $this->drupalPlaceBlock('local_tasks_block');
+
+    // Create two nodes.
+    $this->createContentType(['type' => 'bundle_with_section_field']);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The first node title',
+      'body' => [
+        [
+          'value' => 'The first node body',
+        ],
+      ],
+    ]);
+  }
+
+  /**
+   * Tests that section loading is delegated to plugins during rendering.
+   *
+   * @see \Drupal\layout_builder_test\Plugin\SectionStorage\TestStateBasedSectionStorage
+   */
+  public function testRenderByContextAwarePluginDelegate() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    // No blocks exist on the node by default.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextNotContains('Defaults block title');
+    $assert_session->pageTextNotContains('Test block title');
+
+    // Enable Layout Builder.
+    $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save');
+
+    // Add a block to the defaults.
+    $page->clickLink('Manage layout');
+    $page->clickLink('Add Block');
+    $page->clickLink('Powered by Drupal');
+    $page->fillField('settings[label]', 'Defaults block title');
+    $page->checkField('settings[label_display]');
+    $page->pressButton('Add Block');
+    $page->clickLink('Save Layout');
+
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('Defaults block title');
+    $assert_session->pageTextNotContains('Test block title');
+
+    // Enable the test section storage.
+    $this->container->get('state')->set('layout_builder_test_state', TRUE);
+    $this->drupalGet('node/1');
+    $assert_session->pageTextNotContains('Defaults block title');
+    $assert_session->pageTextContains('Test block title');
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php
index 155799c9e0..b3b99d1fcd 100644
--- a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php
+++ b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php
@@ -6,6 +6,8 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\Context\Context;
@@ -203,10 +205,11 @@ public function testFindDefinitions() {
    *
    * @dataProvider providerTestFindByContext
    *
-   * @param bool $plugin_access
-   *   The result for the plugin's access method to return.
+   * @param bool $plugin_is_applicable
+   *   The result for the plugin's isApplicable() method to return.
    */
-  public function testFindByContext($plugin_access) {
+  public function testFindByContext($plugin_is_applicable) {
+    $cacheability = new CacheableMetadata();
     $contexts = [
       'foo' => new Context(new ContextDefinition('foo')),
     ];
@@ -218,10 +221,10 @@ public function testFindByContext($plugin_access) {
     $this->discovery->getDefinitions()->willReturn($definitions);
 
     $provider_access = $this->prophesize(SectionStorageInterface::class);
-    $provider_access->access('test_operation')->willReturn($plugin_access);
+    $provider_access->isApplicable($cacheability)->willReturn($plugin_is_applicable);
 
     $no_access = $this->prophesize(SectionStorageInterface::class);
-    $no_access->access('test_operation')->willReturn(FALSE);
+    $no_access->isApplicable($cacheability)->willReturn(FALSE);
 
     $missing_contexts = $this->prophesize(SectionStorageInterface::class);
 
@@ -235,8 +238,8 @@ public function testFindByContext($plugin_access) {
     $this->factory->createInstance('missing_contexts', [])->willReturn($missing_contexts->reveal());
     $this->factory->createInstance('provider_access', [])->willReturn($provider_access->reveal());
 
-    $result = $this->manager->findByContext('test_operation', $contexts);
-    if ($plugin_access) {
+    $result = $this->manager->findByContext($contexts, $cacheability);
+    if ($plugin_is_applicable) {
       $this->assertSame($provider_access->reveal(), $result);
     }
     else {
@@ -249,11 +252,59 @@ public function testFindByContext($plugin_access) {
    */
   public function providerTestFindByContext() {
     // Data provider values are:
-    // - the result for the plugin's access method to return.
+    // - the result for the plugin's isApplicable() method to return.
     $data = [];
     $data['plugin access: true'] = [TRUE];
     $data['plugin access: false'] = [FALSE];
     return $data;
   }
 
+  /**
+   * @covers ::findByContext
+   */
+  public function testFindByContextCacheableSectionStorage() {
+    $cacheability = new CacheableMetadata();
+    $contexts = [
+      'foo' => new Context(new ContextDefinition('foo')),
+    ];
+
+    $definitions = [
+      'first' => new SectionStorageDefinition(),
+      'second' => new SectionStorageDefinition(),
+    ];
+    $this->discovery->getDefinitions()->willReturn($definitions);
+
+    // Create a plugin that has cacheability info itself as a cacheable object
+    // and from within ::isApplicable() but is not applicable.
+    $first_plugin = $this->prophesize(SectionStorageInterface::class);
+    $first_plugin->willImplement(CacheableDependencyInterface::class);
+    $first_plugin->getCacheContexts()->shouldNotBeCalled();
+    $first_plugin->getCacheTags()->shouldNotBeCalled();
+    $first_plugin->getCacheMaxAge()->shouldNotBeCalled();
+    $first_plugin->isApplicable($cacheability)->will(function ($arguments) {
+      $arguments[0]->addCacheTags(['first_plugin']);
+      return FALSE;
+    });
+
+    // Create a plugin that adds cacheability info from within ::isApplicable()
+    // and is applicable.
+    $second_plugin = $this->prophesize(SectionStorageInterface::class);
+    $second_plugin->isApplicable($cacheability)->will(function ($arguments) {
+      $arguments[0]->addCacheTags(['second_plugin']);
+      return TRUE;
+    });
+
+    $this->factory->createInstance('first', [])->willReturn($first_plugin->reveal());
+    $this->factory->createInstance('second', [])->willReturn($second_plugin->reveal());
+
+    // Do not do any filtering based on context.
+    $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions)->willReturnArgument(1);
+    $this->contextHandler->applyContextMapping($first_plugin, $contexts)->shouldBeCalled();
+    $this->contextHandler->applyContextMapping($second_plugin, $contexts)->shouldBeCalled();
+
+    $result = $this->manager->findByContext($contexts, $cacheability);
+    $this->assertSame($second_plugin->reveal(), $result);
+    $this->assertSame(['first_plugin', 'second_plugin'], $cacheability->getCacheTags());
+  }
+
 }
