@sixzensed/wordguard
v0.1.5
Published
Lightweight multilingual profanity and slur detector with CDN-ready wordlists.
Maintainers
Readme
WordGuard
Lightweight multilingual profanity and slur detection for JavaScript and TypeScript apps.
WordGuard is built for products that want moderation logic inside the app, while keeping wordlists easy to update from a CDN, cloud storage, or local JSON file. It works without a separate API service.
Features
- Multilingual wordlists, currently with Thai and English samples
- CDN-ready wordlist loading
- TypeScript definitions included
- Works in Node.js, Next.js server code, and browser code
- Thai-aware normalization for spacing, punctuation, tone marks, vowel insertions, and repeated characters
ignoreListfor false positivesspecialWordsfor transliterations, code words, slurs, and keyboard substitutions
Install
npm install @sixzensed/wordguardQuick Start
import { createDetectorFromWordlist } from "@sixzensed/wordguard";
import wordlist from "@sixzensed/wordguard/wordlists/th";
const detector = createDetectorFromWordlist(wordlist);
const result = detector.check("ค+ว+ย");
console.log(result.isProfane); // true
console.log(result.severity); // "medium" | "high" | ...
console.log(result.matches);If your bundler does not allow JSON imports, load the wordlist from a URL or read it as JSON yourself.
CDN Wordlists
This is the recommended setup for production when you want to update words without redeploying your app.
import { createProfanityDetector } from "@sixzensed/wordguard";
const detector = await createProfanityDetector({
wordlistUrl: "https://cdn.example.com/wordlists/thai-badwords.v1.json",
fetchOptions: {
cache: "force-cache"
}
});
const result = detector.check("ข้อความที่ต้องตรวจ");You can host wordlists on Cloudflare R2, S3, Vercel Blob, Supabase Storage, jsDelivr, unpkg, or any CDN/storage that returns public JSON. If you use the detector in the browser, make sure CORS is enabled.
Next.js Usage
For enforcement, run WordGuard on the server before saving user-generated content.
// app/actions/comments.ts
import { createProfanityDetector } from "@sixzensed/wordguard";
let detectorPromise: ReturnType<typeof createProfanityDetector> | undefined;
function getDetector() {
detectorPromise ??= createProfanityDetector({
wordlistUrl: "https://cdn.example.com/wordlists/thai-badwords.v1.json",
fetchOptions: {
cache: "force-cache"
}
});
return detectorPromise;
}
export async function validateComment(text: string) {
const detector = await getDetector();
const result = detector.check(text);
if (result.isProfane) {
return {
ok: false,
reason: "moderation_failed",
matches: result.matches
};
}
return { ok: true };
}For better UX, you can also run the same check in the browser while the user types. Still validate on the server before writing to your database.
Wordlist Schema
The easiest format is:
{
"version": "1.0.0",
"locale": "th-TH",
"prefixes": ["กู", "มึง", "ไอ้", "อี"],
"ignoreList": ["หีบ", "สัสดี", "กะหรี่ปั๊บ"],
"rootWords": ["ควย", "เหี้ย", "หี", "สัส"],
"specialWords": [
{
"term": "Edok",
"meaning": "อีดอก",
"canonical": "อีดอก",
"severity": "high",
"tags": ["transliteration", "insult"]
},
{
"term": "8;p",
"meaning": "ควย",
"canonical": "ควย",
"severity": "high",
"tags": ["keyboard-substitution", "obfuscated"],
"preserveSymbols": true
}
]
}Field guide:
rootWords: canonical words to detect. Add the normal spelling, not every obfuscated spelling.ignoreList: safe words that should not be flagged after normalization.specialWords: code words, transliterations, slurs, and keyboard substitutions that need metadata.prefixes: reserved for future phrase/policy rules. It is stored but not detected by itself.preserveSymbols: keeps symbols such as;in a special word. Useful for exact patterns like8;p.
Grouped Wordlists
Use groups when many terms share the same severity and tags.
{
"version": "1.0.0",
"locale": "en-US",
"groups": [
{
"severity": "high",
"tags": ["slur"],
"terms": ["example-one", "example-two"]
},
{
"severity": "medium",
"tags": ["vulgar"],
"terms": ["example-three"]
}
]
}The older flat words format is also supported:
{
"words": [
{ "term": "example", "severity": "medium", "tags": ["custom"] }
]
}Included Wordlists
@sixzensed/wordguard/wordlists/th@sixzensed/wordguard/wordlists/en@sixzensed/wordguard/wordlists/samplemaps to the Thai sample
The included wordlists are samples. Treat them as a starting point, then tune them against your product, community, and policy.
Result Shape
type ProfanityCheckResult = {
isProfane: boolean;
severity: "none" | "low" | "medium" | "high" | string;
score: number;
matches: Array<{
term: string;
normalized: string;
severity: string;
tags: string[];
meaning?: string;
canonical?: string;
index: number;
}>;
normalizedText: string;
};Example:
const result = detector.check("8;p");{
"isProfane": true,
"severity": "high",
"score": 3,
"matches": [
{
"term": "8;p",
"normalized": "8;p",
"severity": "high",
"tags": ["keyboard-substitution", "obfuscated"],
"meaning": "ควย",
"canonical": "ควย",
"index": 0
}
],
"normalizedText": "8p"
}API
createProfanityDetector(config)
Loads a wordlist from wordlistUrl or inline words, then returns a detector.
const detector = await createProfanityDetector({
wordlistUrl: "https://cdn.example.com/wordlists/en-badwords.v1.json"
});createDetectorFromWordlist(wordlist, config?)
Creates a detector from an already-loaded JSON wordlist.
const detector = createDetectorFromWordlist(wordlist);loadWordlist(url, options?)
Fetches and normalizes a remote wordlist.
const wordlist = await loadWordlist("https://cdn.example.com/wordlists/th.json");normalizeThaiText(text, options?)
Exposes the normalizer used by the detector. The name is kept for compatibility with the original Thai-focused API, but it is safe to use with general text too.
How Detection Works
WordGuard is rule-based, not AI-based. It normalizes the input, tries canonical matches, folded repeated-character matches, safer loose matches, and exact special-word matches.
Examples that can be detected from one Thai root word:
ควย
คุวย
คีวย
ค ว ย
ค.ว.ย
ค+ว+ย
ค-ว-ย
ค*ว*ยSpecial patterns such as 8;p should go in specialWords because they are code words or keyboard substitutions, not natural spelling variants.
Operational Advice
- Start with server-side enforcement.
- Add client-side checks only for faster user feedback.
- Keep wordlists versioned, for example
thai-badwords.v1.json. - Use CDN cache headers intentionally.
- Keep
ignoreListclose to real false positives from your product. - Review slur and identity-targeted terms separately from ordinary profanity.
- Do not rely on browser-only moderation for anything important, because users can inspect client code and wordlists.
Publishing
Before publishing:
npm test
npm pack --dry-runPublish:
npm login
npm publishThe package is published under the @sixzensed scope:
npm publish --access publicLicense
MIT
