/SDK reference

Node.js SDK /

Zero-dependency PII redaction for Node.js. 86KB package, ~1.6ms per page.

Installation

Terminal
$ npm install euredact

Upgrading from 0.6.0. Four changes in 0.7.0 can affect your code:

  • Every credential is SECRET; CREDENTIAL is now a legacy alias that resolves to it. The Irish PPSN, the Austrian SVNr and the UK National Insurance number are SSN, not NATIONAL_ID.
  • A span the cloud model found reports confidence null instead of "high"; filtering on "high" keeps checksum-backed rules detections only.
  • A daily-quota 429 raises QuotaExceededError at once instead of after every retry.
  • A cloud base URL that is not https is refused, except for localhost.

Which option do I need?

Start here when you know the task but not the option name. Every name is the camelCase form of the Python one.

I want to…Use
redact a document with no further setupredact(text)
restrict scope to the countries I operate incountries: ["NL", "BE"]
keep detection wide but resolve ambiguitycountryHint: ["DE"]
send a prompt to an LLM and restore the replytokenize: true, then restore()
keep relationships visible across a whole sessionreferentialIntegrity: true
never redact my own company name or addressesallowlist: [...]
never redact anything at my own domainallowlistDomains: ["acme.be"]
audit what the allowlist kept in the documentresult.exempted
catch person names, employers, job titles, diagnosesredactAsync(text, { mode: "cloud" })
include dates of birthdetectDates: true
redact a document too large for one callcontext + chunkOffset
detect an identifier the engine does not knowaddCustomPattern()
isolate tenants from each otherone new EuRedact() each
free PII held in memoryclear()

redact()

Main entry point. Redact PII from a text string. Returns a RedactResult with the cleaned text and a list of detections.

Signature
function redact(
  text: string,
  options?: RedactOptions
): RedactResult
ParameterTypeDefaultDescription
textstring—The input text to redact.
countriesstring[]undefinedCountry codes (e.g. ["NL", "BE"]) the document is declared to belong to. Since 0.3.2 this scores detection rather than gating it: every pattern runs whatever you declare, and a match attributed outside the set comes back with outOfScope true rather than being dropped. Declaring it is still worth 99.8% recall against 99.7% blind. Since 0.5.0 both SDKs score identically on every entity type, so this is the same trade-off the Python SDK makes.
countryHintstring[]undefinedA prior that resolves ambiguity without narrowing scope or flagging anything out of scope — use it when you know the likely origin but do not want outOfScope semantics.
contextDocumentContextundefinedShares country evidence across the chunks of one document, so a chunk with no country signal of its own is still scored with what the rest of the document established. Pair with chunkOffset. Caching is disabled while a context is in use.
chunkOffsetnumber0Where this chunk starts in the whole document. Used only to rebase spans recorded on a context; returned detections are always relative to the text passed in.
modestring"rules""rules" runs locally and synchronously. "cloud" adds the model-only types and must go through redactAsync() — redact() is synchronous and a network call cannot be.
referentialIntegritybooleanfalseReplace PII with consistent labels (ENTITY_1, ENTITY_2, ...) instead of generic type labels. Labels persist on the instance across calls. Cannot be combined with tokenize.
tokenizebooleanfalseReplace each value with a reversible token (EMAIL_K7Q2) and return the mapping in result.tokens, for restore(). Tokens are unique to the call, so nothing is retained. Cannot be combined with referentialIntegrity.
allowliststring[] | nullnullValues never to redact, whole-span and case-insensitive, merged with the instance's list. Structured identifiers also match across spacing and hyphenation. Applies to cloud-tier types too.
allowlistDomainsstring[] | nullnullDomains whose addresses are never redacted, e.g. ["acme.be"]. EMAIL and URL only, subdomains covered, matched on a label boundary — so acme.be does not exempt evilacme.be.
detectDatesbooleanfalseInclude DOB and date-of-death detections. Off by default.
cachebooleantrueEnable result caching for faster subsequent calls.

Return value

A RedactResult object with:

ParameterTypeDefaultDescription
redactedTextstring—The text with PII replaced by labels like [NATIONAL_ID], [BANK_ACCOUNT], etc.
detectionsDetection[]—Array of detected PII entities with type, value, position, and country.
sourcestring—Source identifier for the redaction engine.
degradedboolean—Whether the result was produced in a degraded mode.
inferredCountries[string, number][]—New in 0.3.2. The countries the engine concluded the document belongs to, each with a score in 0-1, most likely first. Populated whether or not you passed countries.
evidenceCountryEvidence[]—New in 0.3.2. The individual signals behind inferredCountries, so an attribution can be audited rather than taken on faith.
tokensRecord<string, string>—Token to original value. Populated only with tokenize: true, and the only copy that exists — pass it to restore(), and drop it when you no longer need the values back.
exemptedExemption[]—Every span the allowlist kept, with the rule that matched and whether it was a value or a domain rule. An exemption leaves a direct identifier in a document that otherwise claims to be redacted, so it is reported rather than silent.
detectionMode"declared" | "inferred"—New in 0.3.2. "declared" when you passed countries, "inferred" otherwise (including when you passed only countryHint).

