Back to the blog

How to Check Passport Validity (and the Six-Month Rule)

How to automatically verify whether a passport is valid, why many countries require six months of remaining validity, and how to implement the check using the MRZ expiry date.

Extract Passport Data
passport validitysix month rulepassport expirytravel document checkMRZ

"Is this passport valid?" looks like a trivial date comparison. In practice it has three complications that break naive implementations: the missing century in the MRZ, the six-month rule, and the difference between valid and acceptable.

Where the date lives

The expiry date appears in two places on the data page, and it's worth reading both:

  • The visual inspection zone (VIZ), printed in human-readable form.
  • The MRZ, at positions 22-27 of line 2 in YYMMDD format, followed at position 28 by its check digit.

The MRZ version has a decisive advantage: it comes with arithmetic verification. If the digit at position 28 checks out, the date was read correctly. No other date on the document offers that guarantee.

When both readings agree, the date is confirmed by two independent routes. When they don't, you have an early signal of a reading problem — or a tampered document.

Complication 1: the MRZ omits the century

330705 is six digits. 2033 or 1933?

For expiry the answer is easy: passports are issued with five- or ten-year validity, so the date always falls in the near future. 33 is 2033.

A practical rule with no realistic exceptions:

function expiryYear(yy) {
  const century = Math.floor(new Date().getFullYear() / 100) * 100 // 2000
  const candidate = century + Number(yy)
  // A passport doesn't expire more than ~11 years out,
  // but it can have been expired for a long time.
  return candidate < new Date().getFullYear() - 20 ? candidate + 100 : candidate
}

Don't copy this logic to the date of birth, where the ambiguity is real: 05 could be 1905 or 2005. There the convention is that if the two digits exceed the current year's, it belongs to the previous century.

Complication 2: the six-month rule

This is where most implementations fall down. A passport expiring in three months is valid today — and many countries still won't let its holder in.

It's the so-called six-month rule: numerous destinations require a passport to retain at least six months' validity from the date of entry, and some count from the intended date of departure. Others ask for three months, and others simply require validity for the duration of the stay.

Consequences for your system's design:

  • "Valid" and "acceptable" are two different questions. Don't collapse them into one boolean.
  • The reference date isn't today, it's the travel or transaction date.
  • The threshold depends on the destination, so it's configuration, not a constant.
function assessValidity(expiryDate, { referenceDate = new Date(), monthsRequired = 6 } = {}) {
  const expiry = new Date(expiryDate)

  const minimum = new Date(referenceDate)
  minimum.setMonth(minimum.getMonth() + monthsRequired)

  return {
    valid: expiry > referenceDate,
    meetsMargin: expiry >= minimum,
    daysRemaining: Math.floor((expiry - referenceDate) / 86400000),
  }
}

A passport with valid: true and meetsMargin: false is exactly the case to flag to the user in advance — not to reject, but to warn that they'll likely need to renew before travelling.

Complication 3: validity is not authenticity

Dates checking out tells you the document hasn't expired. It doesn't tell you it's genuine, that it isn't reported stolen, or that the person presenting it is its holder.

A complete verification system separates the layers:

LayerWhat it checksHow
ReadingThe data was read correctlyMRZ check digits
ConsistencyFields agree with each otherMRZ against visual zone
ValidityThe document hasn't expiredExpiry date
StatusNot reportedQuery against official registries
HolderIt belongs to the bearerBiometrics / in-person check

An extraction API covers the first three well. The last two require external sources and sit outside the scope of any OCR — be sceptical of anyone claiming otherwise.

Checks almost nobody implements

With the dates extracted and validated, a few extra checks come for free:

Consistency between issue and expiry. The gap should match a plausible validity period — typically 5 or 10 years, less for minors. A 30-year gap is an anomaly worth reviewing.

Expiry after issue. Sounds obvious, but it's the check that catches a century transcription error.

Plausible age. The date of birth derived from the MRZ should yield a reasonable age, and match your file.

function consistencyChecks({ dateOfIssue, dateOfExpiry, dateOfBirth }) {
  const issue = new Date(dateOfIssue)
  const expiry = new Date(dateOfExpiry)
  const birth = new Date(dateOfBirth)
  const years = (expiry - issue) / (365.25 * 86400000)

  return {
    ordered: expiry > issue && issue > birth,
    plausibleValidity: years > 0.5 && years < 11.5,
  }
}

Automating it

Our API returns all three dates normalized to DD/MM/YYYY, along with both MRZ lines validated against their check digits:

{
  "dateOfBirth": "05/07/1980",
  "dateOfIssue": "10/03/2023",
  "dateOfExpiry": "05/07/2033",
  "mrzLine2": "G123456786MEX8007050F3307054<<<<<<<<<<<<<<08"
}

The digit at position 28 checking out is what lets you build validity logic on a date you can trust, rather than on a reading that might have gone wrong.

Try it on a real passport in the free demo, or paste an MRZ you already have into the validator to see the digits recomputed. The arithmetic behind that is in ICAO 9303 check digits, and the position map that locates the date on line 2 is in How to read the MRZ line by line.

Need to extract passport data automatically?

Try our API with 20 free extractions. Integrate in minutes, get results in seconds.

Start for free