npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sixzensed/wordguard

v0.1.5

Published

Lightweight multilingual profanity and slur detector with CDN-ready wordlists.

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
  • ignoreList for false positives
  • specialWords for transliterations, code words, slurs, and keyboard substitutions

Install

npm install @sixzensed/wordguard

Quick 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 like 8;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/sample maps 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 ignoreList close 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-run

Publish:

npm login
npm publish

The package is published under the @sixzensed scope:

npm publish --access public

License

MIT