Quick answer
This UK postcode regex covers all six outward formats (A9, A99, A9A, AA9, AA99 and AA9A, such as M1, B33, W1A, CR2, DN55 and EC1A), an optional space, and the inward code of a digit plus two letters. It applies Royal Mail's letter rules, so Q, V and X never start a postcode and C, I, K, M, O and V never appear in the last two letters. Use the i flag to accept lowercase.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the UK postcode regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the UK postcode regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the UK postcode regex work?
Read it left to right. Each piece below matches one part of the input:
GIR ?0AA|A special case: GIR 0AA, a non-geographic postcode kept for historical reasons.[A-PR-UWYZ]The area's first letter. Q, V and X are never used here.\d{1,2}Formats A9 and A99, such as M1 and B33.|[A-HK-Y]\d{1,2}Formats AA9 and AA99: the second letter is never I, J or Z (CR2, DN55).|\d[A-HJKPSTUW]Format A9A, as in W1A.|[A-HK-Y]\d[ABEHMNPRV-Y]Format AA9A, as in EC1A and SW1A.?An optional space between the outward and inward codes.\d[ABD-HJLNP-UW-Z]{2}The inward code: a digit and two letters, never C, I, K, M, O or V.
Flags: i (ignore case).
Which strings does the UK postcode regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
SW1A 1AA | Yes | Buckingham Palace: format AA9A. |
EC1A 1BB | Yes | City of London: AA9A. |
W1A 1AA | Yes | BBC Broadcasting House: A9A. |
M1 1AE | Yes | Manchester: A9. |
B33 8TH | Yes | Birmingham: A99. |
CR2 6XH | Yes | Croydon: AA9. |
DN55 1PT | Yes | Doncaster: AA99. |
sw1a1aa | Yes | Lowercase and no space, thanks to the i flag and the optional space. |
QA1 1AA | No | Q is never the first letter. |
SW1A 1CA | No | C is never used in the inward code. |
SW1A-1AA | No | A hyphen instead of a space. |
SW1A 1AAA | No | Three letters in the inward code. |
12345 | No | A US-style number. |
How is a UK postcode structured?
Every postcode has two halves. The outward code (SW1A) identifies the area and district and is used to sort mail to a delivery office; the inward code (1AA) narrows it down to a street or part of one. The inward code is always a digit plus two letters, which is why the space can be omitted and still be inserted reliably: it always comes before the last three characters.
const formatPostcode = (s) => {
const compact = s.replace(/\s+/g, "").toUpperCase();
return compact.slice(0, -3) + " " + compact.slice(-3);
};
formatPostcode("sw1a1aa"); // "SW1A 1AA"Store postcodes in this canonical uppercase, single-space form so lookups and deduplication work.
Does this regex cover British Forces and overseas territories?
It covers the standard Royal Mail formats used across the UK, which also fit the Isle of Man, Jersey and Guernsey (IM, JE, GY areas). Special cases need additional alternatives:
- British Forces Post Office:
BFPOfollowed by a number, such asBFPO 801. - Overseas territories with UK-style codes, such as
ASCN 1ZZ(Ascension) andTDCU 1ZZ(Tristan da Cunha), use four-letter outward codes.
A postcode in the right format can still be unassigned. For address capture, offer a postcode lookup backed by Royal Mail's Postcode Address File so people pick a real address rather than typing one.
How do I use the UK postcode regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^(?:GIR ?0AA|[A-PR-UWYZ](?:\d{1,2}|[A-HK-Y]\d{1,2}|\d[A-HJKPSTUW]|[A-HK-Y]\d[ABEHMNPRV-Y]) ?\d[ABD-HJLNP-UW-Z]{2})$/i;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("SW1A 1AA"));Python
import re
pattern = re.compile(r"^(?:GIR ?0AA|[A-PR-UWYZ](?:[0-9]{1,2}|[A-HK-Y][0-9]{1,2}|[0-9][A-HJKPSTUW]|[A-HK-Y][0-9][ABEHMNPRV-Y]) ?[0-9][ABD-HJLNP-UW-Z]{2})\Z", re.IGNORECASE)
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("SW1A 1AA"))- \d and \w are written as [0-9] and [A-Za-z0-9_] because in Python they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?i)^(?:GIR ?0AA|[A-PR-UWYZ](?:\d{1,2}|[A-HK-Y]\d{1,2}|\d[A-HJKPSTUW]|[A-HK-Y]\d[ABEHMNPRV-Y]) ?\d[ABD-HJLNP-UW-Z]{2})$`)
fmt.Println(re.MatchString("SW1A 1AA"))
}C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^(?:GIR ?0AA|[A-PR-UWYZ](?:[0-9]{1,2}|[A-HK-Y][0-9]{1,2}|[0-9][A-HJKPSTUW]|[A-HK-Y][0-9][ABEHMNPRV-Y]) ?[0-9][ABD-HJLNP-UW-Z]{2})\z", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("SW1A 1AA"));- \d and \w are written as [0-9] and [A-Za-z0-9_] because in .NET they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^(?:GIR ?0AA|[A-PR-UWYZ](?:\d{1,2}|[A-HK-Y]\d{1,2}|\d[A-HJKPSTUW]|[A-HK-Y]\d[ABEHMNPRV-Y]) ?\d[ABD-HJLNP-UW-Z]{2})$/iD';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('SW1A 1AA', $pattern));- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for US ZIP Codes: Five-digit ZIP codes with an optional ZIP+4 extension, like 90210-1234.
- Regex for Indian PIN Codes: Six-digit Indian postal codes that never start with 0, with an optional space.
- Regex for Alphanumeric Characters: Letters A–Z and digits only, with a Unicode variant for every script.
- Regex for Phone Numbers (E.164 Format): International numbers in E.164: a plus, a country code and up to 15 digits in total.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.