DevToys Pro

free web developer tools

Blog
Rate us:
Try browser extension:
← Back to Blog

Regex Newline Behavior in JavaScript

10 min read

Handling newlines in regular expressions is one of the most confusing aspects of pattern matching. The behavior of metacharacters like ., ^, and $ changes dramatically based on regex flags, and different programming languages implement newline handling differently.

This guide focuses on JavaScript's regex newline behavior, covering the DotAll (s) and Multiline (m) flags, common gotchas, and practical patterns for matching text that spans multiple lines.

The Dot (.) Metacharacter

By default, the dot (.) matches any character except newline characters (\n, \r).

Default Behavior

const text = "Hello\nWorld";
const regex = /Hello.World/;

console.log(regex.test(text));  // false

// The dot doesn't match the newline character

This default behavior is intentional. In most text processing scenarios, you want to match patterns within a single line, not across line boundaries.

Using the DotAll Flag (s)

The s flag (also called "dotAll" or "single-line") makes the dot match newline characters:

const text = "Hello\nWorld";
const regex = /Hello.World/s;  // Note the 's' flag

console.log(regex.test(text));  // true

// Now the dot matches the newline

Important: The s flag was introduced in ES2018. For older browsers, use [\s\S] or [^] instead of ..

Anchors: ^ and $

The anchors ^ (start) and $ (end) also behave differently with newlines.

Default Behavior

Without the multiline flag, ^ matches the start of the entire string, and $ matches the end:

const text = "Line 1\nLine 2\nLine 3";
const regex = /^Line 2/;

console.log(regex.test(text));  // false

// ^Line 2 looks for "Line 2" at the start of the string
// but the string starts with "Line 1"

Using the Multiline Flag (m)

The m flag changes ^ and $ to match the start and end of each line:

const text = "Line 1\nLine 2\nLine 3";
const regex = /^Line 2/m;  // Note the 'm' flag

console.log(regex.test(text));  // true

// Now ^Line 2 matches "Line 2" at the start of the second line

Practical Example: Extracting Lines

const log = `
[INFO] Application started
[ERROR] Database connection failed
[INFO] Retrying...
[ERROR] Timeout after 30s
`;

// Extract all ERROR lines
const errorRegex = /^\[ERROR\].*$/gm;
const errors = log.match(errorRegex);

console.log(errors);
// ["[ERROR] Database connection failed", "[ERROR] Timeout after 30s"]

Common Gotchas

Gotcha 1: Confusing s and m Flags

The naming is counterintuitive:

  • s flag = "single-line mode" but makes . match newlines (useful for multi-line matching)
  • m flag = "multi-line mode" and affects ^ and $ anchors

These flags are independent and can be combined:

const regex = /^.*$/sm;  // Both flags together

// s: dot matches newlines
// m: ^ and $ match line boundaries

Gotcha 2: Different Newline Characters

Different operating systems use different newline sequences:

  • Unix/Linux: \n (LF)
  • Windows: \r\n (CRLF)
  • Old Mac: \r (CR)

To match any newline variation:

// Match any newline sequence
const anyNewline = /\r?\n|\r/g;

// Split by any newline
const lines = text.split(/\r?\n|\r/);

// Alternative: use a more comprehensive pattern
const lines2 = text.split(/\r\n|\n|\r/);

Gotcha 3: Greedy Matching Across Lines

When using the s flag, greedy quantifiers can match too much:

const html = `
<div>First</div>
<div>Second</div>
<div>Third</div>
`;

// Trying to match individual <div> blocks
const greedy = /<div>.*<\/div>/s;
console.log(html.match(greedy)[0]);
// Matches from first <div> to LAST </div> - the entire string!

// Use non-greedy matching instead
const nonGreedy = /<div>.*?<\/div>/gs;
console.log(html.match(nonGreedy));
// ["<div>First</div>", "<div>Second</div>", "<div>Third</div>"]

Gotcha 4: Unicode Line Terminators

JavaScript's ^ and $ (with m flag) recognize several Unicode line terminators:

  • \n (U+000A) - Line Feed
  • \r (U+000D) - Carriage Return
  • \u2028 - Line Separator
  • \u2029 - Paragraph Separator
const text = "Line 1\u2028Line 2";  // Unicode line separator
const regex = /^Line 2/m;

console.log(regex.test(text));  // true

Practical Patterns

Pattern 1: Match Everything Between Markers

