Download Cheat sheet PDF 12 pages · syntax, editors, patterns, Unicode, performance, debugging
Concepts May 1, 2026

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) or re.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 →