Patterns & flags
Learn JavaScript regular expressions from zero: create patterns, choose flags, read matches, and use test, exec, matchAll, and replace safely.
- 01Make a regexWrite a regex literal or use
new RegExp(), and know when a string needs a second backslash. - 02Read what a search findsUse flags,
test(),exec(), and match objects, and know whylastIndexcan change the answer. - 03Use regexes with stringsFind, list, cut, and change text with
search,match,matchAll,split,replace, andreplaceAll.
Patterns for text
A string method such as includes() looks for exact text. It can answer: does this text contain cat? Sometimes you want to find a kind of text instead. For example:
- any hashtag, such as
#JSor#web, - anything shaped like a date, such as
2026-09-26, - the word
cat, written with capital or small letters.
A regular expression can find all of these. It is built into JavaScript, so it works in every modern browser.
A regular expression, or regex for short, is a pattern that describes the text you want to find. In JavaScript, a regex is an object of type RegExp. Flags are single letters added to a regex. Each flag turns on one option for the search.
i means "ignore capital and small letters".A recipe card lists what goes in the dish. It also has notes on how to cook it. You follow the card, then taste the food to check it.
- In real life: The list of what goes in
- In JavaScript: The pattern: the text to look for, such as
cat - In real life: Notes such as "bake slowly"
- In JavaScript: The flags, such as
i(ignore case) org(find every match) - In real life: You taste the food
- In JavaScript: You run
test()or a string method and look at the result
This lesson uses what you know about strings, string methods, template literals, and Unicode & strings. It uses a few small pattern pieces, such as [A-Za-z] (any one letter) and \d (any one digit). The lessons after this one explain each kind of piece in full.
Two ways to make a regex
STEP THROUGHThere are two ways to make a regex:
- A regex literal writes the pattern between two slashes, like
/cat/i. The flags go after the last slash. - The constructor,
new RegExp(), builds a regex from strings, likenew RegExp("cat", "i"). The first string is the pattern. The second string holds the flags.
Use a literal when you know the pattern while you write the code. Use new RegExp() when the pattern or the flags are stored in a variable.
The code below uses two tools. test(text) returns true if the pattern is found in the text, and false if not. .source gives the pattern as text, without the slashes or flags.
Step through two ways to make a regex. The literal is written in the code. new RegExp() builds one from the text in a variable.
script
const animal = "dog";const fromText = new RegExp(animal, "i"); console.log(literal.test("CAT"));console.log(fromText.test("hot dog"));console.log(fromText.source);new RegExp() reads the text in animal and builds the regex /dog/i from it. If animal held different text, you would get a different regex.Strings for new RegExp() have one extra rule. Inside a JavaScript string, a backslash \ starts a special code, such as \n for a line break. JavaScript reads the string first and uses up single backslashes. Only then does the regex see the text. So for \d+ (one or more digits), write two backslashes in the string:
- As a literal:
/\d+/ - With the constructor:
new RegExp("\\d+")
Text that comes from a user brings a second problem. In a pattern, some characters have a special meaning. A dot . means "any one character". To escape a character means to put a backslash in front of it, so it stands for itself. Then \. means a real dot.
new RegExp()const userText = "a.b";const unsafe = new RegExp(userText);const safe = new RegExp(userText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")); console.log(unsafe.test("axb"));console.log(safe.test("axb"));console.log(safe.test("a.b"));- Line 2 builds a regex straight from
"a.b". The dot means any character, so line 5 findsaxband printstrue. - Line 3 first puts a backslash in front of every special character. Now the dot means a real dot.
- So line 6 prints
falseforaxb, and line 7 printstruefora.b.
Text can come from a person typing, a search box, or a server. Escape it before you put it in a regex. Newer browsers have RegExp.escape(userText) for this. The Node 22 version that runs this site's tests doesn't have it yet, so the example uses a short replace() instead. The Character classes & sets lesson explains escaping and RegExp.escape in full.
Flags change the search
8 FLAGSWith a literal, flags go right after the last slash: /cat/gi. With the constructor, they go in the second string: new RegExp("cat", "gi"). Each flag turns one option on. The order of the letters doesn't matter.
Each row of this table is one flag: its letter, its name, and what it changes.
| Flag | Its name | What it changes |
|---|---|---|
g | Global | It finds every match, not just the first. It also makes the regex remember lastIndex, the index where the next search starts. |
i | Ignore case | It treats capital and small letters as the same, so cat, CAT, and Cat all match. |
m | Multiline | It lets ^ (start) and $ (end) match at the start and end of every line, not only of the whole text. |
s | Dot all | It lets . (any one character) match a line break too. |
u | Unicode | It reads each emoji and other special characters as one character, so ^.$ matches one emoji. |
v | Unicode sets | It is a newer form of u that also allows set rules inside [ ]. The next lesson covers them. |
y | Sticky | It only matches exactly at lastIndex, not anywhere after it. This helps when you read text one piece at a time. |
d | Indices | It adds an indices list with the [start, end] position of the match and of each group. |
console.log(/cat/g.test("cat cat"));console.log(/cat/i.test("CAT"));console.log(/^cat/m.test("dog\ncat"));console.log(/a.b/s.test("a\nb"));console.log(/^.$/u.test("😀"));console.log(/^[\p{Letter}&&[A-F]]$/v.test("D"));const sticky = /cat/y;sticky.lastIndex = 4;console.log(sticky.test("one cat"));const withIndices = /ID-(\d+)/d.exec("ID-42");console.log(withIndices.indices[1].join("-"));Every line prints true, except the last one, which prints 3-5. A few lines need a word:
- Line 3:
\nis a line break, so the text has two lines. Withm,^can match the start of the second line. - Line 5:
^.$means "exactly one character". Withu, one emoji counts as one character. - Line 6: the
vflag lets the pattern ask "is this a letter, and also one of A to F?" - Lines 7 to 9: the sticky regex starts at index 4, where
catbegins in"one cat". - Lines 10 and 11: with
d, JavaScript records where the digits42sit: from index 3 up to 5.
You don't need every flag yet. Later lessons use m with anchors (^ and $), v with character sets, and d with groups. For now, know what each letter turns on.
test(), exec(), and lastIndex
STATEFULA regex has two methods that search text:
test(text)answers yes or no. It returnstrueorfalse.exec(text)returns details about the first match: the matched text, where it starts, and more. If nothing matches, it returnsnull.
Both are simple, until the regex has the g or y flag. Then the regex keeps a number called lastIndex: the index where the next search starts. An index is a position in a string, counting from 0. Each search that finds a match moves lastIndex to just after the match. Step through the code and watch lastIndex change.
A regex with g or y remembers where it stopped. Step through the test() calls and watch lastIndex change.
script
const text = "cat cat"; console.log(word.test(text));console.log(word.lastIndex);console.log(word.test(text));console.log(word.lastIndex);console.log(word.test(text));console.log(word.lastIndex);word.lastIndex = 0;console.log(word.test(text));word.test(text) finds cat at indexes 0 to 2. The regex then keeps lastIndex = 3, so the next test starts at the space.The same thing happens with a different string. lastIndex belongs to the regex, not to the text.
g regex used on two stringsconst word = /cat/g; console.log(word.test("cat"));console.log(word.test("cat"));word.lastIndex = 0;console.log(word.test("cat"));- Line 3 finds
catand printstrue. NowlastIndexis 3. - Line 4 passes a new string, also
"cat". But the search starts at index 3, the end of the text. So it printsfalse, and the failed search setslastIndexback to 0. - Line 5 sets
lastIndexto 0 by hand, to make the fresh start clear. Line 6 printstrue.
This table helps you pick a method. Start from what you need in the first column.
| What you need | Method to try first | What to watch for |
|---|---|---|
| You need a yes or no answer. | regex.test(text) | It returns true or false. With g or y, a reused regex remembers lastIndex. |
| You need details about the first match. | regex.exec(text) | It returns one match object with index, input, groups, and sometimes indices. It returns null if nothing matches. |
| You need only the matched text. | text.match(regex) | With g, it returns an array of matched strings. Without g, it returns one match object. |
| You need details for every match. | text.matchAll(regex) | The regex must have g. It gives one match object per match, for a loop. |
| You need to change the text. | replace or replaceAll | Use special codes such as $1 in the replacement, or pass a function that builds the new text. |
The g flag is not bad. The problem is that one regex object keeps lastIndex between calls. To avoid surprises, do one of these:
- Set
lastIndex = 0before a new search. - Make a new regex inside the function that does the search.
- Use
match()orreplaceAll()with agregex. They always start from index 0.
What exec() gives back
When exec() finds a match, it returns a match object. A match object is an array with a few extra properties:
match[0]is the whole matched text.match[1],match[2], and so on hold the captured groups. A captured group is a part of the pattern inside round brackets( ). The text it matched is saved on its own.match.indexis the index where the match starts.match.inputis the whole string that was searched.match.groupsholds groups that have names.match.indicesholds start and end positions. It only exists with thedflag.
In the code below, (?<tag>...) is a named group: a captured group with a name, here tag. Inside it, #[A-Za-z]+ means a # and then one or more letters.
const pattern = /(?<tag>#[A-Za-z]+)/d;const text = "Use #JS today";const match = pattern.exec(text); console.log(match[0]);console.log(match.index);console.log(match.input === text);console.log(match.groups.tag);console.log(match.indices[0].join("-"));console.log(match.indices.groups.tag.join("-"));- Line 5 prints
#JS, the whole match. - Line 6 prints 4, because
#is at index 4 in"Use #JS today". - Line 7 prints
true:inputis the same string astext. - Line 8 prints the text of the group named
tag:#JS. - Lines 9 and 10 print
4-7. The match starts at index 4 and ends just before index 7.
Groups get their own lesson later. For now, know two things. groups gives names to parts of a match. indices tells you where each part sits in the text.
String methods that accept regexes
SORT ITSome string methods take a regex. Each one gives back a different kind of result:
search()gives the index of the first match, or-1if there is none.match()gives an array of matched strings withg, or one match object withoutg.matchAll()gives a match object for every match.replace()andreplaceAll()give a new string with the matches changed.split()cuts the string at each match and gives an array of the pieces.
const text = "Order 2026-09-26: chai, cake. Tags: #JS #web"; console.log(text.search(/chai/i));console.log(text.match(/#[A-Za-z]+/g).join(", "));console.log([...text.matchAll(/#[A-Za-z]+/g)].map((m) => `${m[0]}@${m.index}`).join(" | "));console.log("red, green; blue".split(/,\s*|;\s*/).join("|"));console.log(text.replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"));console.log("cat cat".replaceAll(/cat/g, "dog"));- Line 3 prints 18, the index where
chaistarts. - Line 4 prints both hashtags. The pattern
#[A-Za-z]+means a#and then one or more letters. - Line 5 prints each hashtag with its index.
- Line 6 cuts the text at a comma or a semicolon. In the pattern,
|means "or", and\s*takes any spaces after the mark. - Line 7 turns the date around (the next part explains
$3/$2/$1). - Line 8 changes every
cattodog.
search gives a number, match with g gives an array of strings, and replaceAll gives a new string.Special codes in the replacement text
The second argument of replace() is the replacement: the text that goes in place of each match. Inside it, a few codes have a special meaning:
$&puts in the whole match.$1puts in the first captured group,$2the second, and so on.$<name>puts in the group with that name.
You can also pass a function instead of a string. JavaScript calls the function once for each match. The function returns the new text.
console.log("cat".replace(/cat/, "[$&]"));console.log("Ada Lovelace".replace(/([A-Za-z]+) ([A-Za-z]+)/, "$2, $1"));console.log("#javascript".replace(/#(?<name>[a-z]+)/, "topic:$<name>"));console.log("fee fi".replace(/f[a-z]+/g, (match) => match.toUpperCase()));- Line 1 prints
[cat]:$&puts the whole match inside the square brackets. - Line 2 prints
Lovelace, Ada: the two groups swap places. - Line 3 prints
topic:javascript: the group namednameholdsjavascript. - Line 4 prints
FEE FI: the function turns each match into capital letters.
- Does this form field look like an email address?
- Find the first hashtag and where it starts.
- List every hashtag with its index.
Turn `2026-09-26` into `26/09/2026`.- Put square brackets around every search result.
Return just `["#JS", "#web"]` from a short note.
Put each task under the kind of result it needs. One pattern can be used with different methods.
Try patterns in a safe tester
PLAYGROUNDThe tester below shows a pattern, its flags, some text, and the matches together. Type the pattern without the slashes. To stay safe, the tester:
- builds the regex inside
try/catch, so a broken pattern shows an error message instead of breaking the page, - limits how long the pattern and the text can be,
- refuses a repeat inside a repeat, such as
(a+)+, because it can run very slowly, - shows the matches as plain text, never as HTML.
const pattern = new RegExp("#[A-Za-z]+", "gi");const text = "Learning #JavaScript and #regex";const matches = [...text.matchAll(pattern)].map((match) => ({ text: match[0], index: match.index,})); console.log(matches.map((match) => `${match.text}@${match.index}`).join(", "));#JavaScript9..20#regex25..31
2 matches. Indexes count from 0. Each end position is just after the last matched character.
Try #[A-Za-z]+ with the flags gi. Then try a broken pattern, such as a single (. If you add the d flag, each match in the list also shows its d positions.
Practical uses
Regexes show up in everyday web pages. You can use them to:
- give quick feedback while someone fills in a form,
- find hashtags or codes in text,
- change the format of dates that always have the same shape,
- highlight the words someone searched for.
A regex check in the browser only helps the user. It doesn't protect anything, because the user can change code in their own browser. The server must check the data again.
const email = "ada@example.com";const text = "Learning #JavaScript and #regex on 2026-09-26";const term = "js"; console.log(/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email));console.log(text.match(/#[A-Za-z]+/g).join(", "));console.log(text.replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"));console.log("JS regex basics".replace(new RegExp(term, "gi"), "[$&]"));- Line 5 checks that the email has the shape "text, @, text, dot, text". It prints
true. - Line 6 prints both hashtags.
- Line 7 changes the date to
26/09/2026. - Line 8 builds a regex from
termand wraps eachjs, in any case, in square brackets.
const phone = "555-123-4567";const postcode = "SW1A 1AA";const messy = " Asha likes tea ";const note = "New #JS lesson, #regex today"; console.log(/^\d{3}-\d{3}-\d{4}$/.test(phone));console.log(/^[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}$/i.test(postcode));console.log(messy.replace(/\s+/g, " ").trim());console.log(note.match(/#[A-Za-z0-9_]+/g).join(", ")); const cards = "<p>one</p><p>two</p>";console.log(cards.match(/<p>.*<\/p>/)[0]);console.log(cards.match(/<p>.*?<\/p>/)[0]);- Line 6 checks a phone number shape: three digits, a dash, three digits, a dash, four digits.
- Line 7 checks a UK postcode shape, with a space in the middle.
- Line 8 turns each run of spaces into one space.
\s+means one or more spaces. Thentrim()removes the spaces at both ends. - Line 9 collects every hashtag with
match()andg.
A character class such as [A-Za-z] matches one character from a set: here, any letter. A quantifier says how many: \d{3} means exactly three digits. Quantifiers are greedy at first: .* takes as many characters as it can. Adding ? makes it lazy: .*? takes as few as it can. The Character classes and Quantifiers lessons explain more.
Lines 12 and 13 show the difference. The greedy pattern on line 12 takes both paragraphs as one match: <p>one</p><p>two</p>. The lazy pattern on line 13 stops at the first </p>, so it gives <p>one</p>. Choose the narrowest pattern that fits your data. It is easier to read, and it is less likely to match text you didn't expect.
A regex works best for text with a simple shape. Some text has parts inside parts, such as full HTML, JavaScript, JSON with comments, or long URLs. For that, use a parser: a tool that reads the whole structure, not just a pattern. A later lesson on regex speed shows patterns that can freeze a page.
Common misconceptions
- "A regex understands what text means." It only follows the pattern. It can find text shaped like an email address. It can't tell whether that mailbox exists.
- "
gonly means find every match." It also makestest()andexec()movelastIndexon the regex object. - "
match()andmatchAll()give the same thing."match()gives different results with and withoutg.matchAll()always gives one match object per match, and it needsg. - "
new RegExp()uses the same backslashes as a literal." Its pattern is a string first, so each backslash usually needs a second backslash. - "A regex is always the shortest correct answer." Sometimes
startsWith(),includes(),URL,Intl, or a parser is clearer and safer.
These four methods look alike. Each row says what one of them gives back and when to use it.
| Method | What it gives back | When to use it |
|---|---|---|
test() | true or false. | Use it for a quick yes or no check. |
exec() | One match object, or null if nothing matches. | Use it to read index, groups, and indices of the next match. |
match() | With g, an array of matched strings. Without g, one match object. | Use it when you only need the matched text. |
matchAll() | One match object for every match, ready for a for...of loop. | Use it when you need details for every match. |
Practice exercises
5 EXERCISESRead the code. What does it print: true or false?
const answer = /complete/i.test("CompleteFrontend");
console.log(answer);The i flag ignores case, so the regex finds complete inside CompleteFrontend. The code prints true.
lastIndex step by stepType all six printed values, with commas between them.
const word = /js/g;
const text = "js js";
console.log(word.test(text));
console.log(word.lastIndex);
console.log(word.test(text));
console.log(word.lastIndex);
console.log(word.test(text));
console.log(word.lastIndex);The code prints true, 2, true, 5, false, 0. The first two searches find a match. The third search starts at the end, finds nothing, and sets lastIndex back to 0.
What exact text does the code print?
const text = "Ship #JS and #regex";
console.log([...text.matchAll(/#[A-Za-z]+/g)].map((match) => match[0] + "@" + match.index).join(" | "));#JS starts at index 5, and #regex starts at index 13. So the code prints #JS@5 | #regex@13.
Type the date the code prints.
const input = "2026-09-26";
console.log(input.replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"));The groups catch 2026, 09, and 26. The replacement $3/$2/$1 puts them in reverse order, so the code prints 26/09/2026.
What text does the code print?
const userText = "a.b";
const escaped = userText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(escaped, "g");
console.log("a.b axb".replace(pattern, "[$&]"));After escaping, the pattern means a real dot. So a.b gets square brackets, and axb stays as it is. The code prints [a.b] axb.
Check your understanding
8 QUESTIONSQuestion 1 of 8What is a regular expression?
Choose an answer to see the explanation.
Question 2 of 8What does this test with the
iflag print?Read the code, then predictconst pattern = /cat/i; console.log(pattern.test("CAT"));Choose an answer to see the explanation.
Question 3 of 8You build a digit pattern from a string. Which call is right?
Choose an answer to see the explanation.
Question 4 of 8Why does the third call print
false?Read the code, then predictconst word = /js/g; console.log(word.test("js js")); console.log(word.test("js js")); console.log(word.test("js js"));Choose an answer to see the explanation.
Question 5 of 8What does the
dflag add to a match object?Choose an answer to see the explanation.
Question 6 of 8What do the hashtag matches print?
Read the code, then predictconst text = "Tags: #JS #web"; console.log([...text.matchAll(/#[A-Za-z]+/g)].map((m) => m[0]).join(","));Choose an answer to see the explanation.
Question 7 of 8Which code in the replacement means the whole match?
Choose an answer to see the explanation.
Question 8 of 8What does the date replacement print?
Read the code, then predictconsole.log("2026-09-26".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"));Choose an answer to see the explanation.
Key takeaways
- A regex is a
RegExpobject: a pattern you use to search strings. - Use a literal for a pattern you know while writing the code. Use
new RegExp()when the pattern is in a variable. - Flags turn options on. With
gory, the regex also rememberslastIndex. exec()andmatchAll()give match objects. Thedflag adds start and end positions.- Pick the string method that gives the result you need: an index, a list, pieces, or new text.
Remember the one-liner.
A regex is a short text pattern plus flags. Use it when a pattern is clearer than checking the text by hand.
Up next: Character classes & sets.