Detection

Each detection in the array contains:

ParameterTypeDefaultDescription
entityTypeEntityType—The type of PII detected (enum value).
startnumber—Start character index in the original text.
endnumber—End character index in the original text.
textstring—The original PII text that was detected.
sourceDetectionSource—The detection engine that found this entity.
countrystring | null—The country code associated with this detection, or null.
confidencestring—The verdict on this identifier. "high" means a checksum or an unambiguous format confirmed it; "low" means the span was recognised and not confirmed — typically a labelled identifier whose check digit failed, which is masked anyway rather than printed in full. Filter on this, not on countryConfidence, when you need confirmed identifiers only: const strict = result.detections.filter((d) => d.confidence === "high" && !d.outOfScope).
countryConfidencenumber—New in 0.3.2. How strongly the document supports the country on this detection, 0-1. Separate from confidence, which is about the match itself: a pattern can fire unambiguously while its country attribution stays a guess.
outOfScopeboolean—New in 0.3.2. True when the detection was attributed to a country outside the countries you declared. It is still redacted and still returned — filter on this field if you only want in-scope entities.

CountryEvidence

One signal supporting a country attribution. New in 0.3.2.

ParameterTypeDefaultDescription
countrystring—The ISO country code this signal points at.
sourcestring—What produced the signal, e.g. "ibanPrefix", "phonePrefix", "tld".
logOddsnumber—How much this signal moves the score. Signals accumulate, so several weak ones can outweigh a single strong one.
span[number, number]—Where in the text the signal was found. Offsets are absolute when chunkOffset is passed.

DocumentContext

New in 0.3.2. Country inference reads signals out of the text, so a document processed in chunks loses accuracy at exactly the chunks that carry no signal of their own — a page of bare reference numbers between two pages full of Dutch addresses. A DocumentContext carries the evidence across chunks so the whole document is scored as one.

Chunked document
import { DocumentContext, redact } from "euredact";

const ctx = new DocumentContext();
let offset = 0;
for (const chunk of chunks) {
  const result = redact(chunk, { context: ctx, chunkOffset: offset });
  offset += chunk.length;
  console.log(result.redactedText);
}

// Every signal the document produced, spans pointing into the full text.
console.log(ctx.evidence);

Pass chunkOffset or the evidence spans will all point into the start of the text. Caching is disabled while a context is in use, since the result depends on the document rather than on the chunk alone.

In 0.3.3 evidence became a getter, not a method — it was a method alongside size, a getter, on the same class. Read it as ctx.evidence; ctx.evidence() now throws. The Python SDK keeps ctx.evidence(), following its own idiom.

Reuse a context only across chunks of the same document. A context shared between unrelated documents mixes their countries, which cannot cause a miss — a context influences scoring only, never which spans are found — but can attribute a value to the wrong national scheme.

Example

index.ts
import { redact } from "euredact";

const result = redact(
  "Mijn BSN is 111222333 en IBAN NL91ABNA0417164300.",
  { countries: ["NL"] }
);

console.log(result.redactedText);
// "Mijn BSN is [NATIONAL_ID] en IBAN [BANK_ACCOUNT]."

console.log(result.detections);

countries takes an array. Since 0.3.3 a bare string throws a TypeError, on every entry point, in both SDKs. Before that, countries: "NL" was walked character by character into the codes "N" and "L"; neither resolves, so the call declared nothing and every detection came back with outOfScope true while the redacted text still looked correct. A pipeline filtering on that field — which these docs tell you to do — kept none of them. A wrong country code still only warns; a wrong type has no correct reading to fall back on.

redactBatch()

Process multiple texts efficiently. Loads country configs once. Same options as redact(), returns an array of RedactResult.

Signature
function redactBatch(
  texts: string[],
  options?: RedactOptions
): RedactResult[]

Example

batch.ts
import { redactBatch } from "euredact";

const results = redactBatch([
  "BSN 111222333",
  "IBAN DE89370400440532013000",
]);

results.forEach((r) => {
  console.log(r.redactedText);
});

addCustomPattern()

Register a custom regex pattern at runtime. Detected matches will be labeled with the given entity type.

Signature
function addCustomPattern(
  entityType: string,
  pattern: string
): void
ParameterTypeDefaultDescription
entityTypestring—The label to use for matches (e.g. "EMPLOYEE_ID").
patternstring—A regex pattern string to match against input text.

Example

custom.ts
import { addCustomPattern, redact } from "euredact";

addCustomPattern("EMPLOYEE_ID", "EMP-\\d{6}");

const result = redact("Contact EMP-123456 for details");

console.log(result.redactedText);
// "Contact [EMPLOYEE_ID] for details"

availableCountries()

Returns an array of supported ISO country codes.

example.ts
import { availableCountries } from "euredact";

console.log(availableCountries());  // ["AT", "BE", "BG", ...]

