Skip to content

DOMAIN:EU_REGULATION:ADDRESS_STANDARDS

OWNER: aimee (scoping) ALSO_USED_BY: urszula, maxim (backend), floris, floor (frontend), eric (invoicing), ALL dev agents LAST_VERIFIED: 2026-03-26 SCOPE: EU address formats, validation, standardisation, checkout address patterns


Overview

Address handling is deceptively complex in multi-country EU e-commerce. Every country has its own postal format, field ordering, validation rules, and postcode structure. Getting this wrong means failed deliveries, returned packages, and frustrated customers.

This page covers the international standards, country-specific EU formats, validation services, implementation patterns, and GDPR considerations for address data.

Cross-reference with Shipping Providers for carrier label format requirements and Peppol E-Invoicing for address fields in structured invoices.


ISO 19160 — International Addressing Standard

What It Is

ISO 19160 is the multi-part international standard for addressing, jointly developed by ISO and the Universal Postal Union (UPU). It provides a conceptual model for address information that works across all countries and use cases.

Key Parts

Part Title Status
19160-1 Conceptual model (address profiles) Published 2015
19160-2 Assigning and maintaining addresses Published 2023
19160-3 Quality of address data Published 2020
19160-4 International postal address components Published 2017

ISO 19160-4 is also published as UPU S42 — they are the same standard.

Address Profile Model

ISO 19160-1 defines an address as having three hierarchical levels:

Segments    — Functional groups (addressee, delivery point, administrative area)
Constructs  — Logical portions (organisation ID, thoroughfare, postal code)
Elements    — Atomic values (street name, house number, postal code value)

RELEVANCE_TO_GE: use the segment/construct/element model when designing flexible address schemas. Do not hardcode field names like "address_line_1" — they are meaningless across countries.


UPU S42 — Universal Postal Union Address Standard

What It Is

UPU S42 (identical to ISO 19160-4) defines 31 address elements that cover every country's postal format. Each country defines a template specifying which elements are used, their order, and their formatting rules.

Core Address Elements

The 31 elements include:

Element Description Example (NL)
Organisation name Business name Growing Europe B.V.
Building name Named building WTC
Sub-building indicator Floor, apartment, room 4e verdieping
Thoroughfare type Street type (straat, laan, weg) Straat
Thoroughfare name Street name Kalverstraat
Thoroughfare number House number 123
Thoroughfare number suffix House number addition A
Postcode Postal code 1012 PA
Town City / municipality Amsterdam
Province State / province Noord-Holland
Country Country name or code NL

S42 Templates

Each country publishes an S42 template defining which elements are used and how they are rendered. The UPU provides these templates in XML format for machine consumption.

ACCESS: address.post — official S42 template portal with country-specific formats and validation rules.

UPU_API: the UPU Address API provides address capture forms with country-appropriate fields, described in both the local language and Latin script.


EU Country-Specific Address Formats

Netherlands (NL)

{name}
{street} {house_number}{house_number_addition}
{postcode} {city}

EXAMPLE:

Mevrouw J. de Vries
Kalverstraat 123A
1012 PA AMSTERDAM

POSTCODE_FORMAT: NNNN LL (4 digits + space + 2 uppercase letters) POSTCODE_REGEX: ^[1-9]\d{3}\s?[A-Z]{2}$ HOUSE_NUMBER: mandatory, placed after street name HOUSE_NUMBER_ADDITION: common (A, B, -1, -2, bis, etc.) UNIQUE_FEATURE: postcode + house number uniquely identifies a Dutch address. No other fields are strictly needed. This enables postcode lookup — enter postcode + number, auto-fill street and city.

Germany (DE)

{name}
{street} {house_number}
{postcode} {city}

EXAMPLE:

Frau F. Meier
Weberstraße 2
53113 BONN

POSTCODE_FORMAT: NNNNN (5 digits, leading zero possible) POSTCODE_REGEX: ^\d{5}$ HOUSE_NUMBER: placed after street name (same as NL) NOTE: some addresses have no street name (e.g., island of Baltrum)

