
Security News
/Company News
Socket Is Sponsoring Composer and Packagist
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.
profanitybuster
Advanced tools
High-performance, TypeScript profanity detection and filtering library with modular language packs.
A high-performance, cross-platform profanity detection and filtering library built for JavaScript/TypeScript applications. Designed with a custom hybrid tree algorithm for optimal performance and modular language packs for efficient bundle management.
ProfanityBuster provides a unified API for detecting, filtering, and sanitizing profane or harmful language in user-generated content. Built specifically for modern web development with seamless integration for Next.js, React, and other JavaScript frameworks.
npm install profanitybuster
# or
yarn add profanitybuster
# or
pnpm add profanitybuster
ESM:
import ProfanityBuster from 'profanitybuster';
const buster = new ProfanityBuster({
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
});
const has = buster.detect('sh1t happens').hasProfanity; // true (confusables handled)
const clean = buster.sanitize('s*h-i t'); // length-preserving masking
CommonJS:
const { ProfanityBuster } = require('profanitybuster');
const buster = new ProfanityBuster({
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
});
console.log(buster.detect('sh1t').hasProfanity);
f**k)1→i, 0→o, @→a)useProfanityBuster for client usagecreateProfanityMiddleware for server-side sanitizationimport { integrations } from 'profanitybuster';
const { useProfanityBuster } = integrations;
function CommentBox() {
const { buster, ready, sanitize } = useProfanityBuster({
preloadLanguages: ['en'],
});
if (!ready) return null;
return <textarea onChange={(e) => (e.currentTarget.value = sanitize(e.currentTarget.value))} />;
}
React (TypeScript)
'use client';
import { useState } from 'react';
import ProfanityBuster, {
lowLatencyPreset,
type ProfanityBusterConfig,
integrations,
} from 'profanitybuster';
const { useProfanityBuster } = integrations;
export function CommentBoxTS() {
const [value, setValue] = useState('');
const config: Partial<ProfanityBusterConfig> = {
...lowLatencyPreset,
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
};
const { ready, detect, sanitize } = useProfanityBuster({
config,
preloadLanguages: ['en', 'fr'],
});
if (!ready) return null;
const hasProfanity = detect(value).hasProfanity;
return (
<div>
<textarea value={value} onChange={(e) => setValue(e.currentTarget.value)} />
{hasProfanity && <small>Profanity detected</small>}
<button onClick={() => setValue((v) => sanitize(v))}>Sanitize</button>
</div>
);
}
import express from 'express';
import { integrations } from 'profanitybuster';
const app = express();
app.use(express.json());
app.use(
integrations.createProfanityMiddleware({
preloadLanguages: ['en'],
sanitizeFields: ['body.comment'],
}),
);
Notes:
sanitizeFields is a dot-separated path that must contain only letters, numbers, underscores, and dots. Suspicious keys like __proto__ are ignored for safety.Node/SSR (TypeScript)
import ProfanityBuster, { type ProfanityBusterConfig, lowLatencyPreset } from 'profanitybuster';
const config: Partial<ProfanityBusterConfig> = {
...lowLatencyPreset,
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
};
const buster = new ProfanityBuster(config);
await buster.loadLanguages(['es']);
const result = buster.detect('esto es una mierda');
if (result.hasProfanity) {
console.log(result.matches);
}
The library combines multiple matchers for performance and flexibility:
profanitybuster/
├── src/
│ ├── core/
│ │ ├── aho.ts
│ │ ├── trie.ts
│ │ ├── phraseTrie.ts
│ │ ├── normalization.ts
│ │ └── langAutoDetect.ts
│ ├── integrations/
│ │ ├── express/
│ │ │ └── middleware.ts
│ │ └── react/
│ │ └── useProfanityBuster.ts
│ ├── languages/
│ │ ├── ar.ts ... zh.ts
│ │ └── index.ts
│ ├── presets.ts
│ └── index.ts # Public API
├── tests
│ ├── basic.test.ts
│ └── languages-load.test.ts
├── bench/
│ ├── algorithm.bench.ts
│ ├── detect.bench.ts
│ ├── largeWordlist.bench.ts
│ └── perf.bench.ts
├── scripts/
│ └── import-dirtywords.mjs
├── vitest.config.mts
├── tsconfig.json
├── package.json
└── README.md
// Basic usage
import ProfanityBuster from 'profanitybuster';
const filter = new ProfanityBuster({
languages: ['en'],
masking: {
enabled: true,
pattern: '*',
},
detection: {
levenshteinDistance: 1,
caseSensitive: false,
wholeWordsOnly: false,
confusableMapping: true,
},
});
// Detection
const hasProfanity = filter.detect(text);
// Sanitization
const cleanText = filter.sanitize(text);
new ProfanityBuster(config?: Partial<ProfanityBusterConfig>)
// detection
detect(text: string): { hasProfanity: boolean; matches: Array<{ word: string; index: number; length: number; language: string }> }
sanitize(text: string): string
// language management
loadLanguages(codes: string[]): Promise<void> // enable packs and (re)build matchers
setLanguages(enabled: string[], fallback?: string): void
addWord(word: string, language?: string): void
removeWord(word: string, language?: string): void
setAlgorithm(algo: 'trie' | 'aho'): void // switch exact-matching engine
// phrase management
addPhrase(phrase: string): void
removePhrase(phrase: string): void
detection.algorithm: 'aho' | 'trie' (Aho–Corasick recommended)Bundled language codes:
loadLanguages([...]) and setLanguages(...)src/languages/index.ts before building{
masking: {
enabled: true,
pattern: '*', // Character to use for masking
preserveLength: true, // Keep original word length
preserveFirst: true, // Keep first character visible
preserveLast: false // Keep last character visible
}
}
{
detection: {
levenshteinDistance: 2, // Tolerance for variants
caseSensitive: false, // Case sensitivity
wholeWordsOnly: false, // Match whole words vs substrings
customWords: [], // Additional words to detect
confusableMapping: true, // Map common look-alikes (1->i, 0->o, @->a, etc.)
ignoreSeparators: [' ', '.', '-', '_', '*'], // Skip common separators in matches
stripDiacritics: true, // Remove combining accents in a length-preserving manner
useNFKC: false, // Full NFKC normalization (set true if you can accept non-length-preserving)
enableInflections: true, // Generate simple inflection variants (suffixes)
inflectionSuffixes: ['s', 'es', 'ed', 'ing', 'er', 'ers'],
allowlist: [], // Terms to ignore even if matched
tokenBoundedFuzzy: true, // Fuzzy checks start at token boundaries
phraseStopwords: ['of', 'the', 'a', 'an', 'and', 'to'], // Allowed between phrase tokens
phraseMaxSkips: 2, // How many stopwords/separators allowed between tokens
algorithm: 'trie' // 'trie' | 'aho'
}
}
{
languages: {
enabled: ['en', 'es'], // Active language packs to preload
autoDetect: false, // Automatic language detection (see notes below)
fallback: 'en' // Default language pack
}
}
Load languages on demand to keep bundles small:
const filter = new ProfanityBuster({ languages: ['en'] });
await filter.loadLanguages(['es', 'fr']);
Note: Language packs are bundled statically in this package. loadLanguages([...]) enables and (re)builds matchers for packs that are already included; it does not fetch over the network. To ship fewer packs, trim src/languages/index.ts before building or publish a fork with a reduced set.
autoDetect: true and you have preloaded some languages (via loadLanguages([...])), detection will prefer those whose script matches the input. This narrows scans and keeps latency low.autoDetect: true and you have not preloaded any languages, the library will load and scan all known built-in language packs as a fallback. This is slower but maximizes coverage.wholeWordsOnly: false to match within tokens.import ProfanityBuster, { lowLatencyPreset, highRecallPreset } from 'profanitybuster';
const lowLatency = new ProfanityBuster({
...lowLatencyPreset,
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
});
const highRecall = new ProfanityBuster({
...highRecallPreset,
languages: { enabled: ['en', 'fr'], autoDetect: false, fallback: 'en' },
});
Build toolchain: tsup (bundles CJS+ESM with type declarations). Tests via vitest.
Releases are driven by Git tags. The CI workflow publishes to npm on pushes of tags that start with v.
v*.*.* (e.g., v1.2.3, v1.2.3-alpha.1).npm publish --provenance --access public.package.json and create a matching tag (recommended via npm):# from the repo root on main
npm version patch # or: minor | major
# or pre-releases:
# npm version prerelease --preid alpha # -> 1.2.3-alpha.0
# npm version preminor --preid rc # -> 1.3.0-rc.0
# pushes commit and tag created by npm version (e.g., v1.2.3)
git push origin main --follow-tags
v, publish the exact version in package.json to npm.If you prefer to create tags yourself (without npm version):
# 1) Commit your changes with the final version in package.json
git add -A
git commit -m "chore(release): v1.2.3"
# 2) Create an annotated tag (recommended)
git tag -a v1.2.3 -m "release: v1.2.3"
# 3) Push branch and tag
git push origin main
git push origin v1.2.3
# Pre-release example
git tag -a v1.2.4-alpha.1 -m "release: v1.2.4-alpha.1"
git push origin v1.2.4-alpha.1
Pushed the wrong tag? Delete it locally and remotely, then re-tag and push again:
# delete local tag
git tag -d v1.2.3
# delete remote tag
git push origin :refs/tags/v1.2.3
# update package.json if needed, commit, then re-tag
git tag -a v1.2.3 -m "release: v1.2.3"
git push origin v1.2.3
vMAJOR.MINOR.PATCH with optional pre-release, e.g., v1.2.3-alpha.1.package.json version exactly (minus the leading v in the tag).By default, the workflow publishes without a custom dist-tag. If you want pre-releases to avoid becoming latest, publish them with a non-latest tag (e.g., next). You can do this manually or adapt the workflow:
# manual publish example (if not using CI):
npm publish --tag next --provenance --access public
If you routinely publish pre-releases, consider changing the CI npm publish step to include --tag next when the version contains a pre-release identifier.
caseSensitive: true)confusableMapping: true): 1→i, 0→o, 3→e, @→a, $→s, etc.customWordswholeWordsOnly and ignoreSeparatorsphraseStopwords and phraseMaxSkips using precise token start/end offsetslevenshteinDistance > 0flowchart TD
A["Input text"] --> B["Normalization\n- lowercasing (opt)\n- confusable mapping\n- strip diacritics\n- neutralize invisibles\n- NFKC (opt)"]
B --> C{"Exact match engine"}
C -->|algorithm='trie'| C1["Trie per language\n- ignoreSeparators\n- wholeWordsOnly"]
C -->|algorithm='aho'| C2["Aho–Corasick automaton\n- ignoreSeparators\n- wholeWordsOnly"]
C1 -->|"matches found"| H["DetectionResult"]
C2 -->|"matches found"| H
C1 -->|"no matches"| P["Phrase matching\n- token trie\n- stopwords/skips"]
C2 -->|"no matches"| P
P -->|"matches found"| H
P -->|"no matches"| D{"levenshteinDistance > 0?"}
D -->|"no"| H
D -->|"yes"| E["Fuzzy window scan (Levenshtein)"]
E -->|"found"| H
E -->|"none"| H
H --> F{"masking.enabled?"}
F -->|"no"| G["Output original text"]
F -->|"yes"| I["Sanitize (masking)\npattern/preserve options"] --> G
subgraph "Language system"
L["Language packs (bundled)"] --> M["Normalize words"]
M --> N{"Build matcher"}
N -->|algorithm='trie'| N1["Trie"]
N -->|algorithm='aho'| N2["Aho automaton\n(build + fail links)"]
O["customWords"] --> N
Q["addPhrase/removePhrase"] --> R["Phrase trie"]
end
N1 -.-> C1
N2 -.-> C2
R -.-> P
Set<string> per language for storageTrie or AhoCorasick per language for exact matchingPhraseTrie for token-level phraseslevenshteinDistance: 0wholeWordsOnly: avoid substring matches inside larger tokensconfusableMapping: defeat simple obfuscations like leet-speaklevenshteinDistance: tolerance for misspellings (0 for maximum precision)caseSensitive: language-specific needs; keep false for best coverageconst filter = new ProfanityBuster({
detection: {
levenshteinDistance: 0,
caseSensitive: false,
wholeWordsOnly: true,
confusableMapping: true,
algorithm: 'aho',
},
});
const filter = new ProfanityBuster({
detection: {
levenshteinDistance: 1,
caseSensitive: false,
wholeWordsOnly: false,
confusableMapping: true,
algorithm: 'aho',
},
});
Use the low-latency profile with Aho–Corasick, whole-words, minimal separators, and no fuzzy/inflections. Preload only needed languages and avoid mutating wordlists at runtime.
import ProfanityBuster, { lowLatencyPreset, type ProfanityBusterConfig } from 'profanitybuster';
const config: Partial<ProfanityBusterConfig> = {
...lowLatencyPreset, // algorithm: 'aho', levenshteinDistance: 0, inflections disabled
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
detection: {
...lowLatencyPreset.detection,
wholeWordsOnly: true,
ignoreSeparators: [' ', '.', '-', '_'],
confusableMapping: true,
},
};
export const buster = new ProfanityBuster(config);
// optionally preload other packs
// await buster.loadLanguages(['es']);
Notes:
Sample benchmarks on a modern Mac (numbers vary by hardware):
detect small ~0.23 ms/op (~4.33k ops/s)
detect medium ~1.71 ms/op (~585 ops/s)
detect large ~8.22 ms/op (~122 ops/s)
Run locally:
npm run bench # micro bench via vitest
npm run perf # alias of the above
aho small ~0.144 ms/op (~6.94k ops/s)
trie small ~0.146 ms/op (~6.85k ops/s)
aho medium ~1.503 ms/op (~665 ops/s)
trie medium ~1.459 ms/op (~685 ops/s)
aho large ~7.50 ms/op (~133 ops/s)
trie large ~7.14 ms/op (~140 ops/s)
Notes:
algorithm: 'aho', disable inflections, and keep fuzzy off.Medium text (~10–20KB), custom dictionary size:
aho dict10k medium ~1.51 ms/op (~663 ops/s)
trie dict10k medium ~1.72 ms/op (~583 ops/s)
aho dict100k medium ~1.54 ms/op (~648 ops/s)
trie dict100k medium ~1.72 ms/op (~582 ops/s)
Interpretation:
enableInflections: false and levenshteinDistance: 0 for large lists.preloadLanguages in hooks/middleware if needed.import ProfanityBuster from 'profanitybuster';
const buster = new ProfanityBuster({
languages: { enabled: ['en'], autoDetect: false, fallback: 'en' },
});
// Add a phrase (tokens matched with optional stopwords/skips)
buster.addPhrase('son of a bitch');
const res = buster.detect('you are a son of the a bitch indeed');
console.log(res.hasProfanity); // true
// Remove the phrase when no longer needed
buster.removePhrase('son of a bitch');
This project aims to provide a robust, performant solution for content moderation in modern web applications. The modular architecture ensures scalability while maintaining optimal performance.
Development prerequisites:
npm ci && npm run typecheck && npm run lint && npm test && npm run buildFAQs
High-performance, TypeScript profanity detection and filtering library with modular language packs.
The npm package profanitybuster receives a total of 1 weekly downloads. As such, profanitybuster popularity was classified as not popular.
We found that profanitybuster demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
/Company News
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.

Research
/Security News
Benign-looking npm packages split malicious functionality across a dependency chain that deploys a cross-platform RAT targeting Alibaba developers.

Research
/Security News
Two Joyfill npm beta releases contain an import-time implant that uses blockchain transactions to retrieve a remote-access trojan.