Regex match multiline — the dotall flag explained
Why . doesn't match newlines by default — and the three ways to make it.
The short answer
By default, . matches any character except a newline. To make it cross newlines, enable the dotall flag:
- JavaScript:
/pattern/s(ES2018+) - Python:
re.compile(pattern, re.DOTALL)orre.S - PCRE:
/pattern/s - Java:
Pattern.compile(pattern, Pattern.DOTALL) - Go:
(?s)pattern
The default behavior
Pattern: start.*end
Input: "start\nmiddle\nend"
Default: NO MATCH (. doesn't cross the newlines)
With s flag: matches the whole thing
Why the default is this way
Many regex applications work line-by-line: log parsing, grep, sed. For those workflows, you usually want . to stay within the line — matching across newlines would scoop up unrelated content. So the default is "stay on the line."
For multi-line content (HTML blocks, code, paragraph extraction), you need to opt in to multiline behavior.
Three ways to match across newlines
1. The dotall flag (cleanest)
/start.*end/s JS, PCRE
re.compile(r"start.*end", re.DOTALL) Python
This makes . match any character including newlines.
2. [\s\S] instead of . (no flag needed)
/start[\s\S]*end/ universal — works in any flavor without flags
\s matches whitespace (including newlines). \S matches non-whitespace. Together, [\s\S] matches absolutely anything.
Use this when you can't set a flag (e.g., in a regex string from configuration) or for cross-flavor compatibility.
3. Inline modifier (?s)
(?s)start.*end PCRE, Python, Java, Go (NOT JavaScript)
The (?s) at the start enables dotall mode for the rest of the pattern. Useful when constructing patterns programmatically.
Don't confuse with multiline mode
There's a separate flag called "multiline" (m) that's NOT the same:
- Dotall (
s): makes.match newlines - Multiline (
m): makes^and$match start/end of EACH LINE instead of just the whole input
These are independent. You can have both, either, or neither.
Default: /^foo/ matches only if input starts with "foo"
With m: /^foo/m matches at start of any line in the input
Default: /a.b/ a, any char (not newline), b
With s: /a.b/s a, any char (including newline), b
JavaScript example
const html = `<p>First
paragraph</p><p>Second</p>`;
// Without /s, fails on first paragraph (has newline inside)
html.match(/<p>(.+?)<\/p>/g)
// Only finds: ["<p>Second</p>"]
// With /s, finds both
html.match(/<p>(.+?)<\/p>/gs)
// Finds: ["<p>First\nparagraph</p>", "<p>Second</p>"]
Python example
import re
text = """Line 1
Line 2
Line 3"""
re.findall(r"Line 1.*Line 3", text)
# [] — fails without DOTALL
re.findall(r"Line 1.*Line 3", text, re.DOTALL)
# ["Line 1\nLine 2\nLine 3"]
Common confusion
"Multiline mode" doesn't do what people expect
Beginners often enable m hoping . will match newlines. It won't. m only affects ^ and $. For newline-crossing ., you need s (or [\s\S]).
JavaScript before ES2018
Older browsers/Node versions don't have the s flag. Use [\s\S] instead — it's universal.
The takeaway
By default, . doesn't cross newlines. For multi-line content, enable dotall mode with the s flag, or use [\s\S] as a portable alternative.
Don't confuse dotall (s) with multiline (m) — they're different flags with different effects. Most "regex doesn't match across lines" bugs are fixed by s, not m.
Related reading
Try this pattern in the explainer
Paste any regex into the live explainer and see what each token means, with example matches in real time.
Open the regex explainer →