Validating UK Postcodes in JavaScript, Python and SQL

Postcodes · August 2026 · 6 min read

Copy-paste implementations of correct UK postcode validation, normalisation and formatting, with the test cases that catch the usual mistakes.

Correct UK postcode validation is about fifteen lines of code in any language. Here it is, plus the tests that prove it works.

The rules, restated

  • Six permitted formats: A9 9AA, A99 9AA, A9A 9AA, AA9 9AA, AA99 9AA, AA9A 9AA.
  • First position: not Q, V, X. Second position: not I, J, Z.
  • Third position when a letter: A–H, J, K, P, S, T, U, W. Fourth position when a letter: A, B, E, H, M, N, P, R, V, W, X, Y.
  • The final two letters exclude C, I, K, M, O, V.
  • GIR 0AA is valid and matches none of the formats.
  • The space always sits three characters from the end.

JavaScript / TypeScript


const UNIT = 'ABDEFGHJLNPQRSTUWXYZ';
const FIRST = 'ABCDEFGHIJKLMNOPRSTUWYZ';
const SECOND = 'ABCDEFGHKLMNOPQRSTUVWXY';
const THIRD = 'ABCDEFGHJKPSTUW';
const FOURTH = 'ABEHMNPRVWXY';

export const UK_POSTCODE = new RegExp(
  '^(?:GIR ?0AA|(?:' +
    `[${FIRST}](?:[0-9][0-9]?|[0-9][${THIRD}])|` +
    `[${FIRST}][${SECOND}](?:[0-9][0-9]?|[0-9][${FOURTH}])` +
  `) ?[0-9][${UNIT}]{2})

  
    
    

    
    

    Validating UK Postcodes in JavaScript, Python and SQL | UK Random Address Generator
    
    
    

    
    
    
    
    
    
    
    

    
    
    
    
    
    

    
    
    
    
    
    
    
    
    
    
    
    
    
    
  
  
  
  
    ,
  'i'
);

export const normalise = (value: string): string =>
  value.toUpperCase().replace(/\s+/g, '');

export const format = (value: string): string => {
  const compact = normalise(value);
  return compact.length < 5 ? compact : `${compact.slice(0, -3)} ${compact.slice(-3)}`;
};

export const isValidPostcode = (value: string): boolean =>
  UK_POSTCODE.test(format(value));

export const parts = (value: string) => {
  if (!isValidPostcode(value)) return null;
  const [outward, inward] = format(value).split(' ');
  return {
    outward,
    inward,
    area: outward.match(/^[A-Z]{1,2}/)![0],
    district: outward,
    sector: `${outward} ${inward[0]}`,
    unit: inward.slice(1)
  };
};

Python


import re

UNIT = "ABDEFGHJLNPQRSTUWXYZ"
FIRST = "ABCDEFGHIJKLMNOPRSTUWYZ"
SECOND = "ABCDEFGHKLMNOPQRSTUVWXY"
THIRD = "ABCDEFGHJKPSTUW"
FOURTH = "ABEHMNPRVWXY"

UK_POSTCODE = re.compile(
    rf"^(?:GIR ?0AA|(?:"
    rf"[{FIRST}](?:[0-9][0-9]?|[0-9][{THIRD}])|"
    rf"[{FIRST}][{SECOND}](?:[0-9][0-9]?|[0-9][{FOURTH}])"
    rf") ?[0-9][{UNIT}]{{2}})
quot;, re.IGNORECASE, ) def normalise(value: str) -> str: return "".join(value.upper().split()) def format_postcode(value: str) -> str: compact = normalise(value) return compact if len(compact) < 5 else f"{compact[:-3]} {compact[-3:]}" def is_valid(value: str) -> bool: return bool(UK_POSTCODE.match(format_postcode(value)))

PostgreSQL

Store the normalised form and validate with a check constraint:


ALTER TABLE addresses
  ADD CONSTRAINT postcode_format CHECK (
    upper(replace(postcode, ' ', '')) ~
    '^(GIR0AA|([ABCDEFGHIJKLMNOPRSTUWYZ]([0-9][0-9]?|[0-9][ABCDEFGHJKPSTUW])|[ABCDEFGHIJKLMNOPRSTUWYZ][ABCDEFGHKLMNOPQRSTUVWXY]([0-9][0-9]?|[0-9][ABEHMNPRVWXY]))[0-9][ABDEFGHJLNPQRSTUWXYZ]{2})
#39; ); -- Index the outward code for district-level queries CREATE INDEX addresses_outward_idx ON addresses (split_part(postcode, ' ', 1));

The test suite


const valid = [
  'SW1A 1AA', 'M1 1AE', 'B33 8TH', 'CR2 6XH', 'DN55 1PT',
  'EC1A 1BB', 'W1A 0AX', 'GIR 0AA', 'BT1 1AA', 'G1 1XW',
  'CF10 1EP', 'EH1 1BB', 'sw1a1aa', 'ec1a 1bb'
];

const invalid = [
  'QW1A 1AA',   // Q in the first position
  'SI1 1AA',    // I in the second position
  'M1 1AC',     // C in the unit
  'SW1A 1AAA',  // too long
  'SW1A1',      // too short
  '12345',      // not a postcode
  ''            // empty
];

What validation cannot tell you

A regular expression proves that a postcode is well formed. It cannot tell you that the postcode exists, that it belongs to the town the user typed, or that a specific property receives post there. Those questions need address data — Royal Mail's Postcode Address File under licence, or the ONS Postcode Directory for statistical geography.

For test data, structural validity is exactly what you want: use the postcode generator for valid examples, or check a specific one with the address and postcode validator.

More guides