Regular expressions7 min readExamples run on PHP 8.5.10

preg_quote()

Learn how preg_quote() escapes regex metacharacters and an optional delimiter so runtime strings can be matched literally inside PCRE patterns.

Signature

preg_quote(string $str, ?string $delimiter = null): string

Purpose

A regular expression pattern is a small program, not plain text. If a string comes from a variable, such as a product code, a search term or a configured keyword, characters like ., + or ( can change what the pattern means. preg_quote() solves this. It puts a backslash in front of every character that has special meaning in regex syntax, so the result can be embedded in a pattern and matched literally.

Signature

Parameters and defaults

  • $str is the input string to be quoted.
  • $delimiter is optional and defaults to null. When you pass one, that character is escaped as well. Pass the same delimiter you use around the pattern. / is the most common one. Because the delimiter is not part of regex syntax itself, preg_quote() does not escape / unless you ask it to.

The characters escaped by default are:

. \ + * ? [ ^ ] $ ( ) { } = ! < > | : - #

The # character is part of this list since PHP 7.3.0. A nullable $delimiter has been accepted since PHP 7.2.0.

Return value and failure cases

The function returns the quoted string. The manual documents no failure return value for it: any string can be quoted, and the function is binary-safe. The function only escapes text. It never adds delimiters or modifiers, and it does not check whether the final pattern is valid. If you assemble a broken pattern elsewhere, the error will appear when you call preg_match() or a similar function.

Example 1: quoting a delimiter

The manual's classic case contains a dollar sign and a slash. The dollar sign is always escaped. The slash is escaped here only because '/' is passed as the delimiter. Spaces and letters stay unchanged.

Example
<?php
$keywords = '$40 for a g3/400';
echo preg_quote($keywords, '/'), "\n";
Output
\$40 for a g3\/400

Example 2: matching user text literally

The needle 3.5+tax contains two metacharacters. Without quoting, . means any character and + repeats the previous item, so the unquoted pattern does not match the text it came from.

Example
<?php
$text = 'Price: 3.5+tax (approx)';
$needle = '3.5+tax';
$pattern = '/' . preg_quote($needle, '/') . '/';
echo json_encode([
    'pattern' => $pattern,
    'matched' => preg_match($pattern, $text) === 1,
    'unquotedMatched' => preg_match('/' . $needle . '/', $text) === 1,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"pattern":"/3\\.5\\+tax/","matched":true,"unquotedMatched":false}

Example 3: building an alternation with another delimiter

When you combine several literal strings, quote each one separately before joining them with |. Joining first would let a | inside a word split the alternation. This example also uses ~ as the pattern delimiter and passes the same character to preg_quote().

Example
<?php
$words = ['a.b', 'c|d', 'e*'];
$parts = array_map(fn(string $word): string => preg_quote($word, '~'), $words);
$pattern = '~^(?:' . implode('|', $parts) . ')$~';
$results = [];
foreach (['a.b', 'axb', 'c|d', 'c'] as $candidate) {
    $results[$candidate] = preg_match($pattern, $candidate) === 1;
}
echo json_encode(['pattern' => $pattern, 'results' => $results], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"pattern":"~^(?:a\\.b|c\\|d|e\\*)$~","results":{"a.b":true,"axb":false,"c|d":true,"c":false}}

The JSON output shows each backslash doubled, because JSON escapes backslashes. The real pattern contains single backslashes.

Edge cases

  • / is not special in regex syntax. If your pattern uses / as its delimiter, you must pass '/' as the second argument.
  • Whitespace is not on the escaped list. That matters under the x (extended) modifier, where unescaped spaces in a pattern are ignored. If you use that modifier, handle spaces yourself.
  • The hyphen is escaped, so quoted text is safe even if you place it inside a character class.
  • The function works on bytes, so multibyte text passes through unchanged apart from the listed ASCII characters.
  • An empty string returns an empty string.

Common mistakes

  1. Forgetting the delimiter argument. The default call leaves / alone, which can end the pattern early.
  2. Quoting the replacement string. The manual notes that preg_quote() is not meant for the replacement argument of preg_replace() and related functions. Replacement strings follow different rules.
  3. Quoting a whole pattern. Quote only the literal fragments. Quoting a pattern that already contains regex syntax turns that syntax into plain text.
  4. Using it as a general security filter. It only prepares literal text for a regex. It does not sanitize input for HTML, SQL or shell use.
  5. Joining before quoting. Quote each piece first, then combine them.
  • preg_match() tests a pattern against a subject string.
  • preg_replace() replaces matches; quote the search fragments, not the replacement.
  • preg_split() splits on a pattern, which can be built from quoted text.
  • str_contains() is simpler if you only need a plain substring check with no regex features.
  • escapeshellcmd() is listed by the manual as related, but it escapes shell metacharacters, not regex syntax.

Recap

Use preg_quote() whenever a runtime string should be matched as plain text inside a regex. Pass the pattern delimiter as the second argument, quote each fragment separately, and do not apply it to replacement strings. It returns a string and has no documented failure return.

Tested examples

Quoting a dollar sign and a slash delimiter
<?php
$keywords = '$40 for a g3/400';
echo preg_quote($keywords, '/'), "\n";
Output
\$40 for a g3\/400
Matching user text literally
<?php
$text = 'Price: 3.5+tax (approx)';
$needle = '3.5+tax';
$pattern = '/' . preg_quote($needle, '/') . '/';
echo json_encode([
    'pattern' => $pattern,
    'matched' => preg_match($pattern, $text) === 1,
    'unquotedMatched' => preg_match('/' . $needle . '/', $text) === 1,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"pattern":"/3\\.5\\+tax/","matched":true,"unquotedMatched":false}
Building an alternation with a custom delimiter
<?php
$words = ['a.b', 'c|d', 'e*'];
$parts = array_map(fn(string $word): string => preg_quote($word, '~'), $words);
$pattern = '~^(?:' . implode('|', $parts) . ')$~';
$results = [];
foreach (['a.b', 'axb', 'c|d', 'c'] as $candidate) {
    $results[$candidate] = preg_match($pattern, $candidate) === 1;
}
echo json_encode(['pattern' => $pattern, 'results' => $results], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"pattern":"~^(?:a\\.b|c\\|d|e\\*)$~","results":{"a.b":true,"axb":false,"c|d":true,"c":false}}