France (FR)

{name}
{house_number} {street}
{postcode} {city}

EXAMPLE:

Mme Marie Page
23 Rue de Grenelle
75700 PARIS

POSTCODE_FORMAT: NNNNN (5 digits, first 2 = department) POSTCODE_REGEX: ^\d{5}$ HOUSE_NUMBER: placed BEFORE street name (opposite of NL/DE) CEDEX: organisations receiving large volumes of mail may use CEDEX addresses

Belgium (BE)

{name}
{street} {house_number}
{postcode} {city}

EXAMPLE:

M. Emile Dubois
Rue du Diamant 215
4800 VERVIERS

POSTCODE_FORMAT: NNNN (4 digits) POSTCODE_REGEX: ^\d{4}$ HOUSE_NUMBER: placed after street name BIS_NUMBER: optional sub-number (e.g., 215bis) BOX_NUMBER: apartment/unit identifier (e.g., bus 3) ABBREVIATION_RULE: abbreviations forbidden in city name unless line exceeds 50 chars

Austria (AT)

{name}
{street} {house_number}
{postcode} {city}

POSTCODE_FORMAT: NNNN (4 digits) POSTCODE_REGEX: ^\d{4}$

Spain (ES)

{name}
{street}, {house_number}, {floor} {door}
{postcode} {city} ({province})

POSTCODE_FORMAT: NNNNN (5 digits) POSTCODE_REGEX: ^\d{5}$ NOTE: floor (piso) and door (puerta) are commonly used

Italy (IT)

{name}
{street}, {house_number}
{postcode} {city} {province_code}

POSTCODE_FORMAT: NNNNN (5 digits) PROVINCE_CODE: 2-letter province abbreviation (e.g., MI for Milano)


Address Format Summary

Country Postcode Format House # Position Postcode + HN = Unique API Available
NL NNNN LL (6) After street Yes Yes
DE NNNNN (5) After street No Limited
FR NNNNN (5) Before street No Limited
BE NNNN (4) After street No Yes
AT NNNN (4) After street No Limited
ES NNNNN (5) After street No Limited
IT NNNNN (5) After street No Limited

Netherlands-Specific: BAG and Postcode APIs

BAG — Basisregistratie Adressen en Gebouwen

The BAG is the authoritative Dutch address database, maintained by municipalities and managed nationally by Kadaster. It contains:

  • Panden (buildings): geometry, construction year, status
  • Verblijfsobjecten (dwellings): usage, floor area
  • Nummeraanduidingen (address designations): postcode, house number, addition
  • Openbare ruimtes (public spaces): street names
  • Woonplaatsen (cities/towns): municipality mapping

DATA_SOURCE: data.overheid.nl/dataset/bag PDOK_API: https://api.pdok.nl/bzk/locatieserver/search/v3_1/free — free government API

Postcode Lookup Services

For Dutch projects, postcode + house number lookup is table stakes. Users expect it.

PostcodeAPI.nu

DESCRIPTION: Dutch address data from BAG, built for developers BASE_URL: https://api.postcodeapi.nu/v3/ AUTH: API key in X-Api-Key header PRICING: EUR 28.50/year for up to 1,000 calls/month DATA_SOURCE: Kadaster BAG + CBS open data DOCUMENTATION: postcodeapi.nu/docs

// PostcodeAPI.nu lookup example
const response = await fetch(
  `https://api.postcodeapi.nu/v3/lookup/${postcode}/${houseNumber}`,
  { headers: { 'X-Api-Key': process.env.POSTCODE_API_KEY } }
);
const { street, city, province, municipality } = await response.json();

Postcode.eu (Postcode.nl)

DESCRIPTION: official Dutch and international address data via REST BASE_URL: https://api.postcode.eu/ AUTH: HTTP Basic (API key + secret) PRICING: EUR 50/year for up to 12,500 calls/year COUNTRIES: NL, BE, DE, FR, AT, ES, IT, UK, and more DOCUMENTATION: developer.postcode.eu BAG_INTEGRATION: supports lookup by BAG Nummeraanduiding ID

