Regex for people who hate regex: 8 patterns that save your week
Practical guide to regex patterns for validating Spanish data without writing code.
Regular expressions have a fearsome reputation. You see a pattern like ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ and you want to shut the browser tab. The reality is simpler: regex is just a language for saying "find this" or "accept that". Once you learn eight basic patterns, you'll handle 90% of everyday validation tasks.
This guide teaches you those eight patterns, how they work, where to test them, how to copy them into Sheets, Docs or your code editor, and how to integrate them into your workflow. No unnecessary jargon.
Three concepts you need
Before the patterns, three sentences.
Character classes. Square brackets [a-z] mean "any lowercase letter". [0-9] means "any digit". [a-zA-Z0-9] means "letter or number". That's it.
Quantifiers. They control how many times something repeats.
+= one or more times*= zero or more times{n}= exactly n times{n,m}= between n and m times?= zero or one (optional)
Anchors. They mark where something starts or ends.
^= start of the string$= end of the string
Example: ^[A-Z] means "begins with a capital letter". [0-9]$ means "ends with a number". That's all you need to understand every pattern that follows.
Four concepts that save time
Once you grasp the three basics, these four lift you above writing tangled patterns.
Capture groups: (pattern)
Parentheses group parts of your pattern. They serve two purposes: applying quantifiers to multiple characters and reusing what you captured.
Example: (\+34)? means "the entire +34 group is optional". Without parentheses, +34? would mean "3 followed by zero or one 4", which isn't what you want.
In Sheets, REGEXEXTRACT(text, "(pattern)") returns only what's inside the parenthesis. If you write REGEXEXTRACT("aida@apferrer.com", "^([a-z]+)@"), you get "aida".
Lookahead: (?=pattern)
Looks ahead without consuming characters. Validates that something comes next without including it in the match.
Example: ^(?=.*[A-Z]) in a password means "check that somewhere there's a capital letter", but doesn't extract that letter. Often used in password validation.
Backreferences: \1, \2
Reuses what you captured. If you captured ([a-z]+) in your pattern (first group), you can later write \1 to refer to exactly what matched.
Example: ^([a-z])\1+$ finds words like "aaa", "bbb" (the same letter repeated). The \1 says "whatever group 1 captured, again".
In Sheets it's not directly available, but in Apps Script it is.
Flags: /pattern/flags
Modify the global behaviour of the pattern. Most common:
i= case insensitiveg= applies to all matches, not just the firstm= multiline mode (^ and $ consider line breaks)
In Google Sheets, some flags are supported in REGEXMATCH(..., "pattern"), but it depends on the function.
Where to test before you use it
Use regex101.com. It's free, no registration, and explains each part as you type.
Process: Paste the pattern in the top box, select PCRE2 from the left dropdown, type test text below, and you'll see immediately whether it matches or not. If something fails, regex101 marks it red. You gain confidence before putting it in Sheets.
Alternatively, if you use VS Code, install RegEx Previewer and test directly in your editor.
Pattern 1: Email
What it validates: Basic email format (user@domain.extension).
Regex pattern:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
Breakdown: Starts (^), one or more alphanumeric or special characters ([a-zA-Z0-9._%+-]+), at symbol (@), domain name ([a-zA-Z0-9.-]+), literal dot (\.), extension of at least 2 letters ([a-zA-Z]{2,}), ends ($).
Valid examples:
aida@apferrer.comcontacto.legal@empresa-ejemplo.esusuario+etiqueta@dominio.co.uk
Invalid examples:
aida@apferrer(missing extension)@apferrer.com(missing user)aida apferrer.com(missing at symbol)
Real-world use cases:
TechStart Solutions receives registrations for webinars. They validate each email format before sending confirmation. REGEXMATCH filters out approximately 15% of duplicate or mistyped entries.
Administrative Consultancy manages client contacts in Sheets. They use this regex to highlight rows in red if an email looks invalid, so the sales team can call before sending proposals.
E-learning Platform validates emails on user registration. This pattern rejects
usuario@.comandusuario@dominioon the client side, avoiding failed sends.
Variants by strictness:
- More flexible (accepts more formats):
^.+@.+\..+$(very permissive, just checks @, at least one character before and after, and a dot) - Recommended: The pattern above
- More strict (rejects some valid):
^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$(no dots or hyphens in user or domain)
False positives and negatives:
- False positive:
test+123@sub-domain.example.co.ukpasses (correct, it's valid). - False negative:
usuario_nombre@dominio.ejemplo.infofails if your pattern doesn't include underscore. Add it:[a-zA-Z0-9._%-]+. - False positive:
test@.com.passes if your pattern is careless. The recommended pattern rejects it.
In Google Sheets:
=REGEXMATCH(A1,"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
Returns TRUE if it's a valid email, FALSE otherwise. To extract just the domain:
=REGEXEXTRACT(A1,"@([a-zA-Z0-9.-]+)")
Returns apferrer.com from aida@apferrer.com.
To replace all dots with hyphens (rarely useful, just an example):
=REGEXREPLACE(A1,"\.","-")
Converts aida.perez@apferrer.com to aida-perez@apferrer-com.
What it doesn't cover: This pattern doesn't validate whether the domain exists or is active. It only checks that it looks like an email. For full validation, you'd need to send a confirmation email or use an email validation service.
Pattern 2: Spanish phone (mobile and landline with +34)
What it validates: Spanish numbers with or without +34 prefix, mobiles or landlines.
Regex pattern:
^(\+34|0034)?[ -]?[6789]\d{2}[ -]?[0-9]{3}[ -]?[0-9]{3}$
Breakdown: Starts (^), optional prefix ((\+34|0034)?), optional spaces or hyphens ([ -]?), first digit 6-9 (mobile or landline), two more digits, optional spaces/hyphens, 3 digits, optional spaces/hyphens, 3 final digits, ends ($).
Valid examples:
+34 666 123 456666123456+34-666-123-456934 123 456(Barcelona landline)0034 912 345 678
Invalid examples:
+34 566 123 456(5 is not a valid start)+34 66 123 456(missing digits: only 8 total)666-12-345(wrong digit structure)
Real-world use cases:
Rural Health Centre takes appointments via a web form. The regex validates contact phone to prevent scheduling with incomplete numbers. Accepts variations because patients type differently: with prefix or without, with spaces or not.
Customs Agency integrates customer data from third parties. They need to normalise: some send
+34 666 111 222, others send666111222. The regex identifies both as valid before processing.Insurance Call Centre validates incoming phone numbers. Rejects numbers starting with 5 (not valid Spanish) and flags those without enough digits.
Variants by strictness:
- More flexible (accepts more spaces):
^[\s\-\+\d()]+$and count digits (very permissive) - Recommended: The pattern above
- More strict (no prefix):
^[6789]\d{8}$(mobile/landline only, no prefix, no spaces)
False positives and negatives:
- False positive:
+34 699 111 222passes (correct, it's valid). - False negative:
+34 (666) 123 456fails because it doesn't account for brackets. To support brackets, add:(\(|\))?to the character class. - False positive:
+34 877 123 456passes if your pattern includes 8 (it's a valid Barcelona landline). That's correct.
In Google Sheets:
=REGEXMATCH(A1,"^(\+34|0034)?[ -]?[6789]\d{2}[ -]?[0-9]{3}[ -]?[0-9]{3}$")
To extract just the digits and discard prefix:
=REGEXEXTRACT(A1,"([6789]\d{2}[ -]?[0-9]{3}[ -]?[0-9]{3})$")
Returns 666 123 456 from +34 666 123 456.
What it doesn't cover: Doesn't check whether the number is active or whether the 666 prefix belongs to a real operator (Vodafone, Orange, Movistar, etc.). Only verifies structure. For true validation, you'd need a service like TrueCaller API.
Pattern 3: Spanish IBAN
What it validates: Spanish IBAN code (ES + 22 numeric characters with internal structure).
Regex pattern:
^ES\d{2}\d{4}\d{4}[\dA-Z]{1,4}\d{7}([A-Z]\d{3})?$
Breakdown: Starts with ES, two check digits, four bank digits, four branch digits, up to 4 alphanumeric characters, 7 account digits, optionally a letter and 3 digits.
Valid examples:
ES9121000418450200051332ES1234567890123456789012
Invalid examples:
ES9121000418450200(incomplete: missing digits)GB9121000418450200051332(starts with GB, not ES)ES912100041845020005(too short)
Real-world use cases:
B2B Payments Platform receives IBANs from suppliers before transfers. This regex rejects invalid formats in the form, preventing bank rejections later that cost money to retry.
Digital Accountancy Firm imports customer data from CSV files. Uses regex to flag IBANs that don't meet the structure before passing to the accounting system.
Payroll Fintech validates worker IBANs. Rejects disallowed characters and wrong length, reducing errors when loading data in bulk.
Variants by strictness:
- More flexible (basic structure only):
^ES\d{22}$(just checks ES + 22 digits) - Recommended: The pattern above
- More strict (with mod-97 validation): Requires Apps Script in Sheets, not just regex
False positives and negatives:
- False positive:
ES1234567890123456789012passes (meets format, though might not be a real account). - False negative:
es9121000418450200051332(lowercase) fails if your pattern requires uppercase. Solution:^[Ee][Ss]or use theiflag.
In Google Sheets:
=REGEXMATCH(A1,"^ES\d{2}\d{4}\d{4}[\dA-Z]{1,4}\d{7}([A-Z]\d{3})?$")
To extract the account number (last 7 digits):
=REGEXEXTRACT(A1,"(\d{7})$")
Returns 0051332 from ES9121000418450200051332.
What it doesn't cover: This pattern doesn't validate the IBAN check digit (requires mod-97 algorithm, which is mathematical, not regex). It's format validation only. For full validation, you'd need a custom function in Apps Script that implements the mod 97 algorithm.
Pattern 4: Spanish postal code
What it validates: 5 digits, true range 01000-52999 (valid Spanish provinces).
Regex pattern:
^(?:0[1-9]|[1-4]\d|5[0-2])\d{3}$
Breakdown: Starts with 0 and 1-9 (01-09), or 1-4 and any digit (10-49), or 5 and 0-2 (50-52), followed by 3 digits. This restricts to 01000-52999, the valid postal codes in Spain.
Valid examples:
28001(Madrid)08002(Barcelona)41001(Seville)46001(Valencia)52001(Melilla)
Invalid examples:
99001(above 52, no such province)00123(00 is not valid)1234(only 4 digits)
Real-world use cases:
Clothing E-commerce validates postal code when processing shipments. Rejects codes outside the Spanish range to prevent fraudulent or mistyped addresses.
Logistics Company receives data from regional warehouses. Uses this regex to group by province (first digit) and spot errors in codes.
Delivery App limits delivery zone. Validates that the postal code is within its operating area, rejecting deliveries outside coverage.
Variants by strictness:
- More flexible (just 5 digits):
^\d{5}$(accepts anything, even non-Spanish codes) - Recommended: The pattern above
- More strict (by exact province):
^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$(same, but more readable)
False positives and negatives:
- False positive:
52001passes (Melilla, valid). - False negative:
052001(with leading zero) fails if you don't account for that. - False positive:
50999passes (Zaragoza, valid).
In Google Sheets:
=REGEXMATCH(A1,"^(?:0[1-9]|[1-4]\d|5[0-2])\d{3}$")
To extract the province (first 2 digits):
=REGEXEXTRACT(A1,"^(\d{2})")
Returns 28 from 28001.
What it doesn't cover: Doesn't verify that the postal code actually exists in Spain, just that the range is valid. For example, 28001 is a valid format (Madrid), but not all combinations exist. For true validation, you'd need a database of actual Spanish postal codes.
Pattern 5: Spanish DNI
What it validates: 8 digits + letter in correct format (doesn't validate the letter mathematically).
Regex pattern (format only, no letter validation):
^\d{8}[TRWAGMYFPDXBNJZSQVHLCKE]$
Breakdown: Exactly 8 digits, followed by one of the 23 valid letters for Spanish DNI (the complete set of letters allowed by the National Police Directorate).
Valid examples (format):
12345678Z98765432T11111111A
Invalid examples:
12345678A(A is not in the valid DNI letter set)1234567Z(only 7 digits)123456789Z(9 digits)
Real-world use cases:
Private Clinic registers patients over the phone. The regex validates DNI while reception notes it down, rejecting invalid letters before saving to the database.
Online Bank checks DNI when opening an account. Though it doesn't validate the letter mathematically, it rejects obvious formats like
12345678@(invalid symbol).Digital Notary imports client data from third parties. Uses regex to flag suspect rows: if there are 8 digits but an invalid letter, a human reviews it.
Variants by strictness:
- More flexible (no letter validation):
^\d{8}[A-Z]$(accepts any capital letter) - Recommended: The pattern above (only valid letters)
- More strict (with letter validation): Requires Apps Script with mod 23 algorithm
False positives and negatives:
- False positive:
12345678Zpasses (format correct, though might not belong to that person). - False negative:
12345678a(lowercase letter) fails. Solution: Add(?i)at the start to ignore case, or convert to uppercase first. - False negative:
12.345.678Z(with dots) fails. Some citizens write it that way.
In Google Sheets:
=REGEXMATCH(A1,"^\d{8}[TRWAGMYFPDXBNJZSQVHLCKE]$")
To extract just the digits:
=REGEXEXTRACT(A1,"^(\d{8})")
Returns 12345678 from 12345678Z.
To validate the letter is correct (needs Apps Script):
=REGEXMATCH(A1,"^\d{8}[TRWAGMYFPDXBNJZSQVHLCKE]$") * (MOD(VALUE(LEFT(A1,8)),23) = FIND(RIGHT(A1,1),"TRWAGMYFPDXBNJZSQVHLCKE")-1)
This formula is complex; better to use Apps Script.
What it doesn't cover: This pattern verifies that the letter is in the valid set, but doesn't calculate whether the letter is correct for those 8 specific digits. The correct algorithm is: divide the 8 digits by 23, the remainder gives the position in the valid letter string. For full validation, you'd need Apps Script with that logic.
Pattern 6: Spanish NIE
What it validates: Foreigner Identification Number (X, Y or Z + 7 digits + letter).
Regex pattern:
^[XYZ]\d{7}[TRWAGMYFPDXBNJZSQVHLCKE]$
Breakdown: Starts with X, Y or Z (NIE category letters), followed by 7 digits (9 total), ends with a valid NIE letter (same set as DNI).
Valid examples (format):
X1234567LY9876543MZ0000001T
Invalid examples:
A1234567L(starts with A, must be X, Y or Z)X123456L(only 6 digits, missing)X12345678L(9 digits, too many)
Real-world use cases:
Immigration Office records applications. The regex validates NIE in real time as the clerk types it, preventing typographical errors.
HR Company validates NIE of foreign workers. Rejects invalid formats before registering with Social Security.
Tax Accountancy Firm imports data on foreign clients. Flags suspect NIE for manual review if they don't meet the pattern.
Variants by strictness:
- More flexible (no letter validation):
^[XYZ]\d{7}[A-Z]$ - Recommended: The pattern above
- More strict (with letter validation): Requires algorithm, similar to DNI
False positives and negatives:
- False positive:
X1234567Lpasses (format correct). - False negative:
x1234567l(lowercase) fails. Solution: Convert to uppercase or use theiflag. - False negative:
X-1234567-L(with hyphens) fails if you don't account for separators.
In Google Sheets:
=REGEXMATCH(A1,"^[XYZ]\d{7}[TRWAGMYFPDXBNJZSQVHLCKE]$")
To extract the starting letter:
=REGEXEXTRACT(A1,"^([XYZ])")
Returns X from X1234567L.
What it doesn't cover: Like DNI, it doesn't validate whether the letter is correct mathematically. Only that it's in the valid set. Mathematical validation of NIE uses the same mod 23 algorithm as DNI.
Pattern 7: Spanish licence plate (current and older format)
What it validates: Spanish vehicle plate in current format (4 numbers + 3 letters) or older format (2 letters + 4 numbers + 2 letters).
Regex pattern (current format):
^[0-9]{4}[A-Z]{3}$
Regex pattern (older format):
^[A-Z]{2}[0-9]{4}[A-Z]{2}$
Regex pattern (both formats combined):
^(?:[0-9]{4}[A-Z]{3}|[A-Z]{2}[0-9]{4}[A-Z]{2})$
Breakdown (combined): Non-capturing group with alternation: either 4 digits + 3 letters (current), or 2 letters + 4 digits + 2 letters (older).
Valid examples:
1234ABC(current)AB1234CD(older)5678XYZ(current)ZZ9999LL(older)
Invalid examples:
123ABC(incomplete: only 3 digits)1234ABCD(excess: 4 letters)AB123CD(older, but only 3 digits)
Real-world use cases:
Online Mechanic Shop receives maintenance requests via form. Validates licence plate before scheduling, rejecting typography that might cause confusion.
Parking App receives licence plates from users entering. Rejects invalid formats to prevent registering fictional vehicles.
Traffic Accountancy imports mulct data. Uses regex to flag suspect licence plates that don't meet the real format, before sending notices.
Variants by strictness:
- More flexible (no format separation):
^[A-Z0-9]{7}$(accepts any 7-character combination) - Recommended: The combined one above
- More strict (current only):
^[0-9]{4}[A-Z]{3}$(rejects older plates)
False positives and negatives:
- False positive:
1234ABCpasses (valid current format). - False negative:
1234 ABC(with space) fails. Some systems insert spaces. Solution:^(?:[0-9]{4} ?[A-Z]{3}|[A-Z]{2} ?[0-9]{4} ?[A-Z]{2})$ - False negative:
1234abc(lowercase letters) fails if you require uppercase.
In Google Sheets (both formats):
=REGEXMATCH(A1,"^(?:[0-9]{4}[A-Z]{3}|[A-Z]{2}[0-9]{4}[A-Z]{2})$")
To extract the numeric digits:
=REGEXEXTRACT(A1,"([0-9]{4})")
Returns 1234 from 1234ABC or AB1234CD.
To determine if current or older format:
=IF(REGEXMATCH(A1,"^[0-9]{4}[A-Z]{3}$"),"Current","Older")
What it doesn't cover: Doesn't check whether the plate is active or registered with the traffic authority. Only verifies structure. For real validation, you'd need to check the DGT (Traffic Authority) database or use an external service.
Pattern 8: Date in dd/mm/yyyy format
What it validates: Dates in Spanish format (day/month/year) with basic range checks (day 01-31, month 01-12).
Regex pattern:
^(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{4}$
Breakdown: Valid day (01-31), slash, valid month (01-12), slash, 4 year digits. Doesn't validate leap years or months with 30 days.
Valid examples:
15/06/202601/01/200031/12/199929/02/2020(leap year, though the pattern doesn't check it)
Invalid examples:
32/13/2026(day 32 and month 13 don't exist)0/1/2026(missing leading zeros)15-06-2026(hyphens instead of slashes)
Real-world use cases:
Medical Appointment Platform validates date of birth. Rejects dates with day 32 or month 13, preventing invalid registrations before saving.
Events App receives event dates. Validates dd/mm/yyyy format to ensure consistency with Spanish users.
Invoicing System validates issue date. Rejects inconsistent formats, guaranteeing all stored data is readable.
Variants by strictness:
- More flexible (any separator):
^\d{2}[/-]\d{2}[/-]\d{4}$(accepts slashes or hyphens) - Recommended: The pattern above (slashes only)
- More strict (with leap year validation): Requires additional logic in Apps Script
False positives and negatives:
- False positive:
31/12/2026passes (December has 31 days, correct). - False positive:
29/02/1900passes (but 1900 wasn't a leap year; the pattern doesn't check). - False negative:
29/02/2020passes (real leap year, correct). - False positive:
31/04/2026passes (April has 30 days, but the pattern doesn't check).
In Google Sheets:
=REGEXMATCH(A1,"^(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{4}$")
To extract just the year:
=REGEXEXTRACT(A1,"(\d{4})$")
Returns 2026 from 15/06/2026.
To replace slashes with hyphens:
=REGEXREPLACE(A1,"/","-")
Converts 15/06/2026 to 15-06-2026.
To validate that it's not a future date (requires additional logic):
=AND(REGEXMATCH(A1,"^(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{4}$"), DATEVALUE(A1) <= TODAY())
What it doesn't cover: This pattern doesn't validate whether the date actually exists (for example, 29/02 in non-leap years, or 31/04 when April has 30 days). Only validates basic day and month range. For true validation, you'd need a function that analyses the month and year.
Debugging regex that doesn't work
You've copied a pattern, tested it on regex101, but in Sheets or your code it doesn't work. Here are the 6 most common problems and how to fix them.
Problem 1: Unescaped characters
A dot . in regex means "any character". If you want a literal dot, you must escape it: \..
Symptom: Your email pattern ^[a-z]+@[a-z]+.[a-z]+$ accepts aida@example!com (! isn't a dot, but . accepts it).
Solution: Escape all special characters: ^[a-z]+@[a-z]+\.[a-z]+$.
Other characters to escape: * + ? [ ] ( ) { } | ^ $ \ (when you want them literal, not special).
Problem 2: Confusing * and +
* allows zero repetitions, + requires at least one.
Symptom: Your pattern [a-z]*@[a-z]+ accepts @dominio.com (zero characters before @, which isn't valid).
Solution: Use + for "at least one": [a-z]+@[a-z]+.
Rule: If you need at least one character, use +. If zero is acceptable (like an optional suffix), use *.
Problem 3: Forgetting anchors
Without ^ and $, the pattern can match anywhere in the string.
Symptom: Your pattern \d{8}[A-Z] without anchors matches "abc12345678Zxyz", extracting 12345678Z from the middle (probably not what you wanted).
Solution: Add anchors: ^\d{8}[A-Z]$.
This guarantees the entire string matches, not just part of it.
Problem 4: Not escaping or misplacing hyphens in character classes
In [a-z], the hyphen connects a range. In [a-z.-], is the hyphen a range or literal?
Symptom: Your pattern [a-zA-Z.-] is read as a range from . to - (ASCII characters 46-45, which doesn't exist).
Solution: Place the hyphen at the end [a-zA-Z.-] or escape it [a-zA-Z\.-].
Correct: [a-zA-Z.-] works (hyphen at end is literal) or [a-zA-Z\.\-] (both escaped).
Problem 5: Confusing REGEXMATCH, REGEXEXTRACT, REGEXREPLACE in Sheets
REGEXMATCH(text, pattern)returns TRUE/FALSEREGEXEXTRACT(text, pattern)returns the matching textREGEXREPLACE(text, pattern, replacement)replaces
Symptom: You write =REGEXMATCH(A1, "[0-9]+") expecting to get the digits, but you only get TRUE.
Solution: Use REGEXEXTRACT: =REGEXEXTRACT(A1, "[0-9]+") returns the digits found.
Problem 6: Case sensitivity when it shouldn't matter
By default, regex is case sensitive.
Symptom: Your pattern ^[a-z]+$ rejects "Aida" (has capital A).
Solution: Include uppercase in the class: ^[a-zA-Z]+$. Or use flag i (case insensitive): some environments support /pattern/i.
In Sheets, there's no direct flag, so include both: [a-zA-Z].
Comparison table: what it covers, what it doesn't, alternatives
| Pattern | What it validates | What it doesn't cover | When it fails | Alternative if you need more |
|---|---|---|---|---|
| user@domain.ext format | Domain exists, account active | test@nonexistent.fake passes |
Email validation service (NeverBounce, ZeroBounce) | |
| Spanish phone | Structure +34/0034, 9 digits | Number is active, real operator | +34 999 999 999 passes (might not exist) |
Phone validation API (Twilio Lookup) |
| Spanish IBAN | Structure ES + 22 chars | Check digit (mod-97), real account | ES1234567890123456789012 passes (might not exist) |
Apps Script with mod-97 algorithm, or IBAN API |
| Postal code | Range 01000-52999 | Code actually exists | 28999 passes (outside Madrid's valid range) |
Database of actual Spanish postal codes |
| DNI | 8 digits + valid letter | Letter is correct (mod 23), real DNI | 12345678Z passes (letter might be wrong) |
Apps Script with mod 23 validation, or civil registry check |
| NIE | X/Y/Z + 7 digits + valid letter | Letter is correct (mod 23), real NIE | X1234567L passes (letter might be wrong) |
Apps Script with mod 23 validation, or immigration database check |
| Licence plate | Current (4+3) or older (2+4+2) format | Plate is active, registered with DGT | 1234ABC passes (might not be registered) |
DGT API check, or licence plate lookup service |
| Date | dd/mm/yyyy format, day/month range | Leap years, real days per month | 31/04/2026 passes (April has 30) |
Native DATE function in Sheets or Apps Script validation |
Common mistakes when copying regex from the internet
1. Unescaped characters. A dot . in regex means "any character". If you want a literal dot, you must escape it: \.. The email pattern does this correctly with \.[a-zA-Z]{2,}.
2. Confusing * and +. * allows zero repetitions, + requires at least one. In email, [a-zA-Z0-9]+@ is correct because you need at least one character before the @. With * you'd accept @domain.com, which isn't valid.
3. Forgetting anchors. Without ^ and $, the pattern can match anywhere in the string. \d{8}[A-Z] without anchors matches "abc12345678Zxyz", which is probably not what you want.
4. Not escaping the hyphen in character classes. In [a-zA-Z.-], the hyphen must be at the end or escaped [a-zA-Z\.-], otherwise it's read as a range.
5. Confusing REGEXMATCH with REGEXEXTRACT. In Sheets:
REGEXMATCH(text, pattern)returns TRUE/FALSEREGEXEXTRACT(text, pattern)returns the matching textREGEXREPLACE(text, pattern, replacement)replaces what matches
6. Overlooking typographical variations. Users type +34 666 123 456, 666123456, +34-666-123-456. A strict pattern rejects valid ones. Solution: allow optional spaces and hyphens: [ -]?.
Copy and paste without fear
Each pattern above is ready to copy. Before using it:
- Open regex101.com.
- Paste the pattern in the top box.
- Select PCRE2 from the left dropdown.
- Type three valid and three invalid examples in the text box.
- Check that regex101 marks the valid ones in green and ignores the invalid ones.
- Copy the pattern into your Sheets or code.
If something breaks, regex101 shows you exactly where. No surprises in production.
The perfection trap
None of these patterns is perfect. The email one doesn't check that the domain exists. DNI doesn't calculate the letter. Date doesn't know about leap years.
That's not a flaw: it's by design. Regex is a tool for format validation, not absolute truth. If you need absolute truth (does this email exist? Is this DNI real? Is this date valid in February?), you need an external service, a database, or a function with mathematical logic.
What regex does well is reject the obvious: a DNI with 7 digits, a licence plate with letters in the number spots, an email without an at symbol. That handles 95% of input errors.
Next step
These eight patterns cover most of the data you'll collect from Spain. You have two paths now.
If you want to automate validation in Sheets, read the article on Google Sheets tricks where we use REGEXMATCH in validation cascades.
If you want to go further and build your own tools, book a session to discuss how to automate your data processes without code or with minimal code.
References
- regex101.com - Interactive editor to test patterns
- MDN: Regular expressions in JavaScript - Official documentation
- Google Sheets: REGEXMATCH help - Regex functions in Sheets

