/^/
MediumInternational5 min read

UK Postcode Regex

Validates a United Kingdom postcode across all standard outward-code formats (area + district) plus the fixed-format inward code, including the special GIR 0AA Girobank postcode.

#postcode#zip-code#uk#united-kingdom#address#validation#regex

Regex Pattern

^(GIR 0AA|((([A-Z][0-9]{1,2})|([A-Z][A-HJ-Y][0-9]{1,2})|([A-Z][0-9][A-Z])|([A-Z][A-HJ-Y][0-9][A-Z]?))\s?[0-9][A-Z]{2}))$

Default flags: i

Pattern Breakdown

Hover over a token to see what it does.

^(GIR 0AA|((([A-Z][0-9]{1,2})|([A-Z][A-HJ-Y][0-9]{1,2})|([A-Z][0-9][A-Z])|([A-Z][A-HJ-Y][0-9][A-Z]?))\s?[0-9][A-Z]{2}))$
TokenMeaning
^Anchors the match to the start of the string
GIR 0AASpecial-case literal match for the historic Girobank postcode
[A-Z][0-9]{1,2}Outward code format: one letter followed by one or two digits (e.g. M1, W1, N19)
[A-Z][A-HJ-Y][0-9]{1,2}Outward code format: two letters (second restricted to valid area letters) followed by one or two digits (e.g. SW1, EC1)
[A-Z][0-9][A-Z]Outward code format: one letter, one digit, one letter (e.g. W1A)
[A-Z][A-HJ-Y][0-9][A-Z]?Outward code format: two letters, one digit, and an optional trailing letter (e.g. EC1A)
\s?An optional single space between the outward code and the inward code
[0-9][A-Z]{2}Inward code: exactly one digit followed by exactly two letters (e.g. 1AA)
$Anchors the match to the end of the string

Detailed Explanation

What it does

This pattern validates that a string is a syntactically correct UK postcode, covering all recognized outward-code shapes (area letter(s) plus district number, with an optional trailing letter) combined with the fixed three-character inward code (digit + two letters), plus the special-cased 'GIR 0AA'.

Why it works

UK postcodes follow a small, well-documented set of outward-code shapes defined by Royal Mail: one letter + 1-2 digits, two letters + 1-2 digits, one letter + digit + letter, or two letters + digit + optional letter. The second letter in two-letter areas excludes I and Z (represented by [A-HJ-Y]) because those letters are not used in that position. All four shapes are joined in an alternation, followed by an optional space and the mandatory inward code, which is always exactly one digit and two letters. The i flag makes the whole match case-insensitive so lowercase postcodes are also accepted, and the GIR 0AA branch handles the one well-known postcode that doesn't fit the general pattern.

Common use cases

  • Validating postcode fields in UK shipping and checkout forms
  • Cleaning and standardizing address data imported from spreadsheets or third-party APIs
  • Client-side form validation before calling a postcode lookup or geocoding service
  • Filtering log or support-ticket text for strings that look like UK postcodes

Edge cases

  • Postcodes with and without the internal space, like 'SW1A 1AA' and 'SW1A1AA', are both accepted since the space is optional
  • Lowercase input, such as 'sw1a 1aa', is accepted because of the case-insensitive i flag
  • The historic 'GIR 0AA' Girobank postcode is explicitly matched even though it doesn't follow the standard area/district shape
  • Postcodes using the letter I or Z as the second area letter (not used by Royal Mail in that position) are correctly rejected by the [A-HJ-Y] restriction

Limitations

  • Validates format only, not whether the postcode actually exists; use a postcode lookup API (e.g. Royal Mail PAF or a third-party service) to confirm real-world validity
  • Does not normalize whitespace or casing; apply .toUpperCase() and normalize spacing in application code before storing or displaying
  • The exact set of valid single-letter and double-letter area codes changes rarely but is not fully hard-coded here; some genuinely unused area codes may still pass this format check
  • Does not handle British Forces Post Office (BFPO) codes, which use a different numbering scheme entirely

Interactive Tester

Edit the pattern or text below — matching runs live in your browser.

SW1A 1AA M1 1AE CR2 6XH

Test Cases

Editable — add your own inputs to see if they pass.

InputExpectedResult
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass

Language Variants

Production-ready examples in 12 languages.

const ukPostcodeRegex = /^(GIR 0AA|((([A-Z][0-9]{1,2})|([A-Z][A-HJ-Y][0-9]{1,2})|([A-Z][0-9][A-Z])|([A-Z][A-HJ-Y][0-9][A-Z]?))\s?[0-9][A-Z]{2}))$/i;
console.log(ukPostcodeRegex.test('SW1A 1AA')); // true

Common Mistakes

Using a simplistic pattern like ^[A-Z]{1,2}[0-9][A-Z0-9]? ?[0-9][A-Z]{2}$ that doesn't account for the restricted second-area-letter set, letting invalid letters like I or Z slip in

Fix: Restrict the second area letter with [A-HJ-Y] to match Royal Mail's actual allowed letters in that position

Forgetting the special-cased 'GIR 0AA' Girobank postcode, which doesn't fit any of the standard outward-code shapes

Fix: Add an explicit alternative for 'GIR 0AA' at the start of the pattern

Requiring the internal space between outward and inward codes, rejecting valid unspaced input like 'SW1A1AA' often produced by form autofill or database exports

Fix: Make the space optional with \s? rather than a mandatory literal space

Performance Notes

  • The outward-code alternation has four small, mutually distinguishable branches, so backtracking cost is minimal even for non-matching input
  • Anchoring with ^ and $ lets the engine reject clearly malformed postcodes (wrong length, invalid characters) very quickly
  • For bulk address validation, normalize casing and whitespace once during import rather than relying on the i flag and \s? on every subsequent comparison

Browser Compatibility

EngineSupportedNotes
ChromeYes
FirefoxYes
SafariYes
EdgeYes
Node.jsYes