RegEx Modifiers
What Are Modifiers? In simple terms: Modifiers are single letters placed after the closing delimiter of a pattern that change how the pattern is matched — for example, ignoring upper/lower case, or le
What Are Modifiers?
In simple terms: Modifiers are single letters placed after the closing delimiter of a pattern that change how the pattern is matched — for example, ignoring upper/lower case, or letting ^ and $ work on each line instead of the whole text.
Technically: Modifiers (also called flags) set PCRE options for the entire pattern. They are written directly after the closing delimiter, and several can be combined in any order: /pattern/imu.
preg_match('/^php$/i', 'PHP'); // i = case-insensitive
Overview
| Modifier | Name | Effect |
|---|---|---|
i | Case-insensitive | Letters match regardless of case |
m | Multiline | ^ and $ match at the start and end of each line |
s | Dotall | . also matches newline characters |
x | Extended | Whitespace in the pattern is ignored, and # starts a comment |
u | Unicode (UTF-8) | Pattern and subject are treated as UTF-8 |
D | Dollar end only | $ matches only at the very end, not before a final newline |
U | Ungreedy | Quantifiers are lazy by default (and ? makes them greedy) |
A | Anchored | The match must start at the beginning of the subject (or the given offset) |
n | No auto-capture (PHP 8.2+) | Plain (...) groups do not capture; named groups still do |
J | Duplicate names | Allows several groups with the same name |
Removed: The
emodifier (evaluate replacement as PHP code) was removed in PHP 7.0 because it caused security vulnerabilities. Usepreg_replace_callback()instead.
i — Case-Insensitive
<?php
var_dump(preg_match('/^yes$/', 'YES'));
var_dump(preg_match('/^yes$/i', 'YES'));
Output
int(0)
int(1)
Without i, yes and YES are different. With i, the comparison ignores case. For non-ASCII letters (such as É and é), combine it with u.
m — Multiline
By default, ^ matches only at the start of the whole string and $ only at its end. With m, they also match at the start and end of every line:
<?php
$list = "Shopping:\n- Milk\n- Bread\n- Eggs";
echo preg_match_all('/^- (.+)$/', $list, $withoutM) . "\n";
echo preg_match_all('/^- (.+)$/m', $list, $withM) . "\n";
print_r($withM[1]);
Output
0
3
Array
(
[0] => Milk
[1] => Bread
[2] => Eggs
)
Without m, the pattern requires the whole string to start with - , which it does not. With m, each list line is matched separately. This is essential for processing multi-line text such as logs, configuration files, or pasted lists.
s — Dot Matches Newlines
Normally, . matches any character except a newline. With s, it matches newlines too:
<?php
$html = "<p>Line one\nLine two</p>";
var_dump(preg_match('/<p>(.*)<\/p>/', $html));
preg_match('/<p>(.*)<\/p>/s', $html, $m);
var_dump($m[1]);
Output
int(0)
string(17) "Line one
Line two"
Without s, .* cannot cross the line break, so there is no match. With s, the content spanning both lines is captured.
m vs s
These two are often confused:
| Modifier | Changes |
|---|---|
m | The meaning of ^ and $ (line by line) |
s | The meaning of . (includes newlines) |
They are independent and can be combined (/.../ms).
x — Extended Mode (Readable Patterns)
With x, whitespace inside the pattern is ignored and # starts a comment. Long patterns become readable and self-documenting:
<?php
$pattern = '/
^
(?<state>[A-Z]{2}) # state code, e.g. TN
(?<district>\d{2}) # district number
(?<series>[A-Z]{1,2}) # series letters
(?<number>\d{4}) # vehicle number
$
/x';
preg_match($pattern, 'TN09AB1234', $m);
echo "{$m['state']} / {$m['district']} / {$m['series']} / {$m['number']}\n";
Output
TN / 09 / AB / 1234
To match a literal space or # in extended mode, escape it (0 , #) or put it in a character class ([ ], [#]`).
u — UTF-8 Mode
Without u, PCRE works on bytes. Characters outside ASCII — accented letters, Indian scripts, ₹, emoji — take several bytes in UTF-8, so . and quantifiers count bytes instead of characters:
<?php
var_dump(preg_match('/^.{4}$/', 'Café'));
var_dump(preg_match('/^.{4}$/u', 'Café'));
Output
int(0)
int(1)
Café has 4 characters but 5 bytes (é uses two). Only with u does .{4} count characters.
What u Enables
.,\X, and quantifiers operate on characters, not bytes.- Unicode properties such as
\p{L}(any letter) and\p{Tamil}become usable. - Offsets reported by functions are still byte offsets.
Invalid UTF-8 With u
<?php
$result = preg_match('/\w+/u', "abc\xFF");
var_dump($result);
echo preg_last_error_msg() . "\n";
Output
bool(false)
Malformed UTF-8 characters, possibly incorrectly encoded
With u, the subject must be valid UTF-8; otherwise, the function fails and returns false. This is useful: it reveals encoding problems instead of silently producing wrong results. Always add u when processing user input that may contain non-ASCII text.
D — Dollar Matches Only at the Very End
<?php
var_dump(preg_match('/^\d+$/', "12345\n"));
var_dump(preg_match('/^\d+$/D', "12345\n"));
Output
int(1)
int(0)
By default, $ also matches before a newline at the end of the string, so "12345\n" passes a digits-only check. D closes that gap. (\z achieves the same effect within the pattern.) D is ignored if m is also set.
U — Ungreedy by Default
<?php
preg_match('/<.+>/', '<b>bold</b>', $greedy);
preg_match('/<.+>/U', '<b>bold</b>', $ungreedy);
echo $greedy[0] . "\n";
echo $ungreedy[0] . "\n";
Output
<b>bold</b>
<b>
U inverts greediness for the whole pattern, so .+ behaves like .+?. Because this reverses the usual meaning of ?, patterns with U are confusing to readers. Writing lazy quantifiers explicitly (.+?) is usually clearer.
n — No Automatic Capturing (PHP 8.2+)
<?php
preg_match('/(\d{4})-(\d{2})/', '2026-10', $normal);
preg_match('/(\d{4})-(?<month>\d{2})/n', '2026-10', $noCapture);
print_r($normal);
print_r($noCapture);
Output
Array
(
[0] => 2026-10
[1] => 2026
[2] => 10
)
Array
(
[0] => 2026-10
[month] => 10
[1] => 10
)
With n, plain parentheses only group; only named groups capture. This keeps the matches array small when parentheses are used only for grouping and alternation.
Inline Modifiers
Modifiers can be switched on for part of a pattern with (?flags) or (?flags:...):
<?php
$pattern = '/^Status: (?i:active|inactive)$/';
var_dump(preg_match($pattern, 'Status: ACTIVE'));
var_dump(preg_match($pattern, 'STATUS: active'));
Output
int(1)
int(0)
Only the group (?i:active|inactive) is case-insensitive. The label Status: must still match exactly.
Practical Example: Parsing a Simple Config Text
<?php
$text = <<<CONFIG
# Application settings
APP_NAME = GoCourse
app_debug= true
DB_PORT =3306
CONFIG;
$pattern = '/
^ \s*
([a-z_]+) # key
\s* = \s*
(.*?) # value (lazy)
\s* $
/mix';
preg_match_all($pattern, $text, $matches, PREG_SET_ORDER);
$config = [];
foreach ($matches as [, $key, $value]) {
$config[strtoupper($key)] = $value;
}
print_r($config);
Output
Array
(
[APP_NAME] => GoCourse
[APP_DEBUG] => true
[DB_PORT] => 3306
)
How the Modifiers Work Together
| Modifier | Role in This Pattern |
|---|---|
m | ^ and $ apply to each line, so every setting is matched separately |
i | [a-z_] also accepts uppercase keys such as APP_NAME |
x | The pattern is spread over several lines with comments |
- The comment line does not match, because
#is not in[a-z_]. The empty line has no=and is skipped as well. (.*?)\s*$captures the value lazily, so trailing spaces are not included.PREG_SET_ORDERgroups each match's parts together, which allows destructuring in theforeach.
(For real configuration files, use parse_ini_file() or a .env library; the example demonstrates modifiers.)
Common Mistakes
Confusing m and s
Effect: Line anchors or dots do not behave as expected.
Fix: m changes ^/$; s changes ..
Forgetting u for Non-ASCII Text
Effect: Wrong lengths, broken characters, and \p{...} classes not working as expected.
Fix: Add u whenever text may contain non-ASCII characters.
Literal Spaces in x Mode
Effect: Spaces in the pattern are ignored, so /a b/x matches ab.
Fix: Escape spaces (0 ) or use [ ]or\s`.
Using U and Then ?
Effect: .+? becomes greedy under U.
Fix: Avoid U; write lazy quantifiers explicitly.
Expecting $ to Reject a Trailing Newline
Effect: Values like "123\n" pass validation.
Fix: Use D or \z.
Best Practices
- Use
ufor all user-facing text. - Use
iinstead of writing[Aa][Bb]alternatives. - Use
mfor line-by-line processing andswhen.must cross lines. - Use
xwith comments for complex patterns. - Prefer explicit lazy quantifiers and
\zoverUand implicit end-of-string behavior.
Official reference: https\://www.php.net/manual/en/reference.pcre.pattern.modifiers.php