TL;DR: A regex prompt generator should produce three artefacts, not one: the flavour you are targeting, the strings that must and must not match, and only then the pattern. Ask for test cases first and the pattern second. Every regex on this page was executed in five engines before it was published.
What is a regex prompt generator, and what should it produce?
A regex prompt generator is a prompt, or a set of them, that turns a description of the text you want to find into a working pattern. The useful ones produce three artefacts. The useless ones produce one.
The three are: the flavour, because (?<=\$)\d+ is valid Python and a compile error in Go; the test strings, both the ones that must match and the ones that must not; and the pattern. Most pages that rank for this query skip the first two. That is exactly why regex is the canonical task where AI gets it eighty percent right, and why the missing twenty percent is the part that silently corrupts a column in your database.
This page gives you twenty prompts across four jobs: explaining a pattern you inherited, building a new one, debugging one that fails, and porting one between flavours. It also gives you eighteen patterns that were run against positive and negative inputs in Python 3.14, Node 22, PCRE2 10.47, Google's RE2 and BSD POSIX ERE before publication. Where a pattern failed in an engine, the failure is written down rather than sanded off.
Why do AI models get regex almost right?
Because regex rewards two different abilities and models only have one of them in abundance.
The first is recall of common shapes. A model has seen a million date patterns and can produce a competent-looking one instantly. The second is the fiddly arithmetic of ranges, anchors, escaping and precedence, where being off by one character silently changes the answer rather than raising an error. Models are excellent at the first and mediocre at the second, so the characteristic failure is a pattern that reads correctly, compiles, and is wrong for one class of input.
That confidence is not a bug you can prompt away. It is the same mechanism behind every other fluent wrong answer, and the sibling post on why ChatGPT makes things up explains where it comes from. What you can do is refuse to accept a pattern that has not been run.
Which flavour are you actually targeting?
This is the question most regex articles skip, and it decides whether the pattern compiles at all.
Five families cover almost everything you will touch: Python's re, JavaScript, PCRE2 (Perl, PHP, R, pcre2grep, and most editor find-and-replace boxes), Go's regexp package, and POSIX ERE (grep -E, awk, sed -E). The differences below were produced by running each construct in each engine, not by reading a cheat sheet.
| Feature | Python re | JavaScript | PCRE2 | Go RE2 | POSIX ERE |
|---|---|---|---|---|---|
| Lookahead and lookbehind | Yes | Yes | Yes | Parse error | Parse error |
| Backreference \1 | Yes | Yes | Yes | Parse error | Yes (extension) |
| Named group (?P<x>) | Yes | SyntaxError | Yes | Yes | Parse error |
| Named group (?<x>) | Parse error | Yes | Yes | Yes | Parse error |
| Atomic group (?>...) | Yes | SyntaxError | Yes | Parse error | Parse error |
| \d matches non-ASCII digits | Yes | No | No | No | Locale-dependent |
| [[:blank:]] inside a class | Silently wrong | Silently wrong | Yes | Yes | Yes |
| \t inside a class | Yes | Yes | Yes | Yes | Literal t |
| Linear-time guarantee | No | No | No | Yes | No |
Three rows in that table deserve more than a cell.
RE2 has no lookaround at all, and it never will. Go's own documentation explains why: the implementation "is guaranteed to run in time linear in the size of the input", a property it notes is "not guaranteed by most open source implementations of regular expressions" (pkg.go.dev, read August 27, 2026). Lookaround, backreferences and atomic groups all need a backtracking engine, so RE2's syntax reference marks every one of them with a blunt (NOT SUPPORTED) (github.com/google/re2, read August 27, 2026). Running foo(?=bar) through RE2 returns invalid perl operator: (?=. There is no flag that turns it on.
Named groups have two incompatible spellings and no universal one. Python accepts only (?P<name>...) and rejects (?<name>...) with unknown extension. JavaScript is the exact mirror: it accepts only (?<name>...) and throws a SyntaxError on the Python spelling, ending in Invalid group. PCRE2 and RE2 accept both. So a named-group pattern is portable between Python and Go, and between JavaScript and Go, but not between Python and JavaScript without an edit.
The worst row is the silent one. Writing [[:blank:]] in Python or JavaScript does not raise an error. Python parses it as a character class containing the literal characters [, :, b, l, a, n, k followed by a literal ], so the pattern matches the two-character string a] and does not match a tab. Node behaves identically. Node with the u flag finally raises a SyntaxError ending in Lone quantifier brackets, which means the safest thing you can do in JavaScript is add a flag that turns this class of mistake into a crash.
POSIX is the mirror image. [ \t]+$ matched a real tab in all four other engines and did not match one in grep -E, because POSIX leaves the meaning of a backslash before an ordinary character undefined. The specification is explicit about it:
When not inside a bracket expression, the interpretation of an ordinary
character preceded by an unescaped <backslash> is undefined
That is from IEEE Std 1003.1-2024, section 9.3.2 (pubs.opengroup.org, read August 27, 2026). Undefined does not mean forbidden, which is why implementations diverge: BSD grep -E happily accepted a \1 backreference that POSIX defines only for basic regular expressions and never for extended ones, and its \d matched Arabic-Indic digits under a UTF-8 locale while matching nothing under LC_ALL=C. The same pattern, the same binary, a different environment variable, a different answer.
How do you get a model to explain a pattern you inherited?
By forbidding prose summaries and demanding a token-by-token table plus worked examples. A one-paragraph explanation of a regex is almost always a restatement of what the author hoped it did.
Prompt 1: token-by-token breakdown
Explain this regular expression token by token.
FLAVOUR: <python re | javascript | pcre2 | go re2 | posix ere>
PATTERN: <paste the pattern here>
Output a table with one row per token or group. Columns: token, what it
matches, why it is there. Do not summarise in prose before the table.
After the table, give one sentence naming what the pattern is for.
Prompt 2: worked examples in both directions
For the pattern above, list:
- 6 strings that MATCH, each with the exact substring captured
- 6 strings that DO NOT match, each with the first character position
where matching fails and why
Choose strings that are realistic for this domain, not aaa and bbb.
Include at least two near-misses that differ from a match by one character.
Prompt 3: the edge case the author was worried about
Regexes usually carry scars. Looking at the pattern above, identify any
part that exists to handle a specific edge case rather than the happy path.
For each one, give the input that would break the pattern if that part
were removed. If you cannot find one, say so rather than inventing it.
Prompt 4: verbose rewrite
Rewrite the pattern above in verbose/extended form with a comment on every
line, preserving behaviour exactly.
If the target flavour does not support a verbose flag, say so and produce
a commented multi-line string that is concatenated in code instead.
That last clause matters. RE2 has no verbose flag: compiling (?x) returns invalid perl operator: (?x, so in Go you build the commented pattern in source and join it. Python has re.X; PCRE2 has /x; JavaScript has neither.
Prompt 5: what does this silently accept?
List every input this pattern accepts that a reasonable reader would
expect it to reject. Be specific and give literal strings. Cover at least:
unanchored matching, empty or whitespace-only input, values that are
syntactically valid but semantically impossible, and non-ASCII input.
Prompt 5 is the one that pays. Run it against the ISO date pattern below and the honest answer comes back immediately: ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ accepts 2026-02-30, 2026-04-31 and 0000-01-01. It was designed to, because calendar arithmetic is not a job for a regular expression. Knowing that is the difference between a validated field and a corrupted one.
How do you build a pattern without shipping a wrong one?
By reversing the order everybody uses. Ask for the test cases first, the pattern second, and the verification third, in the same turn.
This ordering is the whole trick. A model asked for a pattern optimises for something that looks like a pattern. A model asked first for the strings that must match commits to a specification, and then has to produce something that satisfies a spec it just wrote down. It is the same discipline as writing the schema before the output in a JSON prompt, applied to a smaller and sharper artefact.
Prompt 6: the core build prompt
I need a regular expression. Work in this exact order and do not skip step 1.
STEP 1. Before writing any pattern, list:
- 8 strings that MUST match
- 8 strings that MUST NOT match, including near-misses
- any input you are unsure about, flagged as AMBIGUOUS with a question
STEP 2. Wait for nothing. Write the pattern.
FLAVOUR: <python re | javascript | pcre2 | go re2 | posix ere>
Anchored: <yes | no>
STEP 3. Walk every string from step 1 against the pattern and state
match or no-match for each. If any disagrees with step 1,
fix the pattern and redo step 3. Show the corrected run.
WHAT I WANT TO MATCH: <describe it in plain language>
Prompt 7: negative space first
Before we discuss what should match, list 12 strings that are close to
what I want but must be REJECTED, and group them by the reason for
rejection. Then propose the pattern.
WHAT I WANT TO MATCH: <describe it>
Prompt 8: anchoring interrogation
Produce two versions of the pattern: one anchored with ^ and $, one
unanchored. For each, give one input where the two behave differently.
Then tell me which one I should use given that I will call it with
<full-string validation | a search over a log line | a find-and-replace>.
Prompt 9: portable subset
Write the pattern using only constructs that compile in ALL of:
Python re, JavaScript, PCRE2, Go RE2 and POSIX ERE.
That means: no lookaround, no backreferences, no named groups, no \d \w \s
shorthand, no atomic or possessive quantifiers, no inline flags.
Use explicit character classes and POSIX classes only.
If my requirement cannot be met inside that subset, say so and explain
which single constraint forces you outside it.
Prompt 10: talk me out of it
Here is what I want to match: <describe it>
Before giving me a regex, tell me whether a regex is the right tool.
If a parser, a split, a date library or a URL library would be more
correct, say so and name the specific library function.
Only give me the pattern if regex is genuinely the better answer.
Prompt 10 is not decoration. For CSV with quoted fields, HTML, nested structures and calendar validity, the correct answer is a parser, and a model will tell you so if you give it permission to.
Prompt 11: the character class audit
For the pattern you just wrote, produce a table with one row per character
class or shorthand used. Columns: the construct, the exact set of
characters it matches in the target flavour, and whether that set changes
under a Unicode or locale setting. Flag every construct whose meaning is
not identical across Python, JavaScript, PCRE2 and RE2.
Prompt 12: the harness
Write a runnable test harness for the pattern above in <language>.
It must contain the pattern, the positive cases, the negative cases, and
exit non-zero if any case disagrees. No test framework, no dependencies,
one file I can paste into a terminal.
How do you debug a pattern that fails on one input?
By supplying the failing input and explicitly forbidding a rewrite. Left alone, a model will replace your battle-scarred pattern with a clean new one that loses every edge case the original was carrying, which is the regex version of the problem described in why AI rewrites code you did not ask it to touch.
Prompt 13: minimal fix, not a rewrite
This pattern is mostly right and I do not want it rewritten.
FLAVOUR: <flavour>
PATTERN: <paste>
FAILING INPUT: <paste the exact string>
EXPECTED: <match | no match>
ACTUAL: <match | no match>
Give me the SMALLEST edit that fixes this case. State the edit as a diff of
the pattern. Then list every previously-working input that your edit could
plausibly break, and how you checked. Do not restructure the pattern.
Prompt 14: bisect the pattern
Split the pattern above into its top-level components. For my failing
input, run each component in order against the input and tell me the exact
component where matching first fails, and the character position reached.
Report it as a table before proposing any fix.
Prompt 15: the greedy check
For each quantifier in this pattern, tell me whether it is greedy, lazy or
possessive, what it currently consumes on my failing input, and what it
would consume if I flipped it. Then tell me whether flipping any of them
fixes my case, and whether the target flavour supports lazy and possessive
quantifiers at all.
Prompt 16: the delta between two patterns
PATTERN A: <the old one>
PATTERN B: <the model's replacement>
FLAVOUR: <flavour>
List every input class where A and B behave differently, with a literal
example string for each. Do not tell me B is better. Tell me what B loses.
How do you port a pattern to another flavour?
By asking for a compatibility report before the translation, so unsupported constructs are named rather than quietly dropped.
Prompt 17: translate with a report
Port this pattern from <source flavour> to <target flavour>.
PATTERN: <paste>
First, list every construct in the pattern that the target flavour does not
support, with the exact error the target engine would produce.
Second, for each one, give the workaround or state plainly that there is none.
Third, produce the ported pattern.
Fourth, note any input where the ported pattern differs in behaviour.
Prompt 18: strip to RE2-safe
Rewrite this pattern so it compiles under Go's regexp package (RE2 syntax).
Remove all lookahead, lookbehind, backreferences, atomic groups and
possessive quantifiers. Where a construct cannot be expressed in RE2, do NOT
approximate it silently: tell me which part of the job has to move into Go
code, and show that Go code.
That last instruction exists because the honest answer is often "you cannot". A doubled-word detector such as \b(\w+)\s+\1\b matched the the cat in Python, JavaScript and PCRE2 and returned invalid escape sequence: \1 in RE2. There is no RE2 rewrite. You capture words and compare them in Go.
Prompt 19: the backtracking audit
Audit this pattern for catastrophic backtracking.
PATTERN: <paste>
FLAVOUR: <flavour>
Identify any nested quantifier, any alternation where branches can match the
same text, and any unanchored pattern with a leading .* . For each, give a
crafted input of about 30 characters that would trigger exponential
behaviour, and the rewrite that removes the risk.
State whether the target engine has a linear-time guarantee.
Prompt 20: the review pass
Review this pattern the way a senior engineer reviews a pull request.
PATTERN: <paste> FLAVOUR: <flavour> PURPOSE: <one line>
Cover, in this order: correctness on the stated purpose, anchoring,
escaping, character-class portability, backtracking risk, readability, and
whether a non-regex approach would be clearer. End with a verdict of
APPROVE or REQUEST CHANGES and the single most important change.
That review shape is deliberately the same one used in the code review prompt generator, for the same reason: a verdict forces a position, and a position is checkable.
The test-string box, and how to run it in sixty seconds
Every prompt above is worthless if you do not execute the result. Here is the harness used to test this article, reduced to something you can paste. Replace the pattern and the two lists.
import re, sys
PATTERN = r"^([01][0-9]|2[0-3]):[0-5][0-9]$"
MUST_MATCH = ["00:00", "09:05", "13:45", "23:59"]
MUST_NOT_MATCH = ["24:00", "7:30", "23:60", "1345", "23:59:59"]
rx, bad = re.compile(PATTERN), 0
for s in MUST_MATCH:
if not rx.search(s):
print(f"FAIL expected match: {s!r}"); bad += 1
for s in MUST_NOT_MATCH:
if rx.search(s):
print(f"FAIL expected no match: {s!r}"); bad += 1
print(f"{'FAILED' if bad else 'PASSED'} {bad} failure(s)")
sys.exit(1 if bad else 0)
The JavaScript version, for when the pattern will run in a browser and the Python result cannot be trusted to transfer:
const PATTERN = /^([01][0-9]|2[0-3]):[0-5][0-9]$/;
const MUST_MATCH = ["00:00", "09:05", "13:45", "23:59"];
const MUST_NOT_MATCH = ["24:00", "7:30", "23:60", "1345", "23:59:59"];
let bad = 0;
for (const s of MUST_MATCH)
if (!PATTERN.test(s)) { console.log(`FAIL expected match: ${s}`); bad++; }
for (const s of MUST_NOT_MATCH)
if (PATTERN.test(s)) { console.log(`FAIL expected no match: ${s}`); bad++; }
console.log(bad ? `FAILED ${bad}` : "PASSED");
process.exit(bad ? 1 : 0);
Two rules make this worth the sixty seconds. Write the lists before you see the pattern, so you are testing your specification rather than the model's. And run the harness in the language that will run the pattern in production, because as the flavour table shows, passing in Python proves nothing about Go.
Why is the email regex a trap?
Because the goal most people state is not the goal they want, and the standard everyone cites was never designed for validation.
Full RFC 5322 compliance is achievable and almost always wrong. The HTML specification says so about its own definition, in unusually direct language:
This requirement is a willful violation of RFC 5322, which defines a syntax for email addresses that is simultaneously too strict (before the "@" character), too vague (after the "@" character), and too lax (allowing comments, whitespace characters, and quoted strings in manners unfamiliar to most users) to be of practical use here.
That is from the WHATWG HTML Living Standard's definition of the email input type (html.spec.whatwg.org, read August 27, 2026). The specification then publishes the regex browsers actually use, which is the single best default for a form field because it is what your users' browsers already enforce:
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
Tested here in Python, JavaScript, PCRE2 and RE2, it accepted nafiul@prompt-architects.com, a.b+tag@sub.example.co.uk and the bare x@y, and rejected no-at-sign.com, two@@example.com, user@-example.com and user@example-.com. Note that it accepts x@y, which has no dot at all, and that this is correct: intranet hostnames are real addresses.
If you want something you can read, the two-part check is ^[^@\s]+@[^@\s]+\.[^@\s]+$ in the Perl family, or ^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$ in POSIX and RE2. Both passed their tests. Neither tells you the address exists, which is the actual question, and only a confirmation email answers it.
What is catastrophic backtracking, and which engines are immune?
It is exponential slowdown caused by nested quantifiers, and exactly one of the five flavours here is structurally immune to it.
The classic demonstration is ^(a+)+$ against a string of a characters ending in one character that cannot match. The inner and outer quantifiers can divide the input in exponentially many ways, and a backtracking engine tries them all before conceding. Measured on this machine against 30 a characters followed by an exclamation mark:
| Feature | Python 3.14 re | Node 22 (V8) | Google RE2 |
|---|---|---|---|
| 20 a characters, then ! | 0.031 s | 0.030 s | 0.00007 s |
| 26 a characters, then ! | 1.91 s | 0.34 s | 0.00009 s |
| 30 a characters, then ! | 31.1 s | 5.66 s | 0.00018 s |
| Growth per extra character | roughly doubles | roughly doubles | flat |
Add four more characters to the Python case and you are past ten minutes. That is a denial-of-service vector any time a pattern touches user input. RE2 does not have the problem because it does not have the mechanism, which is the entire trade it makes: no lookaround, no backreferences, and in exchange a runtime that cannot be made to explode.
Two cheap defences work in every flavour. Anchor the pattern, and avoid a quantifier applied to a group that already contains one. Rewriting the same test as ^a+$ dropped Python from 31 seconds to 0.0002 seconds. In Python 3.11 and later, and in PCRE2, an atomic group also fixes it: ^(?>a+)+$ returned immediately. Neither JavaScript nor RE2 supports atomic groups, so there the answer is the rewrite.
Every pattern on this page, and the flavour it was tested in
Eighteen patterns, 71 pattern-and-engine combinations, 481 individual match assertions, all passing at time of writing. Where a pattern needed a different spelling per flavour, both spellings are listed.
| Pattern | Purpose | Verified in |
|---|---|---|
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ | ISO date, syntax only | Python, JS, PCRE2, RE2 |
^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$ | Same, portable spelling | all five |
^([01][0-9]|2[0-3]):[0-5][0-9]$ | 24-hour time | all five |
^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ | Hex colour | all five |
^[a-z0-9]+(-[a-z0-9]+)*$ | URL slug, no leading or doubled dash | all five |
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ | UUID version 4 only | all five |
[ \t]+$ | Trailing whitespace | Python, JS, PCRE2, RE2 |
[[:blank:]]+$ | Same, POSIX spelling | POSIX, PCRE2, RE2 |
^[^@\s]+@[^@\s]+\.[^@\s]+$ | Pragmatic email | Python, JS, PCRE2, RE2 |
^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$ | Same, POSIX spelling | POSIX, PCRE2, RE2 |
| WHATWG email regex (above) | Browser-equivalent email | Python, JS, PCRE2, RE2 |
| semver.org named-group regex | Semantic version | Python, PCRE2, RE2 |
| semver.org numbered-group regex | Semantic version | Python, JS, PCRE2, RE2 |
^(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+(?P<level>DEBUG|INFO|WARN|ERROR)\s+(?P<msg>.+)$ | Log line, Python spelling | Python, PCRE2, RE2 |
^(?<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+(?<level>DEBUG|INFO|WARN|ERROR)\s+(?<msg>.+)$ | Log line, JS spelling | JS, PCRE2, RE2 |
\b(\w+)\s+\1\b | Doubled word | Python, JS, PCRE2 |
(?<=\$)\d+(?:\.\d{2})? | Price after a dollar sign, lookbehind | Python, JS, PCRE2 |
\$([0-9]+(\.[0-9]{2})?) | Same, capture group instead | all five |
The semver patterns are semver.org's own, published in answer to whether a suggested regular expression exists; the site notes there are two, "One with named groups for those systems that support them" and one with numbered groups for everything else (semver.org, read August 27, 2026). Both were run here rather than trusted.
Stop rewriting prompts. Start shipping.
Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 5.0★ on the Chrome Web Store.
Create An AccountWhat a prompt cannot do for you
It cannot run the pattern. That is the honest boundary of this page and of every tool in the category, including ours.
Prompt Architects generates, enhances and stores the prompt, not the regex. There is no regex engine inside it, no test-string box, and no execution step, and a page that implied otherwise would be selling you the thing that does not exist. What it is genuinely good for is the part that decays: the twenty prompts above are long, and long prompts get shortened by hand until they stop working. Saving Prompt 6 with the flavour as a variable, so that the test-cases-first ordering survives contact with a busy Tuesday, is a real problem worth solving. Our FAQ page publishes five prompt enhancements per day on the free plan, forever, which is enough to try that without a card.
For the running, use a real engine. python3 -c, node -e, pcre2grep, the Go playground, or a scratch file. Sixty seconds. If you want the broader habit rather than just this one, the post on red-teaming your own prompt before you trust the output generalises it.
The pattern the model gives you is a hypothesis. The test strings are the experiment. Nothing on this page, and no amount of prompt engineering, replaces the moment you run it and find out.