A regular expression that looks right can match too much, too little, or something you never thought of. The reliable way to write one is to test it against real text after every change, rather than writing the whole pattern and hoping.
Testing as you type
- Open Regex Tester.
- Type the pattern between the slashes and the flags in the small box next to it.
- Paste some text below. The tester counts the matches and lists each one (up to 200) as you type. An empty match shows as ∅, which usually means part of your pattern is optional when it shouldn't be.
It uses the JavaScript regex engine of your browser — the same one your web front end will use — and the text never leaves the page.
The flags
| Flag | Effect |
|---|---|
| g | find every match, not just the first (the tester always adds it) |
| i | ignore upper and lower case |
| m | ^ and $ match at the start and end of every line, not only the whole text |
| s | the dot also matches line breaks |
| u | Unicode mode: needed for \p{…} classes and to treat emoji as one character |
| y | sticky: match only at the exact current position |
Build it up in steps
Say you want the dates in "Due 14.03.2026, paid 2026-03-02".
- Start with the simplest thing that matches one case:
\d{4}-\d{2}-\d{2}finds 2026-03-02. - Add the other format as an alternative:
\d{4}-\d{2}-\d{2}|\d{2}\.\d{2}\.\d{4}. Note the escaped dots — an unescaped dot means "any character", so 14x03x2026 would also match. - Add word boundaries so you don't match inside longer numbers:
\b(?:\d{4}-\d{2}-\d{2}|\d{2}\.\d{2}\.\d{4})\b.
Check the match list after each step. Then paste text that should not match — a phone number, an invoice number — and make sure the count stays where it should.
Mistakes that catch everyone
- Greedy quantifiers. In
<b>one</b> and <b>two</b>, the pattern<b>.*</b>matches the whole line, from the first<b>to the last</b>. The lazy version<b>.*?</b>stops at the first possible end and gives you two matches. - Forgetting to escape. The characters
. * + ? ( ) [ ] { } ^ $ | \ /have special meanings. To match a literal full stop or bracket, put a backslash in front:\.or\(. - Missing anchors. Validating an input with
\d+accepts "abc123def", because the pattern matches somewhere inside it. Use^\d+$to require the whole string. - Letters with accents.
[a-zA-Z]does not match ă, ș, é or ł — on "Școală Łódź" it finds only "coal" and "d". In Unicode mode,\p{L}matches any letter in any alphabet: with theuflag,\p{L}+returns both words whole. - Catastrophic backtracking. Nested quantifiers such as
(a+)+$can make the engine try an exponential number of combinations on input that almost matches. If the page freezes for a moment on a long test string, simplify the nesting.
JavaScript is not PCRE
Patterns copied from a PHP, Python or grep example usually work, but not always. JavaScript supports lookbehind, named groups (?<name>…) and Unicode property classes, but not possessive quantifiers, atomic groups or recursive patterns. If a pattern from elsewhere reports "Invalid regular expression" here, one of those is the usual suspect.
When not to use a regex
To replace one fixed piece of text with another, Find and Replace is simpler and treats special characters literally. To pull values out of structured data, use the right parser: JSON Formatter for JSON, URL Parser for links, CSV to JSON for spreadsheets. A regex is the right tool when the pattern genuinely varies.
