Normalize Dutch Company Names for Reliable Matching
KVKBase Team

Normalize Dutch Company Names for Reliable Matching

B.V., BV, b.v. and 'De Gouden Korst' vs 'Gouden Korst, De': learn how to normalize Dutch company names before matching, and when to stop and use the KVK number.

kvkdata qualitymatchingapi

You import a spreadsheet with 4,000 customers and want to link each one to the Trade Register. Row 17 says “Bakkerij De Gouden Korst BV”, row 912 says “Gouden Korst, Bakkerij De B.V.” and row 2,301 says “bakkerij de gouden korst b.v. (Zwolle)”. To a person these are one company. To a database they are three strings, and a plain equality check finds none of them.

This article shows how to normalize Dutch company names so that matching works for the easy cases, and how to recognise the cases where no amount of string cleaning will save you.

Why Dutch company names do not compare cleanly

A Dutch company name carries more noise than most. The legal form is part of the registered name, so it is written as “B.V.”, “BV”, “b.v.” or “besloten vennootschap” depending on who typed it. Articles move around: “De Gouden Korst” becomes “Gouden Korst, De” in some systems. Punctuation and spacing vary, and so do accents and the ampersand.

On top of that, a company has a statutory name and often one or more trade names, as explained on our page about trade name versus statutory name. A customer will usually give you the trade name, while the register matches best on the statutory one. Normalization cannot bridge that gap by itself, but it removes everything else, so what remains is a real difference.

The goal is not a pretty name. It is a key: a string that is the same for every spelling of the same name. You compute it on both sides, your data and the register data, and compare keys instead of raw names.

A normalization function that works

Start with the steps that are always safe: lowercase, strip accents, replace & with en or and, and remove punctuation. Then collapse whitespace. Only after that do you remove legal-form suffixes, because the suffix is easier to recognise once the dots are gone.

import re
import unicodedata

LEGAL_FORMS = {
    "bv", "nv", "vof", "cv", "maatschap", "stichting",
    "vereniging", "coöperatie", "cooperatie", "eenmanszaak",
    "besloten vennootschap", "naamloze vennootschap",
}

def name_key(name: str) -> str:
    s = unicodedata.normalize("NFKD", name.lower())
    s = "".join(c for c in s if not unicodedata.combining(c))
    s = s.replace("&", " en ")
    s = re.sub(r"\(.*?\)", " ", s)        # drop "(Zwolle)" notes
    s = re.sub(r"[^\w\s]", "", s)          # drop dots, commas, hyphens
    words = [w for w in s.split() if w not in LEGAL_FORMS]
    return " ".join(sorted(words))        # makes word order irrelevant

Sorting the words is a blunt trick, but it makes “Gouden Korst, De” and “De Gouden Korst” produce the same key. The price is that “Jansen Bouw” and “Bouw Jansen” collapse into one key as well. That is a trade-off you choose on purpose, so use it for candidate generation, not for final decisions.

Be careful with the suffix list. Remove a legal form only at the start or the end of the name, never in the middle. A company called “Stichting Beheer Vereniging Noord” is not helped by deleting every legal word it contains. A stricter version checks only the first and last token.

Use the key to find candidates, not to decide

Once you have keys, matching becomes a lookup. Search the register for the cleaned name, compute the key for each hit and compare. Our search endpoint returns the name, city and active status for every result, which is exactly what you need for a second check.

import requests

HEADERS = {"Authorization": "Bearer your_api_key"}

def candidates(raw_name: str, city: str | None = None) -> list[dict]:
    r = requests.get(
        "https://api.kvkbase.nl/v1/search",
        params={"q": raw_name, "pageSize": 20},
        headers=HEADERS,
        timeout=10,
    )
    r.raise_for_status()
    wanted = name_key(raw_name)
    hits = [c for c in r.json()["results"]
            if c["isActive"] and name_key(c["name"]) == wanted]
    if city:
        hits = [c for c in hits if c["city"].lower() == city.lower()]
    return hits

Then sort the outcome into three buckets. Exactly one hit: link it automatically and store the KVK number. Several hits: show them to a person with the city next to each name. No hits: try a looser search with fewer words, and if that still fails, send the row to manual review.

The important habit is the last step of the first bucket. Once a row has a KVK number, drop the name as an identifier. The number is stable and unique, while the name is a label that changes when the company rebrands.

Where normalization stops helping

Some mismatches no string function can fix. A customer who types “Jansen” for “Jansen Installatietechniek Midden-Nederland B.V.” gives you a prefix, not a name. A holding company and its operating company often share most of their words and differ in one: “Beheer”. A rebranded company may have no word in common with its old name at all.

For these, add a second signal instead of a cleverer algorithm. A postcode, a city, an email domain or a phone number narrows twenty hits to one far better than any edit distance. If you still have two candidates after that, a human decision is cheaper than a wrong merge. Merging two customers by mistake is much harder to undo than leaving a duplicate for later.

It also helps to keep the raw input next to the match. Store the original string, the key you computed, the KVK number you linked and how you got there (automatic, or confirmed by a person). When a match turns out wrong six months later, you can see why.

Clean up what you already have

If your database is already full of loose names, run the same key over it. Group rows by key, and you will see the duplicate clusters at once. That is the first step of the cleanup described in deduplicating companies in your CRM. The more rows you attach a KVK number to, the less you rely on name matching at all, and the basics of that lookup are covered in our complete guide to KVK number lookup.

Want to try it? Create a KVKBase account, take twenty rows from your own customer list and run them through the search endpoint to see how many link cleanly.