# Postcode Regex: Tested Patterns for UK, US, CA, DE, NL, IE

> A postcode regex should check one country's pattern on input that has been trimmed, uppercased and given its standard spacing. For the UK that means six outward-code shapes plus GIR 0AA; for the US five digits with an optional hyphen and four more. A regex confirms the shape only, never that the postcode exists.

- URL: https://address-valid.com/validation/postcode-regex/
- Language: en
- Updated: 2026-10-19
- Publisher: AddressValid (OnlineBuilders LLC, https://onlinebuilders.biz)

The UK Home Office published a postcode regular expression for visa sponsors who upload data in bulk. The PDF is still online. Its pattern accepts `123 SW1A 1AA` and `GIR 0AAxyz` as valid postcodes. We tested it. The main bug is two misplaced anchors, and it is a good reminder that a regex for postcodes needs testing like any other code.

Below are patterns for six countries, all run against valid and invalid samples, and a normalising function that makes them usable in a real form.

## The bug in the government regex

Here is the expression from the [Home Office bulk data transfer document](https://assets.publishing.service.gov.uk/media/5a7f3ff4ed915d74e33f5438/Bulk_Data_Transfer_-_additional_validation_valid_from_12_November_2015.pdf), section 3.1, shortened:

```
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|( ... )) [0-9][A-Za-z]{2})$
```

The structure is `^(GIR part)|(normal part)$`. Alternation binds weaker than anchors, so this reads as "starts with GIR 0AA" or "ends with a normal postcode". Neither branch is anchored at both ends. Run it in Node:

```
> gov.test("SW1A 1AA")      true
> gov.test("123 SW1A 1AA")  true
> gov.test("GIR 0AAxyz")    true
> gov.test("SW1A1AA")       false
> gov.test("W1A 4ZZ")       false
```

Two failures in opposite directions: garbage is accepted, and a correctly typed postcode without the space is rejected. The last line is a second bug. `W1A 4ZZ` is one of Royal Mail's own examples, but one character class in the published pattern reads `[AZa-z]` instead of `[A-Za-z]`, so an uppercase W cannot start that shape. The fix is one pair of brackets, `^(?:GIR 0AA|...)$`, the missing hyphen in that character class, and a normalising step so missing spaces stop being an error.

## Normalise first, then match

Most postcode validation problems come from treating raw input as the thing to test. People type `sw1a1aa`, add a trailing space, or paste with a non-breaking space. The strict regex then rejects valid input, and developers loosen the regex until it accepts nonsense.

The better order: clean the input into the country's canonical form, test the strict pattern, and store the canonical form.

```
const normalise = (cc, raw) => {
  let z = raw.trim().toUpperCase().replace(/\s+/g, '');
  if (cc === 'GB' || cc === 'CA') z = z.replace(/^(.+)(.{3})$/, '$1 $2');
  if (cc === 'NL') z = z.replace(/^(\d{4})/, '$1 ');
  if (cc === 'IE') z = z.replace(/^(.{3})/, '$1 ');
  return z;
};
```

UK and Canadian postcodes always have a three-character second part, so the space goes before the last three characters. Dutch postcodes put it after the four digits, Irish Eircodes after the first three characters.

## Six tested patterns

**Postcode patterns by country (input normalised first)**

| | Pattern | Valid example |
|---|---|---|
| United Kingdom | `^(GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|[A-HK-Y][0-9]{1,2}|[0-9][A-HJKS-UW]|[A-HK-Y][0-9][ABEHMNPRV-Y]) [0-9][ABD-HJLNP-UW-Z]{2})$` | `EC1A 1HQ` |
| United States | `^\d{5}(-\d{4})?$` | `20500-0003` |
| Canada | `^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z] \d[ABCEGHJ-NPRSTV-Z]\d$` | `K1A 0B1` |
| Germany | `^(?!00)\d{5}$` | `01067` |
| Netherlands | `^[1-9]\d{3} (?!SA|SD|SS)[A-Z]{2}$` | `1012 AB` |
| Ireland (Eircode) | `^([AC-FHKNPRTV-Y]\d{2}|D6W) [0-9AC-FHKNPRTV-Y]{4}$` | `D02 X285` |

All patterns tested in Node.js against the samples in the table below.

Results from the test run, after normalising:

| Country | Input | Normalised | Result |
|---|---|---|---|
| UK | `sw1a1aa` | `SW1A 1AA` | valid |
| UK | `123 SW1A 1AA` | `123SW1A 1AA` | rejected |
| US | `2134` | `2134` | rejected (lost leading zero) |
| Canada | `k1a0b1` | `K1A 0B1` | valid |
| Canada | `W1A 0B1` | `W1A 0B1` | rejected (W never first) |
| Germany | `00123` | `00123` | rejected |
| Netherlands | `1012 SS` | `1012 SS` | rejected |
| Ireland | `d02x285` | `D02 X285` | valid |

A few notes on the less obvious parts. The UK pattern encodes the letter restrictions in the inward code (C, I, K, M, O, V never appear in the last two characters) and in the first positions of the outward code. It accepts all six shapes from Royal Mail's entry in the UPU catalogue: `M2 5BQ`, `M34 4AB`, `CR0 2YR`, `DN16 9AA`, `W1A 4ZZ` and `EC1A 1HQ`. Canada never uses the letters D, F, I, O, Q, U, and W and Z do not appear as the first letter. Dutch postcodes skip the letter pairs SA, SD and SS. German postcodes have five digits, and none of them starts with `00`.

The US pattern looks too simple, and it is. Any five digits pass, including ZIP Codes that do not exist. The more common bug is upstream: a spreadsheet or a numeric database column turns `02134` into `2134`. Store postcodes as text.

## Where regex stops

Even a UK postcode that matches every rule above may not exist. Royal Mail's PAF held 1,810,763 postcodes in August 2026, while the pattern accepts millions of combinations. The same applies everywhere. Treat the regex as a cheap first filter for typing errors, and nothing more.

For a form, combine three layers. Run the regex on blur to catch typos while the user is still there. Check that postcode and city plausibly belong together. Then, for orders that ship, match the full address against postal data through an API. Our [overview of address validation APIs](https://address-valid.com/validation/address-validation-api/) compares the options, and the [three levels of address verification](https://address-valid.com/validation/verify-an-address/) explains what each layer can promise.

Do not block the form when a country has no pattern in your list. Some countries have no postcodes at all; a required postcode field with a regex fallback of "at least one character" makes people type `00000`, which then pollutes your data.

## Using the patterns in HTML and on the server

The HTML `pattern` attribute is tempting, because it gives you browser validation for free. Two details matter. First, the browser anchors the pattern for you: the specification wraps it as `^(?:` your pattern `)$`, so the government-style anchoring bug cannot happen there, and adding your own `^` and `$` is harmless. Second, current browsers compile the attribute with the `v` flag, which is stricter about unescaped characters inside character classes. Test the attribute in a real browser rather than assuming that a pattern copied from a JavaScript file behaves the same.

The bigger limitation is that `pattern` runs on the raw value. It cannot uppercase or insert a space first. So either accept a looser pattern in the attribute and normalise on submit, or skip `pattern` and validate in your own script on blur.

Whatever runs in the browser, run the same check again on the server. Client-side validation is a convenience for the user. It does not protect your database from API calls, imports or a disabled script, and the canonical form you store should come from server code you control.

## Testing your own pattern

Whatever you use, test it with a fixed list before it goes live. Include at least:

**Test cases every postcode regex needs**

- [ ] One valid example of each shape the country uses
- [ ] Lowercase and missing-space versions (should pass after normalising)
- [ ] Text before and after a valid code (must fail)
- [ ] Leading zero cases, such as US `02134` and German `01067`
- [ ] Letters that the country never uses in a given position
- [ ] An empty string and a single space

If you only need to check individual addresses, you do not need any of this code: our [address format checker](https://address-valid.com/address-checker/) applies country patterns for 15 countries in the browser and prints the formatted address. For background on the UK layout itself, see the [UK address format guide](https://address-valid.com/address-formats/uk-address-format/). Teams that validate millions of records rather than one form field usually move the checks into their data pipeline, which is work a studio like [OnlineBuilders](https://onlinebuilders.biz/solutions/data-analytics) does.

Copy the test list above into your test suite today and run it against the pattern you already have in production.

## FAQ

### Is there one regex for all postcodes worldwide?

No useful one. Formats range from four digits to alphanumeric codes with spaces, and some countries have no postcodes at all. Pick the pattern by the selected country, and skip the check for countries you have no pattern for.

### Should the regex accept lowercase and missing spaces?

Accept them from the user, then normalise before testing. Uppercase the input, strip spaces and reinsert the space where the country uses one. That keeps the regex strict and the form friendly.

### Why does my UK postcode regex accept text before the postcode?

Probably because of operator precedence. In a pattern like ^A|B$ the start anchor only applies to A and the end anchor only to B. Wrap the alternatives in a group: ^(?:A|B)$.

### Can a regex tell me if a postcode is real?

No. It can reject impossible shapes, but a well-formed postcode may not be in use. Only a lookup against postal data, such as Royal Mail's PAF for the UK, confirms that a postcode exists.

Photo: Ewan-M, CC BY-SA 2.0 (https://commons.wikimedia.org/wiki/File:EC1_-_2428353431.jpg)