On this page
Postal code formats are not interchangeable, and a validator that assumes five digits will reject most of the world. The differences are structural, not cosmetic: some systems are numeric with leading zeros, some mix letters and digits, some place the code before the city, and two countries in this site's fixture set have no postal codes at all.
This reference collects the patterns that matter for a developer building an address form, with regexes you can test directly. It also draws the line the site itself draws: postal validation here is a structure check for every country, plus an existence check for US ZIPs against a 40,979-row dataset. Nothing in this guide implies that a matching code is assigned or deliverable.
Why postal codes are not a numeric field
The United States uses five digits or ZIP+4, which tempts developers to model postal codes as integers. That model breaks immediately in Canada, the United Kingdom, the Netherlands, Ireland, Malta, and Argentina, where letters are part of the code. It also breaks quietly in Germany, France, Spain, Italy, and Poland, where leading zeros are meaningful and an integer column drops them.
A second class of bugs comes from position. Most Western countries put the postal code after the city, but Germany, France, the Netherlands, and Japan place it before the city, and Japan puts the whole code at the very start of the address. If your form renders a fixed template, it will produce addresses that a local reader finds obviously wrong even though every field is populated.
- Always store postal codes as text, never as integers
- Keep leading zeros intact through every layer including CSV export
- Allow letters unless the country is strictly numeric
- Do not assume the postal code follows the city
- Treat the separator as presentation, not structure
Numeric systems and their ranges
Most of Europe uses fixed-length numeric codes. Germany, France, Spain, Italy, Greece, Turkey, and Saudi Arabia use five digits; Austria, Belgium, Switzerland, Denmark, Norway, Hungary, and South Africa use four; Poland and Portugal add a hyphen; Sweden uses three digits, a space, and two. Czechia is five digits typically written with a space after the third.
Asia and Latin America are mostly numeric but with different lengths. India, China, Russia, Singapore, and Nigeria use six digits; Japan uses seven with a conventional hyphen after the third; South Korea uses five; Brazil uses eight with a hyphen after the fifth; Chile uses seven; Colombia uses six; Peru and Mexico use five. Length alone is not enough to identify a country, which is why a validator needs the country field before it validates anything.
| Country | Length | Canonical form | Regex |
|---|---|---|---|
| Germany | 5 digits | 10115 | ^\d{5}$ |
| France | 5 digits | 75008 | ^\d{5}$ |
| Spain | 5 digits | 28013 | ^\d{5}$ |
| Austria | 4 digits | 1010 | ^\d{4}$ |
| Poland | 5 digits with hyphen | 00-001 | ^\d{2}-\d{3}$ |
| Portugal | 7 digits with hyphen | 1250-096 | ^\d{4}-\d{3}$ |
| Sweden | 5 digits with space | 114 55 | ^\d{3}\s\d{2}$ |
| Japan | 7 digits with hyphen | 100-0001 | ^\d{3}-\d{4}$ |
| Brazil | 8 digits with hyphen | 01310-100 | ^\d{5}-\d{3}$ |
| India | 6 digits | 110001 | ^\d{6}$ |
| South Korea | 5 digits | 04524 | ^\d{5}$ |
| Mexico | 5 digits | 06600 | ^\d{5}$ |
Alphanumeric systems and their character rules
Canada uses the A1A 1A1 pattern, and the letters are restricted. The first character cannot be D, F, I, O, Q, U, W, or Z, and the remaining letters cannot be D, F, I, O, Q, or U. The space between the two triplets is conventional. A validator that allows any letter will accept codes that Canada Post never issues.
The United Kingdom uses an outward code of two to four characters followed by a single space and a three-character inward code, as in SW1A 1AA. The Netherlands is four digits, a space, and two uppercase letters. Ireland uses a seven-character Eircode split as a three-character routing key and a four-character unique identifier. Malta is three letters, a space, and four digits. Argentina uses the eight-character CPA, a letter followed by four digits, three letters, for example C1425ABC.
| Country | Pattern | Example | Regex |
|---|---|---|---|
| Canada | A1A 1A1 with restricted letters | K1A 0B1 | ^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] ?\d[ABCEGHJKLMNPRSTVWXYZ]\d$ |
| United Kingdom | 2-4 char outward, 3 char inward | SW1A 1AA | ^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$ |
| Netherlands | 4 digits, 2 letters | 1012 AB | ^\d{4} ?[A-Z]{2}$ |
| Ireland | 3 char routing key, 4 char identifier | D02 X285 | ^(?:[A-Z]\d{2}|D6W) ?[A-Z0-9]{4}$ |
| Malta | 3 letters, 4 digits | VLT 1117 | ^[A-Z]{3} ?\d{4}$ |
| Argentina | Letter, 4 digits, 3 letters | C1425ABC | ^[A-Z]\d{4}[A-Z]{3}$ |
Countries with no postal code
Hong Kong and the United Arab Emirates have no postal code system. Both are in this site's 38-country fixture set, and the generator returns an empty postal value for them rather than inventing digits. This is the correct behaviour and it is also a useful test case, because a form that marks postal code as required is broken for both markets.
The UAE convention is a P.O. Box number, which is not a postal code and should not be forced into that field. Hong Kong addresses use the district and street, and mail is delivered without any code. If your data model requires a non-null postal value, use an explicit sentinel such as a country-level flag, not a fake code, because a fake code will pass validation and then fail every downstream check that assumes a real one.
- Hong Kong: no postal code, district plus street is the address
- United Arab Emirates: no postal code, P.O. Box is the common convention
- Make the postal field optional when either country is selected
- Never substitute 00000 or N/A into a postal column
- Record the absence explicitly so exports do not look like missing data
Structure checks versus existence checks
A structure check answers whether a string matches the country pattern. An existence check answers whether the code appears in a reference dataset. Only the second can tell you that a code was never assigned. This site performs the structure check for all 38 countries and an existence check for US ZIPs against 40,979 rows, and it labels them differently so nobody confuses one for the other.
Neither check proves deliverability. A five-digit US ZIP can exist while the street address under it does not, and a structurally valid German code can belong to a different city than the one supplied. If you need that level of assurance, you need a postal reference dataset and a street-level database, which is a different product from a format checker.
| Check type | Covers | Proves | Does not prove |
|---|---|---|---|
| Pattern match | All 38 countries | Well-formed for that country | That the code is assigned |
| US ZIP existence | 40,979 US ZIP rows | The code is in the dataset | City or street agreement |
| Required-field rule | Form level | Submission can proceed | Any real-world validity |
| Length and charset | Field level | Storage will not overflow | Country correctness |
Regex details that cause production bugs
Anchor every pattern. An unanchored /\d{5}/ matches the five digits inside a nine-digit string and reports success. Use ^ and $, or the equivalent full-match API. In JavaScript, \d matches only ASCII digits; in Python, \d matches Unicode digits by default, so a pattern can accept Eastern Arabic numerals unless you compile with re.ASCII or write [0-9].
Decide explicitly whether the separator is required. For display, insert the canonical separator yourself. For input, accept zero or one space or hyphen and normalize. Be careful with hyphens inside character classes, where they define ranges unless escaped or placed last. And do not use a case-insensitive flag to paper over a normalization bug: uppercase the value on write and keep the display form canonical.
- Anchor with ^ and $, or use a full-match function
- Use [0-9] when you mean ASCII digits
- Accept optional separators on input, emit canonical separators on display
- Escape hyphens or place them last inside a character class
- Uppercase before matching instead of scattering case flags
- Reject non-breaking spaces and zero-width characters explicitly
Normalize on write, format on display
Store one canonical form and derive the display form. The canonical form is usually uppercase with the standard separator, or digits only when the separator is purely conventional. Keep the raw user input in a separate column if you need an audit trail, because normalization is lossy and a support agent may need to see exactly what was typed.
Country-specific normalization is small but real. Canada wants an uppercase letter-digit-letter space digit-letter-digit. The Netherlands wants uppercase letters after the digits. The UK wants a single space and uppercase. Brazil and Japan use a hyphen that users frequently omit, so normalize to the hyphenated form. Germany wants no separator at all and must keep the leading zero.
| Country | Raw input | Stored canonical | Display |
|---|---|---|---|
| Canada | k1a0b1 | K1A 0B1 | K1A 0B1 |
| Netherlands | 1012ab | 1012 AB | 1012 AB |
| United Kingdom | sw1a1aa | SW1A 1AA | SW1A 1AA |
| Brazil | 01310100 | 01310-100 | 01310-100 |
| Japan | 1000001 | 100-0001 | 100-0001 |
| Germany | 10115 | 10115 | 10115 |
| Sweden | 11455 | 114 55 | 114 55 |
A test corpus you can copy
Every validator needs a corpus with at least one valid and one invalid case per country, plus cases that specifically test normalization and storage. The table below is a starting set. Notice that several invalid cases are invalid for country-specific reasons rather than generic ones, which is where most hand-written tests stop too early.
Add cases for the countries with no postal code, for leading zeros, and for the separator variants users actually type. If the validator is shared between client and server, run the same corpus against both implementations; a divergence between a permissive client and a strict server is the most common source of "the form accepted it but the API rejected it" reports.
| Case | Value | Country | Expected |
|---|---|---|---|
| Valid ZIP+4 | 97205-2515 | United States | Accept |
| Invalid ZIP+4 | 97205-251 | United States | Reject |
| Valid Canadian | K1A 0B1 | Canada | Accept |
| Bad Canadian letter | D1A 0B1 | Canada | Reject |
| Valid UK | SW1A 1AA | United Kingdom | Accept |
| Missing UK inward | SW1A | United Kingdom | Reject |
| German leading zero | 01067 | Germany | Accept and preserve |
| Letters in a numeric system | 1011A | Germany | Reject |
| No code | Hong Kong | Accept as empty | |
| Invented code | 99999 | United Arab Emirates | Reject the field |
Questions
- Hong Kong and the United Arab Emirates have none, and both are in this site's 38-country fixture set. The generator returns an empty postal field for them instead of inventing a value.
- No. A structural match only means the value is well formed. Only US ZIPs get an existence check here, against 40,979 rows, and no country gets a deliverability claim.
- Never. Canada, the UK, the Netherlands, Ireland, Malta, and Argentina use letters, and Germany, France, Spain, Italy, and Poland have meaningful leading zeros.
- The Dutch code is four digits followed by two uppercase letters, for example 1012 AB. The letters are part of the code, not a region suffix, and a digits-only regex rejects valid addresses.
- Accept input with zero or one space, uppercase it, and store a single canonical separator. Emit the canonical form for display rather than echoing whatever the user typed.
Which countries have no postal codes?+
Does a valid postal code mean the address is real?+
Should postal codes be stored as integers?+
Why does the Netherlands put letters after the digits?+
How should I handle the space in a UK or Canadian code?+
Examples are synthetic. Do not use a generated address for identity checks, account registration, or delivery.