DEV Community

Cover image for I got tired of hand-rolling PII redaction for logs, so I built it into a small text utility package
Nicolas Alkhoury
Nicolas Alkhoury

Posted on

I got tired of hand-rolling PII redaction for logs, so I built it into a small text utility package

Every project eventually hits the same problem: a user pastes their email into a support ticket, or a phone number ends up in a log line, and now you're writing a one-off regex to mask it before it hits storage. You reach for a library — and the options are either a full NLP-based PII detector (Presidio, and friends) that's way more than you need, or a paid API. There's no small, dependency-free "just mask the obvious stuff" option.

So I added one to textConvert, a small TypeScript text-utility library I maintain:

import { redact } from 'textconvert';

redact('Contact me at jordan@example.com or 555-123-4567');
// 'Contact me at jo**************** or 55**********'
Enter fullscreen mode Exit fullscreen mode

It scans free-form text — not just a single known value — and masks every email and phone number it finds, in place, leaving everything else untouched.

How it works

redact isn't a new detector bolted on from scratch — it's composed from two other functions already in the library:

import { extractEmails, maskText } from 'textconvert';

// Find PII embedded in a block of text (not just validate one known string)
extractEmails('Reach us at hello@example.com or support@example.org.');
// ['hello@example.com', 'support@example.org']

// Partially mask a string for display
maskText('4111111111111234', { visibleEnd: 4 });
// '************1234'
Enter fullscreen mode Exit fullscreen mode

redact finds matches with extractEmails (and an equivalent internal scanner for phone numbers), then masks each one with maskText. You can scope it to one type, or customize the mask character:

redact('Email: jordan@example.com', { types: ['email'] });
// 'Email: jo****************'

redact('Email: jordan@example.com', { maskChar: '#' });
// 'Email: jo################'
Enter fullscreen mode Exit fullscreen mode

The rest of the toolkit

redact is the newest piece, but the library covers the usual text-utility ground too — case conversion, validation, text analysis:

import { camelCase, slugify, isEmail, isUrl, truncate, getTextStats } from 'textconvert';

camelCase('hello world'); // 'helloWorld'
slugify('Café Résumé Review'); // 'cafe-resume-review'
isEmail('user@example.com'); // true
truncate('The quick brown fox jumps over the lazy dog', 20); // 'The quick brown f...'
getTextStats('Hello world! This is a test.'); // { wordCount: 6, sentenceCount: 2, readingTimeFormatted: '2 sec', ... }
Enter fullscreen mode Exit fullscreen mode

Why bundle instead of composing best-in-class libraries?

Honestly — for case conversion or general validation, the individual specialist libraries (change-case, validator.js) are more mature and more thoroughly battle-tested than any single-maintainer bundle can claim to be, and I'd rather say that upfront than pretend otherwise. If you need exhaustive validation edge-case coverage, those projects will serve you better.

Where textConvert earns its place is smaller projects that need a handful of text utilities — including the PII-masking piece nothing else covers at this size — without stitching together several installs and TypeScript definition files. One import, one consistent API, zero runtime dependencies to audit.

Try it

npm install textconvert
Enter fullscreen mode Exit fullscreen mode

MIT-licensed and open to contributions — several issues are tagged good first issue if you want to add a function. Feedback welcome, especially on redact — it's brand new.

Top comments (0)