diff --git a/config/vufind/CollectionTabs.ini b/config/vufind/CollectionTabs.ini
new file mode 100644
index 0000000000000000000000000000000000000000..6d9c078964602648efcc2fe09b2a9596f31c31f3
--- /dev/null
+++ b/config/vufind/CollectionTabs.ini
@@ -0,0 +1,7 @@
+; This file controls the tab display in the Collection view (as opposed to the
+; single record view). The structure is identical to RecordTabs.ini; see the
+; comments in that file for details on the meanings of the various settings.
+[VuFind\RecordDriver\AbstractBase]
+tabs[CollectionList] = CollectionList
+tabs[HierarchyTree] = CollectionHierarchyTree
+defaultTab = null
diff --git a/config/vufind/RecordTabs.ini b/config/vufind/RecordTabs.ini
new file mode 100644
index 0000000000000000000000000000000000000000..bfd3a2fb57ccdb2390ff5a62012eb3664c0386ef
--- /dev/null
+++ b/config/vufind/RecordTabs.ini
@@ -0,0 +1,98 @@
+; This file controls the display of tabs on the Record page for various types of
+; records. Each section name matches a record driver class name, and those settings
+; will be used when displaying that type of record. If no settings are found for a
+; particular class, its parent classes will be checked in turn; thus, you could set
+; up global defaults using a [VuFind\RecordDriver\AbstractBase] section.
+;
+; Within each section, the following settings are supported:
+;
+; tabs[X] = Y -- This activates a tab, using "X" to identify that tab in the URL,
+;                and using a service named "Y" loaded from the RecordTab plugin
+;                manager. The order of tabs entries controls display order.
+; defaultTab  -- This matches an "X" value from a tabs setting, and controls which
+;                tab is active by default. If empty, the global default tab setting
+;                (defaultRecordTab) from config.ini will be used.
+; backgroundLoadedTabs[] -- This repeatable setting can be used to identify tabs
+;                that should be asynchronously loaded in the background to improve
+;                performance. Use the "X" value from the tabs setting as the id.
+[VuFind\RecordDriver\EDS]
+tabs[Description] = Description
+tabs[TOC] = TOC
+tabs[UserComments] = UserComments
+tabs[Reviews] = Reviews
+tabs[Excerpt] = Excerpt
+tabs[Preview] = preview
+tabs[Details] = StaffViewArray
+defaultTab = null
+
+[VuFind\RecordDriver\Pazpar2]
+tabs[Details] = StaffViewMARC
+defaultTab = null
+
+[VuFind\RecordDriver\Primo]
+tabs[Description] = Description
+tabs[TOC] = TOC
+tabs[UserComments] = UserComments
+tabs[Reviews] = Reviews
+tabs[Excerpt] = Excerpt
+tabs[Preview] = preview
+tabs[Details] = StaffViewArray
+defaultTab = null
+
+[VuFind\RecordDriver\SolrAuthDefault]
+tabs[Details] = StaffViewArray
+defaultTab = null
+
+[VuFind\RecordDriver\SolrAuthMarc]
+tabs[Details] = StaffViewMARC
+defaultTab = null
+
+[VuFind\RecordDriver\DefaultRecord]
+tabs[Holdings] = HoldingsILS
+tabs[Description] = Description
+tabs[TOC] = TOC
+tabs[UserComments] = UserComments
+tabs[Reviews] = Reviews
+tabs[Excerpt] = Excerpt
+tabs[Preview] = preview
+tabs[HierarchyTree] = HierarchyTree
+tabs[Map] = Map
+tabs[Similar] = SimilarItemsCarousel
+tabs[Details] = StaffViewArray
+defaultTab = null
+;backgroundLoadedTabs[] = UserComments
+;backgroundLoadedTabs[] = Details
+
+[VuFind\RecordDriver\SolrMarc]
+tabs[Holdings] = HoldingsILS
+tabs[Description] = Description
+tabs[TOC] = TOC
+tabs[UserComments] = UserComments
+tabs[Reviews] = Reviews
+tabs[Excerpt] = Excerpt
+tabs[Preview] = preview
+tabs[HierarchyTree] = HierarchyTree
+tabs[Map] = Map
+tabs[Similar] = SimilarItemsCarousel
+tabs[Details] = StaffViewMARC
+defaultTab = null
+
+[VuFind\RecordDriver\Summon]
+tabs[Description] = Description
+tabs[TOC] = TOC
+tabs[UserComments] = UserComments
+tabs[Reviews] = Reviews
+tabs[Excerpt] = Excerpt
+tabs[Preview] = preview
+tabs[Details] = StaffViewArray
+defaultTab = null
+
+[VuFind\RecordDriver\WorldCat]
+tabs[Holdings] = HoldingsWorldCat
+tabs[Description] = Description
+tabs[TOC] = TOC
+tabs[UserComments] = UserComments
+tabs[Reviews] = Reviews
+tabs[Excerpt] = Excerpt
+tabs[Details] = StaffViewMARC
+defaultTab = null
diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 0aaed3028081061a593ac70f159cf28ed6e69a02..19f3bac01e45329a674de1aa16e84f8cbf2c62ca 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -97,7 +97,7 @@ defaultLoggedInModule = MyResearch
 ; The route VuFind will send users to following a log out operation. Set to false
 ; or omit to attempt to retain the user's current context after log out.
 ;logOutRoute = home