RECOMMENDATION_FOR_GE: use Postcode.eu for projects targeting multiple EU countries. Use PostcodeAPI.nu for NL-only projects with higher call volumes.

PDOK BAG API (Free / Government)

DESCRIPTION: official government BAG API, free and unlimited BASE_URL: https://api.pdok.nl/bzk/locatieserver/search/v3_1/ AUTH: none LIMITATION: raw BAG data, less developer-friendly than commercial APIs USE_WHEN: budget-constrained projects, internal tools, or bulk geocoding


Address Validation Services

EU-Based Options

REQUIREMENT: for GDPR compliance, prefer address validation services that process data within the EU. Avoid US-based services that transfer personal address data outside the EEA without adequate safeguards.

Service HQ EU Processing Countries API Pricing
Postcode.eu NL Yes 15+ EU REST From EUR 50/yr
PostcodeAPI.nu NL Yes NL only REST EUR 28.50/yr
HERE Technologies NL/DE EU available Global REST Freemium
Loqate (GBG) UK EU processing Global REST Pay-per-use
PDOK NL (gov) Yes NL only REST Free

GE_RECOMMENDATION: Postcode.eu as primary (multi-country, NL-based, BAG-linked). PDOK as fallback for NL-only projects with cost constraints.

Validation Strategy

VALIDATE_AT: point of entry (checkout form, address book save) VALIDATE_AGAIN: before shipping label generation (address may have changed) NEVER: validate addresses on every page load — it burns API calls and adds latency

// Address validation flow
async function validateAddress(address: AddressInput): Promise<ValidationResult> {
  // 1. Format validation (postcode regex by country)
  const formatValid = validatePostcodeFormat(address.country, address.postcode);
  if (!formatValid) return { valid: false, error: 'Invalid postcode format' };

  // 2. NL-specific: postcode + house number lookup
  if (address.country === 'NL') {
    const bagResult = await postcodeEuLookup(address.postcode, address.houseNumber);
    if (!bagResult) return { valid: false, error: 'Address not found in BAG' };
    // Auto-fill street and city from BAG
    return { valid: true, enriched: { street: bagResult.street, city: bagResult.city } };
  }

  // 3. Other countries: format check + optional API validation
  return { valid: true };
}

Checkout Address Patterns

Shipping vs Billing Address

DEFAULT: pre-check "billing address same as shipping" — 85%+ of orders use same address. Show billing address fields only when unchecked.

// Address form state
interface CheckoutAddressState {
  shippingAddress: Address;
  billingAddressSameAsShipping: boolean;  // default: true
  billingAddress?: Address;               // only when different
}

Address Book (Authenticated Users)

PATTERN: users save multiple addresses with labels (Home, Work, Partner). One default shipping address, one default billing address.

