UK PostCode Validation Function In PHP

Creating a function to validate UK postcodes would seem like a simple task, but there is a little more to it than checking the number of characters. In fact there are several different variants of UK postal codes, especially if you include BFPO and overseas addresses. The official postcode specification details the structure of the postcode, and there is also a list of BFPO numbers from mod.uk.

Thankfully, John Gardner took the time to write a little function that is able to validate a UK postcode, and this has been coppied all over the internet. I came across this function a while ago in a project, but as it wasn't attributed to anyone I assumed that it was written by another coder on the same project. It was nice to track down the original author and give them the credit. :)

The original version I found was a little out of date as it used the old ereg PHP regular expression functions, which caused deprecated errors in PHP 5.3. At the time I was creating a search feature that used full and partial postcodes so I also changed the function to be able to detect both. The code block below is my take on the function. Note that the postcode is passed by reference so if a postcode is found it is standardised into a correct format.

/**
 * Function to see if a string is a UK postcode or not. The postcode is also 
 * formatted so it contains no strings. Full or partial postcodes can be used.
 * 
 * @param string $toCheck
 * @return boolean 
 */
function postcode_check(&$toCheck) {
  // Permitted letters depend upon their position in the postcode.
  $alpha1 = "[abcdefghijklmnoprstuwyz]";                          // Character 1
  $alpha2 = "[abcdefghklmnopqrstuvwxy]";                          // Character 2
  $alpha3 = "[abcdefghjkstuw]";                                   // Character 3
  $alpha4 = "[abehmnprvwxy]";                                     // Character 4
  $alpha5 = "[abdefghjlnpqrstuwxyz]";                             // Character 5
  
  // Expression for postcodes: AN NAA, ANN NAA, AAN NAA, and AANN NAA with a space
  // Or AN, ANN, AAN, AANN with no whitespace
  $pcexp[0] = '^(' . $alpha1 . '{1}' . $alpha2 . '{0,1}[0-9]{1,2})([[:space:]]{0,})([0-9]{1}' . $alpha5 . '{2})?$';

  // Expression for postcodes: ANA NAA
  // Or ANA with no whitespace
  $pcexp[1] = '^(' . $alpha1 . '{1}[0-9]{1}' . $alpha3 . '{1})([[:space:]]{0,})([0-9]{1}' . $alpha5 . '{2})?$';

  // Expression for postcodes: AANA NAA
  // Or AANA With no whitespace
  $pcexp[2] = '^(' . $alpha1 . '{1}' . $alpha2 . '[0-9]{1}' . $alpha4 . ')([[:space:]]{0,})([0-9]{1}' . $alpha5 . '{2})?$';

  // Exception for the special postcode GIR 0AA
  // Or just GIR
  $pcexp[3] = '^(gir)([[:space:]]{0,})?(0aa)?$';

  // Standard BFPO numbers
  $pcexp[4] = '^(bfpo)([[:space:]]{0,})([0-9]{1,4})$';

  // c/o BFPO numbers
  $pcexp[5] = '^(bfpo)([[:space:]]{0,})(c\/o([[:space:]]{0,})[0-9]{1,3})$';
  
  // Overseas Territories
  $pcexp[6] = '^([a-z]{4})([[:space:]]{0,})(1zz)$';

  // Anquilla
  $pcexp[7] = '^(ai\-2640)$';
  
  // Load up the string to check, converting into lowercase
  $postcode = strtolower($toCheck);

  // Assume we are not going to find a valid postcode
  $valid = false;

  // Check the string against the six types of postcodes
  foreach ($pcexp as $regexp) {
    if (preg_match('/' . $regexp . '/i', $postcode, $matches)) {

      // Load new postcode back into the form element
      $postcode = strtoupper($matches[1]);
      if (isset($matches[3])) {
        $postcode .= ' ' . strtoupper($matches[3]);
      }

      // Take account of the special BFPO c/o format
      $postcode = preg_replace('/C\/O/', 'c/o ', $postcode);
      
      // Remember that we have found that the code is valid and break from loop
      $valid = true;
      break;
    }
  }

  // Return with the reformatted valid postcode in uppercase if the postcode was 
  // valid
  if ($valid) {
    $toCheck = $postcode;
    return true;
  } else {
    return false;
  }
}

Run the function in the following way.

$postcode = 'WR5 3DA';

$valid = postcode_check($postcode);

if ($valid === FALSE) {
  print $postcode . ' is invalid';
}
else {
  print $postcode . ' is valid';
}

Comments

Hi Philip, I wasted three days and nights and found your this post and it just helped me a lot :) Thanks man and keep it up :)
Permalink
Hello Philip, I know this was quite a while ago but thought i would take a punt. The above has helped me in many aspects but it doesn't add the space should a client add their postcode like: AN121AN. My question is, is it meant to and if so can you say why it wouldn't be working? Hopefully hear from you shortly. Cheers Julian
Permalink
I tried to pass your postcode through the function and it worked correctly.
Name
Philip Norton
Permalink

Hi this is a great piece of code but it doesn't seem to like AN NAA (L6 3AB for example)

Permalink

But "L6 3AB" is returned as a valid postcode?...

Name
Philip Norton
Permalink

Yes sorry it does work fine I was tampering before passing it through it seems.

Permalink

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
6 + 4 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.