-; This tab will show by default when a record is viewed:
+; Default tab to display when a record is viewed (see also RecordTabs.ini):
 defaultRecordTab = Holdings
 ; Hide the holdings tab if no holdings are available from the ILS; note that this
 ; feature requires your ILS driver to support the hasHoldings() method.
@@ -1594,7 +1594,8 @@ HMACkey = mySuperSecretValue
 ; link to the respective collections page rather than the record page
 ; (default = false).
 ;collections = true
-; Control default tab of Collection view (default = CollectionList)
+; Control default tab of Collection view (default = CollectionList); see also
+; CollectionTabs.ini.
 ;defaultTab = CollectionList
 ; This controls where data is retrieved from to build the Collections/Home page.
 ; It can be set to Index (use the Solr index) or Alphabetic (use the AlphaBrowse
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index e00b93c159d4441e12eb3fb4b724bbf0425ce908..da276254db64b0fff45b65cca51c430b0ec18fab 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -371,6 +371,7 @@ $config = [
             'VuFind\Record\Router' => 'VuFind\Record\RouterFactory',
             'VuFind\RecordDriver\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\RecordTab\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
+            'VuFind\RecordTab\TabManager' => 'VuFind\RecordTab\TabManagerFactory',
             'VuFind\Related\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\Resolver\Driver\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\Role\PermissionManager' => 'VuFind\Role\PermissionManagerFactory',
@@ -538,110 +539,6 @@ $config = [
             'search_results' => [ /* See VuFind\Search\Results\PluginManager for defaults */ ],
             'session' => [ /* see VuFind\Session\PluginManager for defaults */ ],
         ],
-        // This section behaves just like recorddriver_tabs below, but is used for
-        // the collection module instead of the standard record view.
-        'recorddriver_collection_tabs' => [
-            'VuFind\RecordDriver\AbstractBase' => [
-                'tabs' => [
-                    'CollectionList' => 'CollectionList',
-                    'HierarchyTree' => 'CollectionHierarchyTree',
-                ],
-                'defaultTab' => null,
-            ],
-        ],
-        // This section controls which tabs are used for which record driver classes.
-        // Each sub-array is a map from a tab name (as used in a record URL) to a tab
-        // service (found in recordtab plugin manager settings above). If a
-        // particular record driver is not defined here, it will inherit
-        // configuration from a configured parent class.  The defaultTab setting may
-        // be used to specify the default active tab; if null, the value from the
-        // relevant .ini file will be used. You can also specify which tabs are
-        // loaded in the background when arriving at a record tabs view with
-        // backgroundLoadedTabs as a list of tab indexes.
-        'recorddriver_tabs' => [
-            'VuFind\RecordDriver\EDS' => [
-                'tabs' => [
-                    'Description' => 'Description',
-                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
-                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
-                    'Preview' => 'preview',
-                    'Details' => 'StaffViewArray',
-                ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\Pazpar2' => [
-                'tabs' => [
-                    'Details' => 'StaffViewMARC',
-                 ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\Primo' => [
-                'tabs' => [
-                    'Description' => 'Description',
-                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
-                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
-                    'Preview' => 'preview',
-                    'Details' => 'StaffViewArray',
-                ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\SolrAuthDefault' => [
-                'tabs' => [
-                    'Details' => 'StaffViewArray',
-                 ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\SolrAuthMarc' => [
-                'tabs' => [
-                    'Details' => 'StaffViewMARC',
-                 ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\DefaultRecord' => [
-                'tabs' => [
-                    'Holdings' => 'HoldingsILS', 'Description' => 'Description',
-                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
-                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
-                    'Preview' => 'preview',
-                    'HierarchyTree' => 'HierarchyTree', 'Map' => 'Map',
-                    'Similar' => 'SimilarItemsCarousel',
-                    'Details' => 'StaffViewArray',
-                ],
-                'defaultTab' => null,
-                // 'backgroundLoadedTabs' => ['UserComments', 'Details']
-            ],
-            'VuFind\RecordDriver\SolrMarc' => [
-                'tabs' => [
-                    'Holdings' => 'HoldingsILS', 'Description' => 'Description',
-                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
-                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
-                    'Preview' => 'preview',
-                    'HierarchyTree' => 'HierarchyTree', 'Map' => 'Map',
-                    'Similar' => 'SimilarItemsCarousel',
-                    'Details' => 'StaffViewMARC',
-                ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\Summon' => [
-                'tabs' => [
-                    'Description' => 'Description',
-                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
-                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
-                    'Preview' => 'preview',
-                    'Details' => 'StaffViewArray',
-                ],
-                'defaultTab' => null,
-            ],
-            'VuFind\RecordDriver\WorldCat' => [
-                'tabs' => [
-                    'Holdings' => 'HoldingsWorldCat', 'Description' => 'Description',
-                    'TOC' => 'TOC', 'UserComments' => 'UserComments',
-                    'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
-                    'Details' => 'StaffViewMARC',
-                ],
-                'defaultTab' => null,
-            ],
-        ],
     ],
     // Authorization configuration:
     'zfc_rbac' => [
diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetails.php b/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetails.php
index e8c4a1a866d2b3938fa27ae90d9c570c638b9850..f30d23363b61baf455a42bee72e055d27aa45cd7 100644
--- a/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetails.php
+++ b/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetails.php
@@ -28,7 +28,7 @@
 namespace VuFind\AjaxHandler;
 
 use VuFind\Record\Loader;
-use VuFind\RecordTab\PluginManager as TabManager;
+use VuFind\RecordTab\TabManager;
 use Zend\Http\PhpEnvironment\Request;
 use Zend\Mvc\Controller\Plugin\Params;
 use Zend\View\Renderer\RendererInterface;
@@ -72,7 +72,7 @@ class GetRecordDetails extends AbstractBase
      *
      * @var TabManager
      */
-    protected $pluginManager;
+    protected $tabManager;
 
     /**
      * View renderer
@@ -87,16 +87,16 @@ class GetRecordDetails extends AbstractBase
      * @param array             $config   ZF configuration
      * @param Request           $request  HTTP request
      * @param Loader            $loader   Record loader
-     * @param TabManager        $pm       RecordTab plugin manager
+     * @param TabManager        $tm       Record Tab manager
      * @param RendererInterface $renderer Renderer
      */
     public function __construct(array $config, Request $request, Loader $loader,
-        TabManager $pm, RendererInterface $renderer
+        TabManager $tm, RendererInterface $renderer
     ) {
         $this->config = $config;
         $this->request = $request;
         $this->recordLoader = $loader;
-        $this->pluginManager = $pm;
+        $this->tabManager = $tm;
         $this->renderer = $renderer;
     }
 
@@ -115,11 +115,8 @@ class GetRecordDetails extends AbstractBase
             '/\W/', '', trim(strtolower($params->fromQuery('type')))
         );
 
-        $details = $this->pluginManager->getTabDetailsForRecord(
-            $driver,
-            $this->config['vufind']['recorddriver_tabs'],
-            $this->request,
-            'Information'
+        $details = $this->tabManager->getTabDetailsForRecord(
+            $driver, $this->request, 'Information'
         );
 
         $html = $this->renderer->render(
@@ -128,9 +125,8 @@ class GetRecordDetails extends AbstractBase
                 'defaultTab' => $details['default'],
                 'driver' => $driver,
                 'tabs' => $details['tabs'],
-                'backgroundTabs' => $this->pluginManager->getBackgroundTabNames(
-                    $driver, $this->config['vufind']['recorddriver_tabs']
-                )
+                'backgroundTabs' => $this->tabManager
+                    ->getBackgroundTabNames($driver),
             ]
         );
         return $this->formatResponse(compact('html'));
diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetailsFactory.php b/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetailsFactory.php
index 361884e127fbdad5ea72ce0fb4f2fb4bdeeaf817..8730340ed54abb6095cd599f28e85d0464a5523d 100644
--- a/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetailsFactory.php
+++ b/module/VuFind/src/VuFind/AjaxHandler/GetRecordDetailsFactory.php
@@ -67,7 +67,7 @@ class GetRecordDetailsFactory
             $container->get('Config'),
             $container->get('Request'),
             $container->get(\VuFind\Record\Loader::class),
-            $container->get(\VuFind\RecordTab\PluginManager::class),
+            $container->get(\VuFind\RecordTab\TabManager::class),
             $container->get('ViewRenderer')
         );
     }
diff --git a/module/VuFind/src/VuFind/Controller/AbstractBase.php b/module/VuFind/src/VuFind/Controller/AbstractBase.php
index fdc729f9a73ece7dfccbfbca928498d84034b331..c7024ba7d54f726da972353cd7bf7262dbb9a4f6 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractBase.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractBase.php
@@ -673,11 +673,10 @@ class AbstractBase extends AbstractActionController
     /**
      * Get the tab configuration for this controller.
      *
-     * @return array
+     * @return \VuFind\RecordTab\TabManager
      */
-    protected function getRecordTabConfig()
+    protected function getRecordTabManager()
     {
-        $cfg = $this->serviceLocator->get('Config');
-        return $cfg['vufind']['recorddriver_tabs'];
+        return $this->serviceLocator->get(\VuFind\RecordTab\TabManager::class);
     }
 }
diff --git a/module/VuFind/src/VuFind/Controller/AbstractRecord.php b/module/VuFind/src/VuFind/Controller/AbstractRecord.php
index 3eb73720f2b7540ca57fe0d04ccc317ee4159671..5a5bd02cd5cc532b4f877fa3708afb5fdc19a11f 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractRecord.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractRecord.php
@@ -647,16 +647,12 @@ class AbstractRecord extends AbstractBase
     {
         $driver = $this->loadRecord();
         $request = $this->getRequest();
-        $rtpm = $this->serviceLocator->get(\VuFind\RecordTab\PluginManager::class);
-        $details = $rtpm->getTabDetailsForRecord(
-            $driver, $this->getRecordTabConfig(), $request,
-            $this->fallbackDefaultTab
-        );
+        $manager = $this->getRecordTabManager();
+        $details = $manager
+            ->getTabDetailsForRecord($driver, $request, $this->fallbackDefaultTab);
         $this->allTabs = $details['tabs'];
         $this->defaultTab = $details['default'] ? $details['default'] : false;
-        $this->backgroundTabs = $rtpm->getBackgroundTabNames(
-            $driver, $this->getRecordTabConfig()
-        );
+        $this->backgroundTabs = $manager->getBackgroundTabNames($driver);
     }
 
     /**
diff --git a/module/VuFind/src/VuFind/Controller/AuthorityController.php b/module/VuFind/src/VuFind/Controller/AuthorityController.php
index 4491051d9fb42b998df4630b1805ca70a7e26e8e..d803f88fbfa4d558e5cf85faaabc87f4a2a1ab56 100644
--- a/module/VuFind/src/VuFind/Controller/AuthorityController.php
+++ b/module/VuFind/src/VuFind/Controller/AuthorityController.php
@@ -79,9 +79,7 @@ class AuthorityController extends AbstractSearch
         $driver = $this->serviceLocator->get(\VuFind\Record\Loader::class)
             ->load($id, 'SolrAuth');
         $request = $this->getRequest();
-        $tabs = $this->serviceLocator
-            ->get(\VuFind\RecordTab\PluginManager::class)
-            ->getTabsForRecord($driver, $this->getRecordTabConfig(), $request);
+        $tabs = $this->getRecordTabManager()->getTabsForRecord($driver, $request);
         return $this->createViewModel(['driver' => $driver, 'tabs' => $tabs]);
     }
 
diff --git a/module/VuFind/src/VuFind/Controller/CollectionController.php b/module/VuFind/src/VuFind/Controller/CollectionController.php
index 959f1ef839f9c121686fc10cd5549504afb95579..39b32670b6982968ebbdbb5ac2197e84b9ccc2ed 100644
--- a/module/VuFind/src/VuFind/Controller/CollectionController.php
+++ b/module/VuFind/src/VuFind/Controller/CollectionController.php
@@ -61,12 +61,13 @@ class CollectionController extends AbstractRecord
     /**
      * Get the tab configuration for this controller.
      *
-     * @return array
+     * @return \VuFind\RecordTab\TabManager
      */
-    protected function getRecordTabConfig()
+    protected function getRecordTabManager()
     {
-        $cfg = $this->serviceLocator->get('Config');
-        return $cfg['vufind']['recorddriver_collection_tabs'];
+        $manager = parent::getRecordTabManager();
+        $manager->setContext('collection');
+        return $manager;
     }
 
     /**
diff --git a/module/VuFind/src/VuFind/RecordTab/PluginManager.php b/module/VuFind/src/VuFind/RecordTab/PluginManager.php
index 215f77130a389cfcfbc51af118c6b303d6c980dc..920541c56bab7fccabc5765c8baabfcdfa97aae5 100644
--- a/module/VuFind/src/VuFind/RecordTab/PluginManager.php
+++ b/module/VuFind/src/VuFind/RecordTab/PluginManager.php
@@ -27,7 +27,6 @@
  */
 namespace VuFind\RecordTab;
 
-use VuFind\RecordDriver\AbstractBase as AbstractRecordDriver;
 use Zend\ServiceManager\Factory\InvokableFactory;
 
 /**
@@ -104,35 +103,6 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
         parent::__construct($configOrContainerInstance, $v3config);
     }
 
-    /**
-     * Load the specified key from the configuration array using the best
-     * available match to the class of the provided driver. Return the default
-     * value if no match is found.
-     *
-     * @param AbstractRecordDriver $driver  Record driver
-     * @param array                $config  Tab configuration (map of
-     * driver class => tab configuration)
-     * @param string               $setting Key to load from configuration
-     * @param string               $default Default to use if no setting found
-     *
-     * @return mixed
-     */
-    protected function getConfigByClass(AbstractRecordDriver $driver,
-        array $config, $setting, $default
-    ) {
-        // Get the current record driver's class name, then start a loop
-        // in case we need to use a parent class' name to find the appropriate
-        // setting.
-        $className = get_class($driver);
-        do {
-            if (isset($config[$className][$setting])) {
-                return $config[$className][$setting];
-            }
-        } while ($className = get_parent_class($className));
-        // No setting found...
-        return $default;
-    }
-
     /**
      * Return the name of the base class or interface that plug-ins must conform
      * to.
@@ -143,125 +113,4 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
     {
         return TabInterface::class;
     }
-
-    /**
-     * Get an array of service names by looking up the provided record driver in
-     * the provided tab configuration array.
-     *
-     * @param AbstractRecordDriver $driver Record driver
-     * @param array                $config Tab configuration (associative array
-     * including 'tabs' array mapping driver class => tab service name)
-     *
-     * @return array
-     */
-    protected function getTabServiceNames(AbstractRecordDriver $driver,
-        array $config
-    ) {
-        return $this->getConfigByClass($driver, $config, 'tabs', []);
-    }
-
-    /**
-     * Get an array of tabs names configured to load via AJAX in the background
-     *
-     * @param AbstractRecordDriver $driver Record driver
-     * @param array                $config Tab configuration (associative array
-     * including 'tabs' array mapping driver class => tab service name)
-     *
-     * @return array
-     */
-    public function getBackgroundTabNames(AbstractRecordDriver $driver,
-        array $config
-    ) {
-        return $this->getConfigByClass($driver, $config, 'backgroundLoadedTabs', []);
-    }
-
-    /**
-     * Get a default tab by looking up the provided record driver in the tab
-     * configuration array.
-     *
-     * @param AbstractRecordDriver $driver   Record driver
-     * @param array                $config   Tab configuration (map of
-     * driver class => tab configuration)
-     * @param array                $tabs     Details on available tabs (returned
-     * from getTabsForRecord()).
-     * @param string               $fallback Fallback to use if no tab specified
-     * or matched.
-     *
-     * @return string
-     */
-    public function getDefaultTabForRecord(AbstractRecordDriver $driver,
-        array $config, array $tabs, $fallback = null
-    ) {
-        // Load default from module configuration:
-        $default = $this->getConfigByClass($driver, $config, 'defaultTab', null);
-
-        // Missing/invalid record driver configuration? Fall back to provided
-        // default:
-        if ((!$default || !isset($tabs[$default])) && isset($tabs[$fallback])) {
-            $default = $fallback;
-        }
-
-        // Is configured tab still invalid? If so, pick first existing tab:
-        if ((!$default || !isset($tabs[$default])) && !empty($tabs)) {
-            $keys = array_keys($tabs);
-            $default = $keys[0];
-        }
-
-        return $default;
-    }
-
-    /**
-     * Convenience method to load tab information, including default, in a
-     * single pass. Returns an associative array with 'tabs' and 'default' keys.
-     *
-     * @param AbstractRecordDriver $driver   Record driver
-     * @param array                $config   Tab configuration (map of
-     * driver class => tab configuration)
-     * @param \Zend\Http\Request   $request  User request (optional)
-     * @param string               $fallback Fallback default tab to use if no
-     * tab specified or matched.
-     *
-     * @return array
-     */
-    public function getTabDetailsForRecord(AbstractRecordDriver $driver,
-        array $config, $request = null, $fallback = null
-    ) {
-        $tabs = $this->getTabsForRecord($driver, $config, $request);
-        $default = $this->getDefaultTabForRecord($driver, $config, $tabs, $fallback);
-        return compact('tabs', 'default');
-    }
-
-    /**
-     * Get an array of valid tabs for the provided record driver.
-     *
-     * @param AbstractRecordDriver $driver  Record driver
-     * @param array                $config  Tab configuration (map of
-     * driver class => tab configuration)
-     * @param \Zend\Http\Request   $request User request (optional)
-     *
-     * @return array               service name => tab object
-     */
-    public function getTabsForRecord(AbstractRecordDriver $driver,
-        array $config, $request = null
-    ) {
-        $tabs = [];
-        foreach ($this->getTabServiceNames($driver, $config) as $tabKey => $svc) {
-            if (!$this->has($svc)) {
-                continue;
-            }
-            $newTab = $this->get($svc);
-            if (method_exists($newTab, 'setRecordDriver')) {
-                $newTab->setRecordDriver($driver);
-            }
-            if ($request instanceof \Zend\Http\Request
-                && method_exists($newTab, 'setRequest')
-            ) {
-                $newTab->setRequest($request);
-            }
-            if ($newTab->isActive()) {
-                $tabs[$tabKey] = $newTab;
-            }
-        }
-        return $tabs;
-    }
 }
diff --git a/module/VuFind/src/VuFind/RecordTab/TabManager.php b/module/VuFind/src/VuFind/RecordTab/TabManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..760af483bd17f9ccfa384c950012901edcdedbd8
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/TabManager.php
@@ -0,0 +1,283 @@
+<?php
+/**
+ * Record tab manager
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:record_tabs Wiki
+ */
+namespace VuFind\RecordTab;
+
+use VuFind\Config\PluginManager as ConfigManager;
+use VuFind\RecordDriver\AbstractBase as AbstractRecordDriver;
+
+/**
+ * Record tab manager
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:plugins:record_tabs Wiki
+ */
+class TabManager
+{
+    /**
+     * Settings for different tab contexts.
+     *
+     * @var array
+     */
+    protected $contextSettings = [
+        'record' => [
+            'configFile' => 'RecordTabs',
+            'legacyConfigSection' => 'recorddriver_tabs',
+        ],
+        'collection' => [
+            'configFile' => 'CollectionTabs',
+            'legacyConfigSection' => 'recorddriver_collection_tabs',
+        ],
+    ];
+
+    /**
+     * Tab configurations
+     *
+     * @var array
+     */
+    protected $config = [];
+
+    /**
+     * Configuration plugin manager
+     *
+     * @var ConfigManager
+     */
+    protected $configManager;
+
+    /**
+     * RecordTab plugin manager
+     *
+     * @var PluginManager
+     */
+    protected $pluginManager;
+
+    /**
+     * Overall framework configuration
+     *
+     * @var array
+     */
+    protected $zendConfig;
+
+    /**
+     * Current active context (defaults to 'record')
+     *
+     * @var string
+     */
+    protected $context = 'record';
+
+    /**
+     * Constructor
+     *
+     * @param PluginManager $pm         RecordTab plugin manager
+     * @param ConfigManager $cm         Configuration plugin manager
+     * @param array         $zendConfig Zend Framework configuration
+     */
+    public function __construct(PluginManager $pm, ConfigManager $cm,
+        $zendConfig = []
+    ) {
+        $this->pluginManager = $pm;
+        $this->configManager = $cm;
+        $this->zendConfig = $zendConfig;
+
+        // Initialize default context.
+        $this->initializeCurrentContext();
+    }
+
+    /**
+     * Set and (if necessary) initialize the context.
+     *
+     * @param string $context Context to initialize
+     *
+     * @return void
+     * @throws \Exception
+     */
+    public function setContext($context)
+    {
+        if (!in_array($context, array_keys($this->contextSettings))) {
+            throw new \Exception("Unsupported context: $context");
+        }
+        $this->context = $context;
+        $this->initializeCurrentContext();
+    }
+
+    /**
+     * Initialize the current context (if not already initialized).
+     *
+     * @return void
+     */
+    protected function initializeCurrentContext()
+    {
+        if (!isset($this->config[$this->context])) {
+            $key = $this->contextSettings[$this->context]['legacyConfigSection']
+                ?? 'recorddriver_tabs';
+            $legacyConfig = $this->zendConfig['vufind'][$key] ?? [];
+            $iniConfig = $this->configManager->get(
+                $this->contextSettings[$this->context]['configFile']
+            )->toArray();
+            $this->config[$this->context] = array_merge($legacyConfig, $iniConfig);
+        }
+    }
+
+    /**
+     * Load the specified key from the configuration array using the best
+     * available match to the class of the provided driver. Return the default
+     * value if no match is found.
+     *
+     * @param AbstractRecordDriver $driver  Record driver
+     * @param string               $setting Key to load from configuration
+     * @param string               $default Default to use if no setting found
+     *
+     * @return mixed
+     */
+    protected function getConfigByClass(AbstractRecordDriver $driver,
+        $setting, $default
+    ) {
+        // Get the current record driver's class name, then start a loop
+        // in case we need to use a parent class' name to find the appropriate
+        // setting.
+        $className = get_class($driver);
+        do {
+            if (isset($this->config[$this->context][$className][$setting])) {
+                return $this->config[$this->context][$className][$setting];
+            }
+        } while ($className = get_parent_class($className));
+        // No setting found...
+        return $default;
+    }
+
+    /**
+     * Get an array of service names by looking up the provided record driver in
+     * the provided tab configuration array.
+     *
+     * @param AbstractRecordDriver $driver Record driver
+     *
+     * @return array
+     */
+    protected function getTabServiceNames(AbstractRecordDriver $driver)
+    {
+        return $this->getConfigByClass($driver, 'tabs', []);
+    }
+
+    /**
+     * Get an array of tabs names configured to load via AJAX in the background
+     *
+     * @param AbstractRecordDriver $driver Record driver
+     *
+     * @return array
+     */
+    public function getBackgroundTabNames(AbstractRecordDriver $driver)
+    {
+        return $this->getConfigByClass($driver, 'backgroundLoadedTabs', []);
+    }
+
+    /**
+     * Get a default tab by looking up the provided record driver in the tab
+     * configuration array.
+     *
+     * @param AbstractRecordDriver $driver   Record driver
+     * @param array                $tabs     Details on available tabs (returned
+     * from getTabsForRecord()).
+     * @param string               $fallback Fallback to use if no tab specified
+     * or matched.
+     *
+     * @return string
+     */
+    public function getDefaultTabForRecord(AbstractRecordDriver $driver,
+        array $tabs, $fallback = null
+    ) {
+        // Load default from module configuration:
+        $default = $this->getConfigByClass($driver, 'defaultTab', null);
+
+        // Missing/invalid record driver configuration? Fall back to provided
+        // default:
+        if ((!$default || !isset($tabs[$default])) && isset($tabs[$fallback])) {
+            $default = $fallback;
+        }
+
+        // Is configured tab still invalid? If so, pick first existing tab:
+        if ((!$default || !isset($tabs[$default])) && !empty($tabs)) {
+            $keys = array_keys($tabs);
+            $default = $keys[0];
+        }
+
+        return $default;
+    }
+
+    /**
+     * Convenience method to load tab information, including default, in a
+     * single pass. Returns an associative array with 'tabs' and 'default' keys.
+     *
+     * @param AbstractRecordDriver $driver   Record driver
+     * @param \Zend\Http\Request   $request  User request (optional)
+     * @param string               $fallback Fallback default tab to use if no
+     * tab specified or matched.
+     *
+     * @return array
+     */
+    public function getTabDetailsForRecord(AbstractRecordDriver $driver,
+        $request = null, $fallback = null
+    ) {
+        $tabs = $this->getTabsForRecord($driver, $request);
+        $default = $this->getDefaultTabForRecord($driver, $tabs, $fallback);
+        return compact('tabs', 'default');
+    }
+
+    /**
+     * Get an array of valid tabs for the provided record driver.
+     *
+     * @param AbstractRecordDriver $driver  Record driver
+     * @param \Zend\Http\Request   $request User request (optional)
+     *
+     * @return array               service name => tab object
+     */
+    public function getTabsForRecord(AbstractRecordDriver $driver,
+        $request = null
+    ) {
+        $tabs = [];
+        foreach ($this->getTabServiceNames($driver) as $tabKey => $svc) {
+            if (!$this->pluginManager->has($svc)) {
+                continue;
+            }
+            $newTab = $this->pluginManager->get($svc);
+            if (method_exists($newTab, 'setRecordDriver')) {
+                $newTab->setRecordDriver($driver);
+            }
+            if ($request instanceof \Zend\Http\Request
+                && method_exists($newTab, 'setRequest')
+            ) {
+                $newTab->setRequest($request);
+            }
+            if ($newTab->isActive()) {
+                $tabs[$tabKey] = $newTab;
+            }
+        }
+        return $tabs;
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/TabManagerFactory.php b/module/VuFind/src/VuFind/RecordTab/TabManagerFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..4f352736889f914b5aebab69761615b2ece5f897
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/TabManagerFactory.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Factory for building the TabManager.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the TabManager.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class TabManagerFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        return new $requestedName(
+            $container->get(PluginManager::class),
+            $container->get(\VuFind\Config\PluginManager::class),
+            $container->get('config')
+        );
+    }
+}
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/TabManagerTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/TabManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ff3c31f0e40db8c34fb2bcaaf9e9a00e175fffba
--- /dev/null
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/TabManagerTest.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * RecordTab Manager Test Class
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:testing:unit_tests Wiki
+ */
+namespace VuFindTest\RecordTab;
+
+use VuFind\Config\PluginManager as ConfigManager;
+use VuFind\RecordTab\PluginManager;
+use VuFind\RecordTab\TabManager;
+
+/**
+ * RecordTab Manager Test Class
+ *
+ * @category VuFind
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development:testing:unit_tests Wiki
+ */
+class TabManagerTest extends \VuFindTest\Unit\TestCase
+{
+    /**
+     * Set up a tab manager for testing.
+     *
+     * @param PluginManager $pluginManager Plugin manager to use (null for default)
+     * @param ConfigManager $configManager Config manager to use (null for default)
+     * @return TabManager
+     */
+    protected function getTabManager(PluginManager $pluginManager = null,
+        ConfigManager $configManager = null
+    ) {
+        $legacyConfig = [
+            'vufind' => [
+                'recorddriver_collection_tabs' => [
+                    'VuFind\RecordDriver\AbstractBase' => [
+                        'tabs' => [
+                            'coll' => 'ection',
+                        ],
+                        'defaultTab' => null,
+                    ],
+                ],
+                'recorddriver_tabs' => [
+                    'VuFind\RecordDriver\AbstractBase' => [
+                        'tabs' => [
+                            'foo' => 'bar',
+                        ],
+                        'defaultTab' => null,
+                    ],
+                ],
+            ],
+        ];
+        return new TabManager(
+            $pluginManager ?? $this->getMockPluginManager(),
+            $configManager ?? $this->getMockConfigManager(),
+            $legacyConfig
+        );
+    }
+
+    /**
+     * Build a mock plugin manager.
+     *
+     * @return PluginManager
+     */
+    protected function getMockPluginManager()
+    {
+        $mockTab = $this->getMockBuilder(\VuFind\RecordTab\StaffViewArray::class)
+            ->disableOriginalConstructor()->setMethods(['isActive'])->getMock();
+        $mockTab->expects($this->any())->method('isActive')
+            ->will($this->returnValue(true));
+        $pm = $this->getMockBuilder(\VuFind\RecordTab\PluginManager::class)
+            ->disableOriginalConstructor()->getMock();
+        $pm->expects($this->any())->method('has')
+            ->will($this->returnValue(true));
+        $pm->expects($this->any())->method('get')
+            ->will($this->returnValue($mockTab));
+        return $pm;
+    }
+
+    /**
+     * Build a mock config manager.
+     *
+     * @return ConfigManager
+     */
+    protected function getMockConfigManager()
+    {
+        $iniConfig = new \Zend\Config\Config(
+            [
+                'VuFind\RecordDriver\EDS' => [
+                    'tabs' => [
+                        'xyzzy' => 'yzzyx',
+                        'zip' => 'line',
+                    ],
+                    'defaultTab' => 'zip',
+                    'backgroundLoadedTabs' => ['xyzzy'],
+                ],
+            ]
+        );
+        $configManager = $this->getMockBuilder(\VuFind\Config\PluginManager::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['has', 'get'])
+            ->getMock();
+        $configManager->expects($this->any())->method('has')
+            ->will($this->returnValue(true));
+        $configManager->expects($this->any())->method('get')
+            ->will($this->returnValue($iniConfig));
+        return $configManager;
+    }
+
+    /**
+     * Test that we get the expected tab service names.
+     *
+     * @return void
+     */
+    public function testGetTabDetailsForRecord()
+    {
+        $tabManager = $this->getTabManager();
+        $driver1 = $this->getMockBuilder(\VuFind\RecordDriver\EDS::class)
+            ->disableOriginalConstructor()->getMock();
+        $details1 = $tabManager->getTabDetailsForRecord($driver1);
+        $this->assertEquals('zip', $details1['default']);
+        $this->assertEquals(['xyzzy', 'zip'], array_keys($details1['tabs']));
+        $driver2 = $this->getMockBuilder(\VuFind\RecordDriver\SolrDefault::class)
+            ->disableOriginalConstructor()->getMock();
+        $details2 = $tabManager->getTabDetailsForRecord($driver2);
+        $this->assertEquals('foo', $details2['default']);
+        $this->assertEquals(['foo'], array_keys($details2['tabs']));
+        // Switch to collection mode to load a different configuration:
+        $tabManager->setContext('collection');
+        $details2b = $tabManager->getTabDetailsForRecord($driver2);
+        $this->assertEquals('coll', $details2b['default']);
+        $this->assertEquals(['coll'], array_keys($details2b['tabs']));
+    }
+
+    /**
+     * Test getBackgroundTabNames.
+     *
+     * @return void
+     */
+    public function testGetBackgroundTabNames()
+    {
+        $tabManager = $this->getTabManager();
+        $driver1 = $this->getMockBuilder(\VuFind\RecordDriver\EDS::class)
+            ->disableOriginalConstructor()->getMock();
+        $this->assertEquals(['xyzzy'], $tabManager->getBackgroundTabNames($driver1));
+        $driver2 = $this->getMockBuilder(\VuFind\RecordDriver\SolrDefault::class)
+            ->disableOriginalConstructor()->getMock();
+        $this->assertEquals([], $tabManager->getBackgroundTabNames($driver2));
+    }
+}