/Guide

Add a custom identifier /

Your case numbers are personal data too. Teach the engine their shape.

The problem

The 27 built-in types are the identifiers a European document is likely to carry. They are not the identifiers your system issues: a case reference, a policy number, a patient record ID, an internal employee number. Those are personal data in exactly the sense that matters — they single out one person — and no general engine can guess their format.

Register a pattern

custom.py
from euredact import EuRedact

sdk = EuRedact()
sdk.add_custom_pattern("CASE_REF", r"CASE-\d{8}")

sdk.redact("See CASE-20260401 for details", countries=["NL", "BE"]).redacted_text
# "See [CASE_REF] for details"

The name you give becomes the label in the output, so pick the one your own team already uses. Custom patterns run regardless of the countries parameter — an internal identifier has no nationality.

Register it per instance, not globally

Patterns live on the instance they were added to. That is what makes one EuRedact()per tenant the right default: a pattern registered for one customer cannot leak into another customer’s results, and neither can the result cache or the referential-integrity labels.

The module-level redact() uses a shared singleton, which is convenient for a script and the wrong thing for a multi-tenant service.

Writing a pattern that does not over-match

A custom pattern has no checksum behind it, so precision is entirely in the regex. Two habits pay for themselves.

Anchor the shape. CASE-\d{8} is a claim about length. CASE-\d+ is a claim about nothing, and it will swallow the year in CASE-2026 onwards.

Include the distinguishing prefix. A bare eight-digit pattern matches dates, order numbers and postal codes. The literal CASE- is what makes the match mean something, and it costs nothing.

Where it sits in the ordering

When two patterns claim the same span the engine resolves it in a fixed order: validated patterns, then custom patterns, then regex-only ones.

So a value that passes a real checksum keeps its built-in type even if your pattern also matches it — a custom rule cannot relabel a valid IBAN — while your pattern does win against a built-in regex-only guess. If a custom type is being shadowed by something built in, that is the ordering, and the fix is a narrower pattern rather than a broader one.