diff --git a/app/Search/SearchOptions.php b/app/Search/SearchOptions.php
index af146d5fd..d38fc8d57 100644
--- a/app/Search/SearchOptions.php
+++ b/app/Search/SearchOptions.php
@@ -78,7 +78,7 @@ class SearchOptions
         ];
 
         $patterns = [
-            'exacts'  => '/"(.*?)(?<!\\\)"/',
+            'exacts'  => '/"((?:\\\\.|[^"\\\\])*)"/',
             'tags'    => '/\[(.*?)\]/',
             'filters' => '/\{(.*?)\}/',
         ];
@@ -93,9 +93,9 @@ class SearchOptions
             }
         }
 
-        // Unescape exacts
+        // Unescape exacts and backslash escapes
         foreach ($terms['exacts'] as $index => $exact) {
-            $terms['exacts'][$index] = str_replace('\"', '"', $exact);
+            $terms['exacts'][$index] = static::decodeEscapes($exact);
         }
 
         // Parse standard terms
@@ -118,6 +118,28 @@ class SearchOptions
         return $terms;
     }
 
+    /**
+     * Decode backslash escaping within the input string.
+     */
+    protected static function decodeEscapes(string $input): string
+    {
+        $decoded = "";
+        $escaping = false;
+
+        foreach (str_split($input) as $char) {
+            if ($escaping) {
+                $decoded .= $char;
+                $escaping = false;
+            } else if ($char === '\\') {
+                $escaping = true;
+            } else {
+                $decoded .= $char;
+            }
+        }
+
+        return $decoded;
+    }
+
     /**
      * Parse a standard search term string into individual search terms and
      * convert any required terms to exact matches. This is done since some
@@ -156,7 +178,8 @@ class SearchOptions
         $parts = $this->searches;
 
         foreach ($this->exacts as $term) {
-            $escaped = str_replace('"', '\"', $term);
+            $escaped = str_replace('\\', '\\\\', $term);
+            $escaped = str_replace('"', '\"', $escaped);
             $parts[] = '"' . $escaped . '"';
         }
 
diff --git a/tests/Commands/ResetMfaCommandTest.php b/tests/Commands/ResetMfaCommandTest.php
index 85f8f6430..39c8c689b 100644
--- a/tests/Commands/ResetMfaCommandTest.php
+++ b/tests/Commands/ResetMfaCommandTest.php
@@ -11,7 +11,7 @@ class ResetMfaCommandTest extends TestCase
     public function test_command_requires_email_or_id_option()
     {
         $this->artisan('bookstack:reset-mfa')
-            ->expectsOutput('Either a --id=<number> or --email=<email> option must be provided.')
+            ->expectsOutputToContain('Either a --id=<number> or --email=<email> option must be provided.')
             ->assertExitCode(1);
     }
 
diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php
index a070ce3fa..fbb47226e 100644
--- a/tests/Entity/EntitySearchTest.php
+++ b/tests/Entity/EntitySearchTest.php
@@ -466,10 +466,10 @@ class EntitySearchTest extends TestCase
         $search = $this->asEditor()->get('/search?term=' . urlencode('\\\\cat\\dog'));
         $search->assertSee($page->getUrl(), false);
 
-        $search = $this->asEditor()->get('/search?term=' . urlencode('"\\dog\\"'));
+        $search = $this->asEditor()->get('/search?term=' . urlencode('"\\dog\\\\"'));
         $search->assertSee($page->getUrl(), false);
 
-        $search = $this->asEditor()->get('/search?term=' . urlencode('"\\badger\\"'));
+        $search = $this->asEditor()->get('/search?term=' . urlencode('"\\badger\\\\"'));
         $search->assertDontSee($page->getUrl(), false);
 
         $search = $this->asEditor()->get('/search?term=' . urlencode('[\\Categorylike%\\fluffy]'));
diff --git a/tests/Entity/SearchOptionsTest.php b/tests/Entity/SearchOptionsTest.php
index 8bc9d02e4..ea4d727a4 100644
--- a/tests/Entity/SearchOptionsTest.php
+++ b/tests/Entity/SearchOptionsTest.php
@@ -20,9 +20,9 @@ class SearchOptionsTest extends TestCase
 
     public function test_from_string_properly_parses_escaped_quotes()
     {
-        $options = SearchOptions::fromString('"\"cat\"" surprise "\"\"" "\"donkey" "\""');
+        $options = SearchOptions::fromString('"\"cat\"" surprise "\"\"" "\"donkey" "\"" "\\\\"');
 
-        $this->assertEquals(['"cat"', '""', '"donkey', '"'], $options->exacts);
+        $this->assertEquals(['"cat"', '""', '"donkey', '"', '\\'], $options->exacts);
     }
 
     public function test_to_string_includes_all_items_in_the_correct_format()
@@ -40,13 +40,13 @@ class SearchOptionsTest extends TestCase
         }
     }
 
-    public function test_to_string_escapes_quotes_as_expected()
+    public function test_to_string_escapes_as_expected()
     {
         $options = new SearchOptions();
-        $options->exacts = ['"cat"', '""', '"donkey', '"'];
+        $options->exacts = ['"cat"', '""', '"donkey', '"', '\\', '\\"'];
 
         $output = $options->toString();
-        $this->assertEquals('"\"cat\"" "\"\"" "\"donkey" "\""', $output);
+        $this->assertEquals('"\"cat\"" "\"\"" "\"donkey" "\"" "\\\\" "\\\\\""', $output);
     }
 
     public function test_correct_filter_values_are_set_from_string()