diff --git a/module/VuFind/src/VuFind/Config/SearchSpecsReader.php b/module/VuFind/src/VuFind/Config/SearchSpecsReader.php
index 3398214c76cc393813ff1c10d40a808f1a726e84..8bc6b752e9fca8f11b684477c7e5ba47582a8834 100644
--- a/module/VuFind/src/VuFind/Config/SearchSpecsReader.php
+++ b/module/VuFind/src/VuFind/Config/SearchSpecsReader.php
@@ -74,39 +74,75 @@ class SearchSpecsReader
     {
         // Load data if it is not already in the object's cache:
         if (!isset($this->searchSpecs[$filename])) {
-            // Connect to searchspecs cache:
-            $cache = (null !== $this->cacheManager)
-                ? $this->cacheManager->getCache('searchspecs') : false;
+            $this->searchSpecs[$filename] = $this->getFromPaths(
+                Locator::getBaseConfigPath($filename),
+                Locator::getLocalConfigPath($filename)
+            );
+        }
+
+        return $this->searchSpecs[$filename];
+    }
+
+    /**
+     * Given core and local filenames, retrieve the searchspecs data.
+     *
+     * @param string $defaultFile Full path to file containing default YAML
+     * @param string $customFile  Full path to file containing local customizations
+     * (may be null if no local file exists).
+     *
+     * @return array
+     */
+    protected function getFromPaths($defaultFile, $customFile = null)
+    {
+        // Connect to searchspecs cache:
+        $cache = (null !== $this->cacheManager)
+            ? $this->cacheManager->getCache('searchspecs') : false;
 
-            // Determine full configuration file path:
-            $fullpath = Locator::getBaseConfigPath($filename);
-            $local = Locator::getLocalConfigPath($filename);
+        // Generate cache key:
+        $cacheKey = basename($defaultFile) . '-'
+            . (file_exists($defaultFile) ? filemtime($defaultFile) : 0);
+        if (!empty($customFile)) {
+            $cacheKey .= '-local-' . filemtime($customFile);
+        }
+        $cacheKey = md5($cacheKey);
 
-            // Generate cache key:
-            $cacheKey = $filename . '-'
-                . (file_exists($fullpath) ? filemtime($fullpath) : 0);
-            if (!empty($local)) {
-                $cacheKey .= '-local-' . filemtime($local);
+        // Generate data if not found in cache:
+        if ($cache === false || !($results = $cache->getItem($cacheKey))) {
+            $results = $this->parseYaml($customFile, $defaultFile);
+            if ($cache !== false) {
+                $cache->setItem($cacheKey, $results);
             }
-            $cacheKey = md5($cacheKey);
+        }
 
-            // Generate data if not found in cache:
-            if ($cache === false || !($results = $cache->getItem($cacheKey))) {
-                $results = file_exists($fullpath)
-                    ? Yaml::parse(file_get_contents($fullpath)) : [];
-                if (!empty($local)) {
-                    $localResults = Yaml::parse(file_get_contents($local));
-                    foreach ($localResults as $key => $value) {
-                        $results[$key] = $value;
-                    }
-                }
-                if ($cache !== false) {
-                    $cache->setItem($cacheKey, $results);
+        return $results;
+    }
+
+    /**
+     * Process a YAML file (and its parent, if necessary).
+     *
+     * @param string $file          YAML file to load (will evaluate to empty array
+     * if file does not exist).
+     * @param string $defaultParent Parent YAML file from which $file should
+     * inherit (unless overridden by a specific directive in $file). None by
+     * default.
+     *
+     * @return array
+     */
+    protected function parseYaml($file, $defaultParent = null)
+    {
+        // First load current file:
+        $results = (!empty($file) && file_exists($file))
+            ? Yaml::parse(file_get_contents($file)) : [];
+
+        // Now load in missing sections from parent, if applicable:
+        if (null !== $defaultParent) {
+            foreach ($this->parseYaml($defaultParent) as $section => $contents) {
+                if (!isset($results[$section])) {
+                    $results[$section] = $contents;
                 }
             }
-            $this->searchSpecs[$filename] = $results;
         }
 
-        return $this->searchSpecs[$filename];
+        return $results;
     }
 }
diff --git a/module/VuFind/tests/fixtures/configs/yaml/core.yaml b/module/VuFind/tests/fixtures/configs/yaml/core.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6662da1d1c2c2596955134fdecc36ad746915154
--- /dev/null
+++ b/module/VuFind/tests/fixtures/configs/yaml/core.yaml
@@ -0,0 +1,4 @@
+top:
+  foo: "bar"
+bottom:
+  goo: "gar"
\ No newline at end of file
diff --git a/module/VuFind/tests/fixtures/configs/yaml/local.yaml b/module/VuFind/tests/fixtures/configs/yaml/local.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5dbebb0f4d97cfcec662d23980fca8fadb39e21c
--- /dev/null
+++ b/module/VuFind/tests/fixtures/configs/yaml/local.yaml
@@ -0,0 +1,4 @@
+top:
+  foo: "xyzzy"
+middle:
+  moo: "cow"
\ No newline at end of file
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Config/SearchSpecsReaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Config/SearchSpecsReaderTest.php
index 10dea204f4f736ae1cd1c03d2dcb7a15b69d85df..ce7e7fdf408c907e239bc3c520b2b6db762f9826 100644
--- a/module/VuFind/tests/unit-tests/src/VuFindTest/Config/SearchSpecsReaderTest.php
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Config/SearchSpecsReaderTest.php
@@ -68,4 +68,50 @@ class SearchSpecsReaderTest extends \VuFindTest\Unit\TestCase
         $specs = $reader->get('notreallyasearchspecs.yaml');
         $this->assertEquals([], $specs);
     }
+
+    /**
+     * Test direct loading of two single files.
+     *
+     * @return void
+     */
+    public function testYamlLoad()
+    {
+        $reader = new SearchSpecsReader();
+        $core = __DIR__ . '/../../../../fixtures/configs/yaml/core.yaml';
+        $local = __DIR__ . '/../../../../fixtures/configs/yaml/local.yaml';
+        $this->assertEquals(
+            [
+                'top' => ['foo' => 'bar'],
+                'bottom' => ['goo' => 'gar'],
+            ],
+            $this->callMethod($reader, 'getFromPaths', [$core])
+        );
+        $this->assertEquals(
+            [
+                'top' => ['foo' => 'xyzzy'],
+                'middle' => ['moo' => 'cow'],
+            ],
+            $this->callMethod($reader, 'getFromPaths', [$local])
+        );
+    }
+
+    /**
+     * Test merging of two files.
+     *
+     * @return void
+     */
+    public function testYamlMerge()
+    {
+        $reader = new SearchSpecsReader();
+        $core = __DIR__ . '/../../../../fixtures/configs/yaml/core.yaml';
+        $local = __DIR__ . '/../../../../fixtures/configs/yaml/local.yaml';
+        $this->assertEquals(
+            [
+                'top' => ['foo' => 'xyzzy'],
+                'middle' => ['moo' => 'cow'],
+                'bottom' => ['goo' => 'gar'],
+            ],
+            $this->callMethod($reader, 'getFromPaths', [$core, $local])
+        );
+    }
 }