restore()

Signature
function restore(text: string, tokens: Record<string, string>): string

Put the original values back into text that derives from a tokenize: trueresult — typically a model’s reply to the tokenized prompt. tokens is result.tokens. Every occurrence is replaced, including a token a model has glued to other characters (EMAIL_P4RTs) — leaving a token behind is the worse failure.

Reversible tokenization

tokenize: true is for text that has to come back — a prompt you send to a model, whose reply has to read as if the values were never gone.

roundTrip.ts
import { redact, restore } from "euredact";

const result = redact(prompt, { countries: ["BE"], tokenize: true });
console.log(result.redactedText);
// "Write an email to Joren at EMAIL_K7Q2 or call PHONE_P4RT about the invoice."

const reply = await callYourLlm(result.redactedText);
console.log(restore(reply, result.tokens));

Within one call the same value gets the same token, so a prompt that names someone twice still reads as one person. Across calls it gets a different one: nothing is retained on the instance, result.tokens is the only copy, and two tokenized documents never reveal that they share a value. That is the opposite retention model from referentialIntegrity, which is why the two cannot be combined. A token is TYPE_ plus four characters from an alphabet with no vowels and no 0/1/I/O.

Allowlist

Values a caller declares are not PII to them— their own organisation’s name, their own addresses. Set it per call, on the instance, or both; the two merge. It applies to cloud-tier types as well.

allowlist.ts
const sdk = new EuRedact({ allowlistDomains: ["acme.be"] });
sdk.redact("Mail jan@acme.be or piet@acme.be", { countries: ["NL"] }).redactedText;
// "Mail jan@acme.be or piet@acme.be"

A structured identifier matches however the document spaces it, so an allowlisted IBAN is exempt whether it is written NL91ABNA0417164300 or NL91 ABNA 0417 1643 00. Free-text types stay literal, because folding would exempt values the caller never listed. Domain rules cover EMAIL and URL only, including subdomains, and match on a label boundary — so acme.be does not exempt evilacme.be. Every exemption is reported in result.exempted, with the rule that matched, so a document that deliberately keeps a direct identifier can be audited.

There are deliberately no wildcards. The allowlist is the only option that turns redaction off, so an over-broad entry fails toward under-redaction and does so silently.

Cloud tier

The cloud tier is in closed alpha. The options below are documented because the SDK accepts them; access is by invitation while the service is in closed testing.

mode: "cloud" adds the types no pattern can match — person names, organisations, job titles, diagnoses. It must go through redactAsync(): redact() is synchronous and a network call cannot be. This is the one structural difference between the two SDKs; everything else is the camelCase form of the same option.

cloud.ts
const result = await redactAsync(text, { countries: ["BE"], mode: "cloud" });

Credentials come from configure({ apiKey, baseUrl }) or from EUREDACT_API_KEY and EUREDACT_BASE_URL. TypeScript has no coref option; the Python SDK accepts one as a reserved, unimplemented argument.

Secret Detection

euRedact automatically detects secrets and API keys using two strategies: known-prefix patterns for popular services (AWS, GitHub, Stripe, OpenAI, Slack, JWT, SendGrid) and an entropy-based fallback that catches generic secrets near context keywords like api_key, token, and secret. Detected secrets are labeled as [SECRET].

secrets.ts
import { redact } from "euredact";

const result = redact(
  "My API key is sk-proj-abc123def456ghi789"
);

console.log(result.redactedText);
// "My API key is [SECRET]"

Performance

~1.6ms
Per page (2,000 chars)
~20,500
Records per second
300-character record
~50KB
Memory per country
86KB
Package size
0
Dependencies

Supported Countries

31 European and EEA countries with country-specific PII patterns.

ATBEBGCHCYCZDEDKEEELESFIFRHRHUIEISITLTLULVMTNLNOPLPTROSESISKUK

Entity Types

These are the 29 types this package emits, across all supported countries.

27 is the Rules Engine layer. The forthcoming AI layer adds 13 more — names, addresses, medical information and the other categories that have no fixed format for a pattern to match, plus the GDPR Article 9 special-category types. What euRedact detects lists all 40 with the layer responsible for each, so a count of 40 elsewhere on this site is not a different number from this one.

[BANK_ACCOUNT][BIC][CREDIT_CARD][PHONE][EMAIL][DOB][DATE_OF_DEATH][NATIONAL_ID][SSN][TAX_ID][PASSPORT][DRIVERS_LICENSE][LICENSE_PLATE][VIN][VAT][POSTAL_CODE][IP_ADDRESS][IPV6_ADDRESS][MAC_ADDRESS][HEALTH_INSURANCE][HEALTHCARE_PROVIDER][CHAMBER_OF_COMMERCE][IMEI][GPS_COORDINATES][UUID][SOCIAL_HANDLE][SECRET][RESIDENCE_PERMIT][INTERNAL_ID]

View source on GitHub

Browse the code, report issues, or contribute.

GitHubarrow_outward