// Extract content between START and END markers (including newlines)
const text = `
Some text
START
Important content
spanning multiple
lines
END
More text
`;

const pattern = /START([\s\S]*?)END/;
const match = text.match(pattern);
console.log(match[1].trim());
// "Important content\nspanning multiple\nlines"

// Modern alternative with s flag
const pattern2 = /START(.*?)END/s;

Pattern 2: Match Lines Starting With Specific Text

const log = `
2026-01-11 10:00:00 INFO Server started
2026-01-11 10:00:15 ERROR Connection failed
2026-01-11 10:00:20 INFO Retrying
2026-01-11 10:00:25 ERROR Timeout
`;

// Get all ERROR lines
const errors = log.match(/^.*ERROR.*$/gm);
console.log(errors);

Pattern 3: Match Multi-line Code Blocks

const markdown = `
Some text

```javascript
function hello() {
  console.log("Hello");
}
```

More text
`;

// Extract code blocks
const codeBlocks = /```\w*\n([\s\S]*?)\n```/g;
const matches = [...markdown.matchAll(codeBlocks)];

matches.forEach(match => {
  console.log("Code block:", match[1]);
});

Pattern 4: Split Text by Blank Lines

const text = `
Paragraph 1
with multiple lines

Paragraph 2
also with
multiple lines

Paragraph 3
`;

// Split by one or more blank lines
const paragraphs = text.split(/\n\s*\n/);
console.log(paragraphs.map(p => p.trim()));

Cross-Language Differences

Be aware that newline handling differs across languages:

Python

import re

# Python's dot doesn't match newlines by default
pattern = re.compile(r'Hello.World')
# Use re.DOTALL flag (or re.S)
pattern_dotall = re.compile(r'Hello.World', re.DOTALL)

# Python's ^ and $ match line boundaries by default with re.MULTILINE
pattern_multiline = re.compile(r'^Line 2', re.MULTILINE)

Java

// Java dot doesn't match newlines by default
Pattern pattern = Pattern.compile("Hello.World");

// Use DOTALL flag
Pattern dotallPattern = Pattern.compile("Hello.World", Pattern.DOTALL);

// Use MULTILINE for ^ and $ to match line boundaries
Pattern multilinePattern = Pattern.compile("^Line 2", Pattern.MULTILINE);

Go

// Go's dot doesn't match newlines by default
pattern := regexp.MustCompile("Hello.World")

// Use (?s) inline flag for dotall
dotallPattern := regexp.MustCompile("(?s)Hello.World")

// Use (?m) for multiline
multilinePattern := regexp.MustCompile("(?m)^Line 2")

Testing Your Patterns

When working with complex newline patterns, use the DevToys Pro RegEx Tester to:

  • Test patterns with different flag combinations
  • Visualize matches across multiple lines
  • Debug greedy vs. non-greedy matching behavior
  • Experiment with different newline sequences

Best Practices

1. Be Explicit About Flags

// Bad: unclear what behavior you want
const pattern = /start.*end/;

// Good: explicit about newline handling
const pattern = /start.*?end/s;  // s flag: dot matches newlines
const pattern2 = /^start.*end$/m;  // m flag: ^ and $ per line

2. Use [\s\S] for Broad Compatibility

// If s flag support is uncertain, use [\s\S]
const pattern = /start([\s\S]*?)end/;

// [\s\S] means "any whitespace or non-whitespace" = any character

3. Normalize Line Endings When Possible

// Normalize to \n before processing
const normalized = text.replace(/\r\n|\r/g, '\n');

// Now you can use simpler patterns
const lines = normalized.split('\n');

4. Use Raw Strings in Test Data

// Use template literals for multi-line test strings
const testData = `
Line 1
Line 2
Line 3
`;

// Avoid string concatenation with \n
const avoid = "Line 1\n" + "Line 2\n" + "Line 3";

Conclusion

Understanding newline behavior in regular expressions is crucial for text processing tasks. Key takeaways:

  • The s flag makes . match newlines (dotAll mode)
  • The m flag makes ^ and $ match line boundaries (multiline mode)
  • These flags are independent and can be combined: /pattern/sm
  • Use [\s\S] or [^] for broad compatibility instead of .with s flag
  • Be aware of different newline sequences across operating systems
  • Use non-greedy quantifiers to avoid matching too much

For testing and debugging regex patterns, especially those involving newlines, use the RegEx Tester to visualize matches and experiment with different flag combinations.