Validar um código postal de Portugal

Um código postal português (CP7) tem sete dígitos no formato NNNN-NNN — por exemplo, 1000-098. Validar é confirmar duas coisas: que o formato está certo e que o código existe. A primeira faz-se com uma expressão regular; a segunda, com um pedido à API do Moradas, grátis e sem chave.

1. O formato: expressão regular

Quatro dígitos, um hífen e três dígitos:

^\d{4}-\d{3}$

Os quatro primeiros dígitos (CP4) indicam a zona postal; dentro dessa zona, os três últimos (CP3) distinguem ruas, troços de rua ou grandes destinatários com código próprio, como empresas e instituições.

EntradaResultadoO que fazer
1000-098válido—
1000098, 1000 098formato inválidonormalizar para 1000-098
1000incompletosó o CP4 — peça os sete dígitos
1000-98formato inválidofalta um dígito

Os clientes escrevem o código de muitas maneiras — com espaço, sem hífen. Normalize antes de validar:

function normalizeCP7(text) {
  const digits = text.replace(/\D/g, '');  // keep the digits only
  return digits.length === 7 ? digits.slice(0, 4) + '-' + digits.slice(4) : null;
}

normalizeCP7('1000 098');  // '1000-098'
/^\d{4}-\d{3}$/.test('1000-098');  // true
/^\d{4}-\d{3}$/.test('1000098');  // false

Noutras linguagens:

// PHP
preg_match('/^\d{4}-\d{3}$/D', $cp7) === 1

# Python
re.fullmatch(r"\d{4}-\d{3}", cp7, re.ASCII) is not None

// C#
Regex.IsMatch(cp7, @"^\d{4}-\d{3}\z")

2. A existência: um pedido à API

Um código com o formato certo pode não existir. O endpoint /cp/{cp7} responde 200 com a localidade, o concelho, o distrito e as ruas abrangidas, ou 404 se o código não existir:

GET https://moradas.dev/cp/1000-098

{ "cp7": "1000-098", "cp4": "1000", "cp3": "098",
  "distrito": "Lisboa", "concelho": "Lisboa", "localidade": "Lisboa",
  "arterias": [ { "street": "Praça do Chile", ... } ] }

GET https://moradas.dev/cp/9999-999

404  { "error": "CP7 not found" }

Validação completa, no browser ou em Node.js:

async function validateCP7(input) {
  const digits = input.replace(/\D/g, '');
  if (digits.length !== 7) return { valid: false, reason: 'format' };
  const cp7 = digits.slice(0, 4) + '-' + digits.slice(4);
  try {
    const res = await fetch('https://moradas.dev/cp/' + cp7);
    if (res.status === 404) return { valid: false, reason: 'not found', cp7 };
    if (!res.ok) return { valid: null, cp7 };  // 429 or outage: don't block the customer
    const card = await res.json();
    return { valid: true, cp7, localidade: card.localidade, concelho: card.concelho, distrito: card.distrito };
  } catch (e) {
    return { valid: null, cp7 };  // network error: don't block the customer
  }
}

await validateCP7('1000 098');
// { valid: true, cp7: '1000-098', localidade: 'Lisboa', concelho: 'Lisboa', distrito: 'Lisboa' }

Se a API não responder — limite de pedidos ou falha de rede — trate o código como «não verificado» e deixe o cliente continuar: o serviço é grátis e não tem garantia de disponibilidade. O mesmo pedido em PHP, Python e C#, com nova tentativa após um 429, está no início rápido da API.

3. Melhor ainda: preencher o código em vez de o validar

Muitos erros nascem de escrever o código postal à mão. Com o autocomplete do Moradas, o cliente escolhe a rua numa lista e o CP7 é preenchido pelo número de porta — numa rua com vários códigos postais, é o número que decide qual se aplica.

Experimentar a demoInício rápido da API