KVK Number Validation: Rules, Format, and Common Mistakes
Technical deep-dive into the KVK number format: validation rules, difference from branch numbers, regex patterns, and common programming mistakes.
KVK Number Validation: Rules, Format, and Common Mistakes
The KVK number looks simple: eight digits. But in practice, a surprising amount goes wrong when processing KVK numbers in software. From disappearing leading zeros to confusion with branch numbers — in this article we cover all the technical details you need to know.
The KVK number format
A KVK number (also called a dossier number) has the following properties:
- Exactly 8 digits long
- Only numeric characters (0-9)
- Can start with one or more zeros
- Assigned at registration and never changes
- Unique per legal entity
Examples of valid KVK numbers:
12345678
01234567
00123456
80001234
Examples of invalid numbers:
1234567 // Too short (7 digits)
123456789 // Too long (9 digits)
1234567A // Contains a letter
12 345 678 // Contains spaces (unless you strip them)
KVK number vs. branch number
This is one of the most common sources of confusion. The Netherlands has two types of identification numbers in the trade register:
KVK number (dossier number)
- 8 digits
- Identifies the legal entity
- One per company
- Example:
12345678
Branch number (vestigingsnummer)
- 12 digits
- Identifies a specific branch location
- A company can have multiple branches
- Example:
000012345678
A company with three locations therefore has:
- 1 KVK number:
12345678 - 3 branch numbers:
000012345678,000012345679,000012345680
If your system needs to support both types, validate based on length:
function identifyNumberType(input) {
const cleaned = input.replace(/\D/g, '');
if (cleaned.length === 8) {
return { type: 'kvk', value: cleaned };
} else if (cleaned.length === 12) {
return { type: 'branch', value: cleaned };
} else {
return { type: 'invalid', value: null };
}
}
Client-side validation with regex
A solid regex for KVK number validation:
// Basic: exactly 8 digits
const KVK_REGEX = /^\d{8}$/;
// With optional spaces and dots (which you strip out)
function validateKvkNumber(input) {
if (!input || typeof input !== 'string') {
return { valid: false, error: 'Please enter a KVK number' };
}
// Remove spaces, dots, and hyphens
const cleaned = input.replace(/[\s.\-]/g, '');
if (!KVK_REGEX.test(cleaned)) {
if (cleaned.length < 8) {
return { valid: false, error: 'KVK number is too short (8 digits required)' };
}
if (cleaned.length > 8) {
return { valid: false, error: 'KVK number is too long (8 digits required)' };
}
return { valid: false, error: 'KVK number may only contain digits' };
}
// Additional check: not all zeros
if (cleaned === '00000000') {
return { valid: false, error: 'Invalid KVK number' };
}
return { valid: true, value: cleaned };
}
Python example
import re
def validate_kvk_number(kvk_number: str) -> tuple[bool, str]:
"""Validate a KVK number format."""
if not kvk_number:
return False, "Please enter a KVK number"
cleaned = re.sub(r'[\s.\-]', '', kvk_number)
if not re.match(r'^\d{8}$', cleaned):
return False, f"Invalid format: '{kvk_number}' (expected 8 digits)"
if cleaned == '00000000':
return False, "Invalid KVK number"
return True, cleaned
Common mistakes
1. Storing as an integer
The most common mistake. If you store a KVK number as INT or BIGINT in your database, the leading zeros disappear:
-- WRONG: leading zeros disappear
CREATE TABLE companies (
kvk_number INT PRIMARY KEY -- 01234567 becomes 1234567
);
-- CORRECT: use a text field
CREATE TABLE companies (
kvk_number CHAR(8) PRIMARY KEY -- 01234567 stays 01234567
);
In JavaScript, this problem is even more subtle:
// WRONG: interpreted as octal in some contexts
const kvk = 01234567; // Not what you expect!
// CORRECT: always as a string
const kvk = "01234567";
// WRONG: JSON.parse can cause issues if the source is unstructured
// CORRECT: ensure your API always returns strings for KVK numbers
2. No input sanitization
Users enter KVK numbers in all sorts of ways:
12345678 // Correct
12.34.56.78 // With dots
12 345 678 // With spaces
KVK: 12345678 // With prefix
012-345-678 // With hyphens and 9 characters
Always build in a sanitization step:
function sanitizeKvkInput(input) {
// Remove everything except digits
return input.replace(/\D/g, '').padStart(8, '0').slice(0, 8);
}
Note: the padStart above is only safe if you are certain the input is a KVK number that lost its leading zeros. In most cases, you should show an error message for a number that is too short rather than padding it.
3. Not verifying server-side
Client-side validation only checks the format. It says nothing about whether the number actually exists in the trade register. A number like 99999999 passes the regex check but may not belong to any company.
Always verify server-side via an API call:
async function verifyKvkNumber(kvkNumber) {
const formatCheck = validateKvkNumber(kvkNumber);
if (!formatCheck.valid) {
return { verified: false, error: formatCheck.error };
}
try {
const response = await fetch(
`https://api.kvkbase.nl/api/v1/lookup/${formatCheck.value}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
if (response.status === 404) {
return { verified: false, error: 'KVK number not found' };
}
const data = await response.json();
return {
verified: true,
company: data.tradeName,
isActive: data.isActive
};
} catch (error) {
return { verified: false, error: 'Verification failed, please try again later' };
}
}
4. Not distinguishing active and inactive companies
A KVK number can belong to a company that has been deregistered. This is crucial for invoicing and contracts. Always check the status:
const result = await verifyKvkNumber('12345678');
if (result.verified && !result.isActive) {
showWarning('This company has been deregistered from the KVK');
}
5. No user-facing error messages
When validation fails, provide a clear, specific error message. “Invalid number” is not enough. Tell the user what is wrong and what is expected.
Database design recommendations
CREATE TABLE companies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
kvk_number CHAR(8) NOT NULL UNIQUE,
vestiging_number CHAR(12),
trade_name VARCHAR(255) NOT NULL,
is_active BOOLEAN DEFAULT true,
verified_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT valid_kvk_format CHECK (kvk_number ~ '^\d{8}$'),
CONSTRAINT valid_vestiging_format
CHECK (vestiging_number IS NULL OR vestiging_number ~ '^\d{12}$')
);
CREATE INDEX idx_companies_kvk ON companies(kvk_number);
Conclusion
KVK number validation is a small part of your application, but mistakes here have outsized consequences. By always storing the number as a string, sanitizing input, validating both client-side and server-side, and checking the company status, you prevent the most common problems.
With KVKBase you can combine format validation and existence checking in a single API call, so you know the number is not only valid but actually belongs to an (active) company.