Dit artikel is ouder dan vier jaar. De inhoud kan verouderd zijn, vooral bij technische voorbeelden of verwijzingen naar tooling.
Reguliere expressies zijn handig om invoer op een verwacht patroon te controleren. Een Nederlandse postcode heeft een overzichtelijke structuur; een volledig adres, telefoonnummer of IBAN vraagt om meer dan een paar tekens die op de juiste plek staan. Een regex die true teruggeeft, bewijst niet dat de gegevens bestaan of bij de gebruiker horen.
Hieronder staan JavaScript-voorbeelden voor veelgebruikte Nederlandse invoervelden. Waar een regex niet de juiste oplossing is, gebruiken we een parser of een aanvullende controle. De voorbeelden zijn JavaScript: escaping, flags en beschikbare functies kunnen in andere programmeertalen verschillen.
E-mailadressen valideren
Voor het valideren van e-mailadressen adviseer ik geen Regex te maken. Voor een regulier contactformulier wil je bijvoorbeeld jasper+blog@example.org en naam@example.technology kunnen accepteren. Onderstaande controle kiest bewust voor een gangbare ASCII-notatie: een lokaal deel zonder lege stukken tussen punten, gevolgd door een domeinnaam met minimaal één punt. Domeinlabels mogen geen underscores bevatten of met een koppelteken beginnen of eindigen. Het lokale deel mag maximaal 64 tekens lang zijn; het volledige adres maximaal 254.
function validateEmail(value) {
if (typeof value !== 'string') return false;
const email = value.trim();
if (email.length > 254) return false;
const parts = email.split('@');
if (parts.length !== 2) return false;
const [local, domain] = parts;
const localPattern = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/i;
const domainPattern = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i;
return local.length <= 64 && localPattern.test(local) && domainPattern.test(domain);
}
Dit is geen volledige implementatie van alle e-mailstandaarden. Geciteerde lokale delen, adressen met een IP-literal en Unicode-e-mailadressen vallen buiten dit voorbeeld. Ondersteunt je toepassing die vormen, kies dan validatie die aansluit op je mailprovider en backend. Een beperking van je formulier is niet hetzelfde als een universele regel voor e-mail.
Gebruik in een formulier ook <input type="email" required>. Zonder required is een leeg veld toegestaan. De browser heeft zijn eigen validatieregels; die zijn niet exact gelijk aan het bewust gekozen profiel hierboven. MDN beschrijft de HTML-e-mailvalidatie en de beperkingen daarvan. Alleen een bevestigingsmail kan aantonen dat iemand toegang heeft tot de opgegeven mailbox.
URL valideren
Voor URL's zou ik geen enorme regex onderhouden. JavaScript heeft een URL-parser die ook poorten, IPv6 en internationale domeinnamen verwerkt. Bepaal daarnaast welke protocollen je applicatie accepteert.
Dit voorbeeld accepteert absolute HTTP-, HTTPS- en FTP-URL's. Een expliciet protocol en :// zijn verplicht. Witruimte binnen de URL, backslashes en ingebouwde gebruikersnamen of wachtwoorden worden afgewezen. Dat zijn bewuste invoerregels bovenop de parser, die sommige afwijkende invoer anders normaliseert.
function validateURL(value) {
if (typeof value !== 'string') return false;
const input = value.trim();
if (/[\s\\]/u.test(input)) return false;
if (!/^(?:https?|ftp):\/\/[^/?#]/i.test(input)) return false;
try {
const url = new URL(input);
return ['http:', 'https:', 'ftp:'].includes(url.protocol)
&& url.hostname !== ''
&& url.username === '' && url.password === '';
} catch {
return false;
}
}
https://example.org en http://localhost:3000/ passen binnen deze regels. mailto: en tel: zijn ook URI-schema's, maar vallen bewust buiten deze functie. Een spatie in een pad moet bijvoorbeeld als %20 worden aangeleverd.
Een parsebare URL is niet automatisch bereikbaar of veilig om vanaf je server op te halen. Deze functie laat bijvoorbeeld lokale adressen toe. Voor server-side requests zijn aparte controles op bestemmingen, redirects en netwerktoegang nodig.
Website URL valideren
Voor een websiteveld kun je invoer zonder protocol toestaan en standaard HTTPS aannemen. Deze variant gebruikt de vorige functie, accepteert alleen HTTP(S) en verwacht een hostnaam met een punt. Namen zoals localhost vallen buiten dit websiteveld.
function validateWebsiteURL(value) {
if (typeof value !== 'string') return false;
let input = value.trim();
if (!/^https?:\/\//i.test(input)) {
// Reject other schemes and protocol-relative input.
if (/^[a-z][a-z0-9+.-]*:/i.test(input) || input.startsWith('//')) return false;
input = `https://${input}`;
}
if (!validateURL(input)) return false;
const url = new URL(input);
// This form expects a dotted hostname, rather than an intranet name.
return ['http:', 'https:'].includes(url.protocol) && url.hostname.includes('.');
}
Hierdoor werken example.org, www.example.org en https://example.org. Gebruik bij een expliciete poort de volledige notatie, bijvoorbeeld https://example.org:8080. Deze eenvoudige variant accepteert ook IPv4-adressen, maar geen IPv6-literals. Hij controleert geen domeinregistratie. Wil je uitsluitend publieke domeinnamen, dan vraagt dat om aanvullend beleid.
De functie retourneert een boolean. Wil je het adres opslaan, normaliseer dan op dezelfde manier en bewaar de uitkomst van new URL(input).href. Een HTML-veld met type="url" verwacht zelf een absolute URL; gebruik voor een veld dat ook kale domeinnamen accepteert bijvoorbeeld type="text" inputmode="url" met je eigen validatie.
Nederlandse adressen valideren
Eén regex voor straat, huisnummer en toevoeging wordt snel te streng. Een straatnaam kan apostrofs, accenten, cijfers en koppeltekens bevatten. Een patroon dat alleen a-z accepteert, wijst daardoor normale invoer af.
Splits de invoer liever in straat, huisnummer, huisletter en huisnummertoevoeging. Onderstaande veldcontrole sluit voor de nummerdelen aan op de BAG-catalogus: een huisnummer van 1 tot en met 99999, een optionele letter en een optionele alfanumerieke toevoeging van maximaal vier tekens. Geef de waarden als strings door.
function validateAddressFields({ street, houseNumber, houseLetter = '', addition = '' }) {
if (![street, houseNumber, houseLetter, addition].every(value => typeof value === 'string')) return false;
if ([street, houseNumber, houseLetter, addition].some(value => /[\u0000-\u001f\u007f]/u.test(value))) return false;
return street.trim().length > 0
&& street.trim().length <= 80
&& /^[1-9][0-9]{0,4}$/.test(houseNumber)
&& /^(?:[a-zA-Z])?$/.test(houseLetter)
&& /^[a-zA-Z0-9]{0,4}$/.test(addition);
}
Een straat zoals 's-Gravenweg, 2e Egelantiersdwarsstraat of Curaçaostraat wordt niet afgekeurd vanwege de naam. Bij huisnummer 12 met letter A horen "12" en "A" in aparte velden. De straatcontrole is bewust alleen een controle op aanwezigheid, lengte en ongewenste controletekens.
Deze functie controleert geen volledig adres: postcode en woonplaats zijn hier niet opgenomen en ook de combinatie van de velden wordt niet geverifieerd. Gebruik daarvoor een actuele adresbron zoals de BAG. Niet iedere denkbare combinatie van afzonderlijk correcte velden is een toegekend adres.
Nederlandse postcodes valideren
Een eenvoudige vormcontrole kan met vier cijfers, waarvan de eerste niet nul is, en twee letters. Tussen cijfers en letters staan nul of één gewone spaties. Gebruik hier geen \s: dat accepteert ook tabs en regeleinden.
function validateZipcode(value) {
if (typeof value !== 'string') return false;
return /^[1-9][0-9]{3} ?[a-z]{2}$/i.test(value.trim());
}
Voorbeelden die passen: 1234AB en 1234 ab. Hoofdletters zijn voor deze controle niet nodig; bij het opslaan kun je normaliseren. Spaties rondom de volledige invoer worden met trim() verwijderd.
Dit is uitdrukkelijk een patrooncontrole, geen lijst van uitgegeven postcodes. Ook een niet-uitgegeven lettercombinatie kan slagen. Volgens de BAG-definitie hoort een postcode bij een bepaalde combinatie van straatnaam en huisnummer. Controleer die combinatie via actuele adresgegevens wanneer dat voor je toepassing nodig is.
Nederlandse telefoonnummers valideren
Een telefoonnummer is meer dan “vast of mobiel”. Er zijn ook bedrijfsnummers, informatienummers en korte nummers. Zelf alle toegestane reeksen in één regex bijhouden is foutgevoelig; de ACM beheert verschillende nummercategorieën.
Voor een regulier contactnummer gebruik ik liever libphonenumber-js met de uitgebreide max-metadata. Installeer het met npm install libphonenumber-js. Het volgende voorbeeld is gecontroleerd met versie 1.13.13.
import { parsePhoneNumberFromString } from 'libphonenumber-js/max';
function validatePhone(value) {
if (typeof value !== 'string') return false;
const phone = parsePhoneNumberFromString(value.trim(), {
defaultCountry: 'NL',
extract: false
});
return phone?.country === 'NL' && phone.isValid() && !phone.ext;
}
De standaardregio is Nederland. Daardoor kunnen 06 1234 5678, +31 6 12345678 en 0031 6 12345678 als Nederlandse invoer worden gelezen. extract: false voorkomt dat de parser alleen een telefoonnummer uit een langere zin haalt. Buitenlandse nummers en extensies worden in dit formulier bewust afgewezen.
Deze controle is niet bedoeld voor ieder kort service- of noodnummer. Houd de nummermetadata actueel en kies de toegestane landen en nummertypen op basis van je formulier. Een nummer dat volgens de metadata geldig is, hoeft niet uitgegeven, bereikbaar of van de gebruiker te zijn. Daarvoor heb je bijvoorbeeld verificatie per sms of telefoongesprek nodig.
IBAN bankrekeningnummer valideren
De oude regex controleerde alleen een los patroon van letters en cijfers. Dat is onvoldoende: de lengte en structuur verschillen per land en een IBAN bevat controlecijfers. Een Nederlands IBAN bestaat uit NL, twee controlecijfers, vier letters voor de bankcode en tien cijfers. Zie de uitleg van Betaalvereniging Nederland.
Dit voorbeeld controleert specifiek de Nederlandse structuur én de modulo-97-controle. De letters worden omgezet naar getallen, met A als 10 tot en met Z als 35. Door de rest cijfer voor cijfer te berekenen, voorkomen we precisieverlies door een extreem groot JavaScript-getal.
function validateDutchIban(value) {
if (typeof value !== 'string') return false;
const iban = value.replace(/ /g, '').toUpperCase();
if (/[^A-Z0-9]/.test(iban) || !/^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/.test(iban)) return false;
const digits = (iban.slice(4) + iban.slice(0, 4))
.replace(/[A-Z]/g, letter => String(letter.charCodeAt(0) - 55));
let remainder = 0;
for (const digit of digits) {
remainder = (remainder * 10 + Number(digit)) % 97;
}
return remainder === 1;
}
NL91 ABNA 0417 1643 00 slaagt; dezelfde invoer met controlecijfers 92 niet. Gewone spaties en kleine letters worden genormaliseerd. Tabs en regeleinden worden niet stilzwijgend verwijderd. De controle bewijst niet dat de rekening bestaat, actief is of bij een bepaalde naam hoort.
Gebruik deze Nederlandse demonstratie niet als algemene IBAN-validator. Voor internationale invoer heb je de landstructuren uit de officiële IBAN Registry nodig, naast de controlecijfers. Een onderhouden IBAN-library kan die combinatie verzorgen. De Swift-documentatie beschrijft de MOD-97-10-basis voor de controlecijfers.
Test ook wat je wilt afwijzen
Een voorbeeld dat alleen met één geldige waarde is getest, geeft weinig vertrouwen. Test plustekens en lange domeinextensies bij e-mail, witruimte en ongewenste protocollen bij URL's, internationale telefoonnotaties en een IBAN met één verkeerd cijfer. Controleer ook lege invoer, onjuiste datatypen en waarden net buiten de toegestane lengte.
Gebruik bij boolean-regexvalidatie geen g-flag: herhaald test() op hetzelfde globale regexobject kan door lastIndex een ander resultaat geven. En voer dezelfde inhoudelijke validatie ook op de server uit. Browservalidatie helpt de gebruiker, maar een request kan buiten het formulier om worden verstuurd.

Fijn overzicht! Misschien ook handig om een regex toe te voegen voor validatie van kentekens of BSN-nummers?
Goede uitleg, alleen let op: de IBAN-validatie checkt alleen het patroon, niet of het rekeningnummer echt bestaat of bij een bestaande bank hoort.
Erg handige blog Jasper! De postcode- en e-mailvalidatie kopieer ik vaak.