CREATE TABLE user_addresses (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  label TEXT,                            -- 'Home', 'Work', etc.
  is_default_shipping BOOLEAN DEFAULT false,
  is_default_billing BOOLEAN DEFAULT false,
  -- Address fields (see schema below)
  country_code TEXT NOT NULL,
  postcode TEXT NOT NULL,
  city TEXT NOT NULL,
  street TEXT,
  house_number TEXT,
  house_number_addition TEXT,
  address_line_1 TEXT,                   -- for countries without structured fields
  address_line_2 TEXT,
  province TEXT,
  company_name TEXT,
  -- Metadata
  validated_at TIMESTAMPTZ,              -- last validation timestamp
  bag_id TEXT,                           -- NL: BAG nummeraanduiding ID
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Only one default per type per user
CREATE UNIQUE INDEX idx_default_shipping ON user_addresses(user_id) WHERE is_default_shipping;
CREATE UNIQUE INDEX idx_default_billing ON user_addresses(user_id) WHERE is_default_billing;

Guest Checkout Address

Guest checkout must collect address without requiring account creation. Store address with the order, not in a user address book.

GDPR_RULE: guest checkout addresses are retained only as long as needed for order fulfilment and legal retention periods (typically 7 years for tax/invoice records in NL). After that, anonymise or delete.

Country-Specific Form Fields

The checkout address form must adapt its fields based on the selected country:

// Country-specific field configuration
const addressFieldConfig: Record<string, AddressFieldConfig> = {
  NL: {
    fields: ['postcode', 'houseNumber', 'houseNumberAddition'],
    autoFill: ['street', 'city'],              // from postcode API
    postcodeLabel: 'Postcode',
    postcodeFormat: 'NNNN LL',
    houseNumberRequired: true,
  },
  DE: {
    fields: ['street', 'houseNumber', 'postcode', 'city'],
    autoFill: [],
    postcodeLabel: 'PLZ',
    postcodeFormat: 'NNNNN',
    houseNumberRequired: true,
  },
  FR: {
    fields: ['houseNumber', 'street', 'postcode', 'city'],  // number BEFORE street
    autoFill: [],
    postcodeLabel: 'Code postal',
    postcodeFormat: 'NNNNN',
    houseNumberRequired: true,
  },
  BE: {
    fields: ['street', 'houseNumber', 'busNumber', 'postcode', 'city'],
    autoFill: [],
    postcodeLabel: 'Postcode',
    postcodeFormat: 'NNNN',
    houseNumberRequired: true,
  },
};

GDPR Considerations

Address as Personal Data

Addresses are personal data under GDPR. A postal address combined with a name directly identifies a natural person.

OBLIGATIONS: - Lawful basis for processing (contract performance for order fulfilment) - Data minimisation — only collect fields needed for delivery - Retention limitation — delete or anonymise when no longer needed - Right to erasure — must be able to delete address data on request (except where legal retention periods apply, e.g., invoice records) - Storage security — encrypt at rest, restrict access

Retention Rules

Data Type Retention (NL) Legal Basis
Shipping address Duration of order fulfilment + returns Contract performance
Billing address 7 years (fiscale bewaarplicht) Legal obligation (AWR)
Address book Until user deletes or account is closed Consent / contract
Guest checkout Order fulfilment + legal retention Contract + legal obligation

Shipping Label Format Requirements

Each carrier has specific address formatting requirements for labels. See Shipping Providers for details.

GENERAL_RULES: - Country name in local language OR ISO 3166-1 alpha-2 code - City in UPPERCASE for international shipments - Postcode before or after city depending on country convention - Maximum line length: typically 35-40 characters per line - No special characters that the carrier's system cannot render


GE Drizzle Schema — Flexible EU Address

// Drizzle schema pattern for multi-country EU addresses
export const addresses = pgTable('addresses', {
  id: uuid('id').defaultRandom().primaryKey(),

  // Core fields (all countries)
  countryCode: text('country_code').notNull(),     // ISO 3166-1 alpha-2
  postcode: text('postcode').notNull(),
  city: text('city').notNull(),

  // Structured fields (NL, DE, BE, AT, etc.)
  street: text('street'),
  houseNumber: text('house_number'),
  houseNumberAddition: text('house_number_addition'),  // NL: toevoeging
  busNumber: text('bus_number'),                       // BE: bus number

  // Unstructured fallback (FR, ES, IT, international)
  addressLine1: text('address_line_1'),
  addressLine2: text('address_line_2'),

  // Administrative
  province: text('province'),                          // optional
  companyName: text('company_name'),

  // Validation metadata
  validatedAt: timestamp('validated_at'),
  validationSource: text('validation_source'),         // 'bag', 'postcode_eu', etc.
  bagId: text('bag_id'),                               // NL: nummeraanduiding ID

  // Audit
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
});

DESIGN_DECISION: use both structured fields (street, house number) AND unstructured fallback (address_line_1/2). For NL and DE, populate structured fields from postcode lookup. For countries without reliable postcode APIs, use address_line_1/2.

NEVER: store the formatted/rendered address as a single text field. Always store atomic components. Rendering is a presentation concern — the format changes by context (label, invoice, display).


Sources