By Docify

JavaScript RegExp vs PCRE: Flags and Lookbehind

• 6 min read

JavaScript RegExp differs from PCRE and other regex flavors. Key differences include flag support ( g, i, m, s, u, y), Unicode handling with the u flag, and lookbehind support added in ES2018. Test your patterns with the regex tester to see exactly how JavaScript interprets them.

A pattern that is legal in PHP preg_match or nginx is not automatically legal here. The live regex tester builds new RegExp(pattern, flags) from the constructor string (not a /…/ literal) and lists exec matches. This page starts with how that constructor lists matches, then the dialect gaps that actually change a match or throw, then anchors.

How the g flag lists matches

This pattern with the g flag matches three capitalized words in the sample string. yaml stays unmatched because it is lowercase. Without g, only the first match (Ada) is returned.

\b([A-Z][a-z]+)\b
Ada wrote Notes in Spain, not yaml.
0–3: "Ada" · 10–15: "Notes" · 19–24: "Spain"

Anchors: $ vs \\Z, and \\A is a letter

$ is the end of the string, not a final newline

MDN and ECMA-262 treat $ as “the next character is out of bounds,” unless m is set (then also before a line terminator). So /c$/ matches abc and misses abc\n. PCRE2 $ (default, without PCRE2_DOLLAR_ENDONLY) also matches before a newline at the end of the subject — the same idea as \Z. PCRE2 \z is only the absolute end. JavaScript has neither escape.

/c$/ on "abc\\n" → no match · /c$/m → "c"

\\A is IdentityEscape for A

PCRE2 \A is start-of-subject even when multiline is on. In ECMAScript without u, an unknown letter escape is an IdentityEscape: \A matches the character A, so it hits Abc and misses abc. With u, the constructor throws Invalid escape. The same rule makes \z mean z. PCRE2 \G and \K do not exist here.

Syntax PCRE2 accepts that this engine rejects

Possessive ++ and atomic groups throw

PCRE2 possessive quantifiers (*+, ++, ?+, {n,m}+) and atomic groups (?>…) consume without giving back. ECMAScript has greedy * / + and lazy *? / +? only. a++ throws Nothing to repeat. (?>a+)b throws Invalid group. Recursion (?R) and free-spacing (?x) are the same invalid-group class. There is no x flag on this page.

a++ → SyntaxError · (?R) → Invalid group · (?x)a b → Invalid group

Python (?P<name>) and POSIX [:digit:]

PCRE2 named groups accept Perl (?<name>…), (?'name'…), and Python (?P<name>…). JavaScript (ES2018) only implements (?<name>…); (?P<n>a) throws Invalid group. Named captures from a successful exec are match.groups. POSIX [[:digit:]] is not a digit class here — it is the characters :digit inside [], so it does not match 5.

Lookbehind and Unicode that JS does have

Variable-length (?<=a+) is allowed

ES2018 lookbehind can vary in length. (?<=a+)b matches the b in aaab. PCRE2 (since 10.34) allows variable-length lookbehind only when each top-level branch has a maximum; the default cap is 255 characters (pcre2_set_max_varlookbehind, same as Perl). (?<=X+) is rejected there because it has no upper bound. Older PCRE required a fixed length per branch. This page is not a PCRE2 compiler and does not enforce that 255-character cap.

(?<=a+)b on aaab → "b" at index 3

Unicode properties need the u flag

Unicode property escapes (\p{L}) require u. Without it, \p{L} does not match É. \u{41} is a code-point escape only with u; \x41 is a two-digit hex byte in either mode. The live checkboxes are gimsuy only. The d (indices) and v (unicodeSets / class set notation) flags are not offered. Test output of . plus g on 😀 (U+1F600) is two matches at 0–1 and 1–2 (U+D83D then U+DE00). The same output with u is one match at 0–2. Test output of . plus g on NFC é is 0–1. NFD of e + U+0301 produces 0–1 and 1–2, with or without u — u is not grapheme clustering. NFC normalization e + U+0301 → é (U+00E9) as normalized.txt is /unicode-normalize/. Empty patterns are rejected. Copy writes index-end: match lines. This tool does not do replacement, linting, or ReDoS auditing.

Zero-length g matches advance lastIndex

A successful exec sets lastIndex to the end of the match. An empty match therefore leaves lastIndex on the same index. The live tester then increments lastIndex by 1 so the next exec does not hang. (?=a) with g on aba is two empty matches at indexes 0 and 2. Without g, only the first empty match at 0 is kept. The highlight skips length-0 spans, so those lookaheads appear in the match list but are not marked in the string. This is the tester loop, not a PCRE2 dialect gap and not matchAll.

(?=a) with g on aba → 0–0: "" · 2–2: ""

Test an ECMAScript pattern

Constructor string, flags gimsuy. Not PCRE2. Nothing is uploaded.

Use Regex Tester →

FAQ

What is the difference between JavaScript RegExp and PCRE?
JavaScript uses ECMAScript regex. PCRE (Perl Compatible Regular Expressions) has more features like lookbehind, recursion, and atomic groups.
Does JavaScript support lookbehind?
Yes, since ES2018. Use (?<=...) for positive lookbehind and (?<!...) for negative lookbehind.
Can I use PCRE patterns in JavaScript?
Not directly. Some PCRE features like recursive patterns and possessive quantifiers are not supported.
Does this tool test regex patterns?
No. This article compares JavaScript RegExp and PCRE (flags, Unicode, lookbehind). It does not run matches. Test a pattern in the browser with the live regex tester at /regex-tester/, which builds new RegExp(pattern, flags) and lists exec matches. Flags are gimsuy. It is not PCRE2, Python re, or POSIX regex. Nothing is uploaded.

Related