Practical patterns: validation, logs and URLs

Realistic patterns for the values people actually validate and extract, and an honest account of where each one stops being enough.

Email, URLs and identifiers

// a deliberately loose email check: reject the obvious, do not claim RFC conformance
const EMAIL = /^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/;
EMAIL.test('[email protected]');   // true
EMAIL.test('a@@example.com');      // false
EMAIL.test('a [email protected]');     // false

// an absolute HTTP URL, without trying to parse the query string
const URL_RE = /^https?:\/\/[^\s/?#]+(?:[/?#][^\s]*)?$/i;
URL_RE.test('https://example.com/a?b=1');   // true
URL_RE.test('ftp://example.com');           // false

// a slug, useful for routing and for generating file names
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
SLUG.test('regex-practical-patterns');      // true
  • Deliverability is decided by sending mail and reading bounces, not by a pattern. A strict email regex rejects valid addresses far more often than it catches typos.
  • new URL(value) in JavaScript and urllib.parse in Python understand URLs, including relative ones and percent-encoding. Use regex for the shape and a parser for the parts.
  • Tighten with length limits in code: a 400-character slug passes the pattern above.

Dates, IP addresses and CSV fields

// ISO date, shape only; validate the day count in code
const ISO = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
ISO.exec('2026-09-18').slice(1);       // [ '2026', '09', '18' ]
ISO.test('2026-13-01');                // false, month 13

// IPv4: the octet group must forbid values above 255
const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
IPV4.test('192.168.0.1');              // true
IPV4.test('256.1.1.1');                // false

// one CSV field, quoted form first so the alternation order matters
const FIELD = /(?:^|,)(?:"([^"]*)"|([^",]*))/g;
ValueRegex is good forUse a parser when
ISO datethe shape and the month rangeyou care that 2026-02-30 is not a real date
IPv4 / IPv6IPv4 dotted quads; IPv6 is impracticalthe address must be validated or normalised
CSVone simple field or a quick splitfields contain commas, quotes or newlines — use a CSV library
Credit carddigit grouping and lengthyou need a Luhn check and a BIN lookup
Phone numbera light shape checkyou need country rules — use a phone-number library

Alternation is ordered, so (?:25[0-5]|2[0-4]\d|1?\d?\d) must list the strict branches first. Putting 1?\d?\d at the front would let 2 match, then fail the following \. and force backtracking on every octet.

Extracting from log lines

import re

LINE = re.compile(r'''
    \A
    (?P<ip>\S+) \s+
    (?P<ident>\S+) \s+
    (?P<user>\S+) \s+
    \[(?P<time>[^\]]+)\] \s+
    "(?P<method>[A-Z]+) \s+ (?P<path>\S+) \s+ (?P<proto>[^"]+)" \s+
    (?P<status>\d{3}) \s+
    (?P<size>\d+|-)
    \s*\z
''', re.VERBOSE)

line = '10.0.0.7 - alice [18/Sep/2026:10:00:00 +0000] "GET /learn/ajax/ HTTP/1.1" 200 5123'
row = LINE.match(line).groupdict()
row['status'], row['path']          # ('200', '/learn/ajax/')
💡
Anchor log patterns at both ends and make every field explicit. A pattern that silently accepts a truncated line produces empty groups rather than an error, and empty groups in an analytics pipeline look exactly like real zero values.

FAQ

Should validation happen on the client, the server, or both?
Always on the server, where it is a security boundary. Client-side checks are a convenience for the user. Share the intent, not necessarily the pattern, because the two engines and the two versions of the data differ.
Why does my IPv4 pattern accept 999.1.1.1?
The octet group is too permissive. Each octet needs the ordering 25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d and must be anchored between the dots, or a larger number is matched partially.

Regex in Python When not to use regex

Last refreshed 2026-09-18.