Library / SDK
coffee-and-fun/google-profanity-words avatar
coffee-and-fun/google-profanity-words

google-profanity-words: a Node.js profanity filter for six languages

Full list of bad words and top swear words banned by Google.

709 stars244 forksJavaScriptMIT

At a glance

What is it?
google-profanity-words is a zero dependency Node.js library that detects profanity with whole-word, case-insensitive matching, backed by word lists taken from a Google API before it shut down. Its six language files are wildly uneven, with 962 English entries against 23 for French and 30 for Chinese, so the useful coverage is narrower than the language list suggests.
Who is it for?
google-profanity-words fits a Node.js app that needs a lightweight English or Spanish filter with no dependencies and no regex cost, and it does not fit a product that needs reliable coverage in French, Irish, Arabic or Chinese, or a CommonJS codebase that has to require the package. Before adopting it, check the entry count for the language you actually need and run npm test to confirm the data files load the way the README describes.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 135 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A word list rescued from a Google API that no longer answers

The origin of this package explains its shape. The description in package.json says the words are real profanity words banned by Google, extracted from their hidden API before shutdown, and the library now republishes that capture as an easy to use Node.js library for content filtering. That is worth holding in mind as an adopter, because a static extraction is a snapshot of one moment rather than a feed that stays current.

What the project actually is, is small and well defined. It is a Node.js library that detects and filters out what the README calls naughty language, in multiple languages, for chat apps, comment sections, games and comment moderation. The published package is @coffeeandfun/google-profanity-words, the author is Robert James Gabriel at Coffee & Fun, and the licence is MIT.

The design decisions are conservative. The README lists zero runtime dependencies, O(1) lookups with a Set, types included so no separate type package is needed, and a note that there is no polynomial regex. That last point is the one that matters most for a filter that runs on every user submitted string: the engine matches against a set rather than running a pattern scan, so a long list of terms does not translate into a long regular expression that backtracks on hostile input.

One data file per language, matched by ISO code

The engine picks its word list from the data directory. The options table lists a language option of type string with a default of en, described as an ISO code matching a file in data/, and it falls back to English when the code does not resolve. A testMode option of type boolean, default false, suppresses console.warn output, which the README suggests is useful in tests and, by implication, in any context where a warning on every call would be noise.

The fallback is worth planning around. If you configure a language code that does not exist, the engine does not throw. It silently continues in English and prints a warning, which is exactly the behaviour you want during development and exactly the behaviour that can hide a typo in production, since a misspelt code such as en-US would quietly filter English terms against a French comment. The warning is the only signal, and testMode removes it, so the two options interact: turning on testMode also turns off the one message that would have told you the language was wrong.

The files live in a top level data/ directory that the package ships in its files list alongside index.js, index.d.ts and src/, so the word lists travel with the installed package rather than being fetched at runtime.

Every check is async, even for a single word

The API is promise based across the board, which is a deliberate choice rather than an accident of the loader. The README states that every check method is async and returns a Promise, so search on one word gives you a Promise resolving to a boolean, hasCurseWords on a sentence gives you a Promise resolving to a boolean, and getCurseWords gives you a Promise resolving to an array.

Here is the shape of a first use, taken from the quickstart.

js
import { ProfanityEngine } from '@coffeeandfun/google-profanity-words';
const profanity = new ProfanityEngine();
const isBad = await profanity.search('hell');
const hasCurses = await profanity.hasCurseWords('This is a test sentence');
const found = await profanity.getCurseWords('oh hell and damn');
console.log(isBad, hasCurses, found);

Because the results are promised rather than thrown, a rejected load surfaces at the call site as an unhandled rejection unless you await it, and the API forces await at every call. In an Express or Fastify handler that is not a problem, since the handler is already async. In a synchronous loop over a batch of comments it is, since you would be constructing a promise per item.

Two further methods matter in practice. all() returns the full list for the current language as a copy, so mutating the array cannot corrupt the engine, and reset() clears the in-memory cache so the next call re-reads the data file, which the README points at for tests and for hot swapping a data file.

Whole-word matching is why compound words stay clean

The matching rules are spelled out in the edge case section, and the most consequential one is that matching is whole-word. The README gives the example that a compound containing a listed term as a prefix does not trigger a match, and also that a similar word is not a match. Substring matching would have produced false positives on ordinary vocabulary, and the whole-word rule is what makes the list usable without a review pass over false positives.

The other rules are equally practical. Matching is case-insensitive by default, so casing variants all hit. Punctuation around a term is handled, so a term wrapped in brackets, quotes or followed by a full stop is still detected. Unicode is handled through Intl.Segmenter, which the README credits for Spanish inverted punctuation, Chinese characters and Arabic, and that matters for the languages where a term is not delimited by spaces in the way English is.

Bad input does not crash the engine. Empty strings return false or an empty array, and null, undefined, numbers and objects are documented as returning false or an empty array rather than throwing. The getCurseWords result is unique and lowercased, and it is an empty array rather than null when nothing matched, so callers never need a null check on the array itself.

Six languages, and the counts are not comparable

The supported language table is where an adopter should slow down. The README lists English at 962 entries and Spanish at 564, then French at 23, Arabic at 23, Chinese at 30 and Irish at 15. The same table is described as approximate, but the order of magnitude is the message: French has about two percent of the English coverage, and a filter for a French comment section built on 23 terms will miss nearly everything a reader would notice.

That is a data limitation rather than a code limitation. The engine, the matcher and the Unicode handling are the same for every language, so the file is what determines the coverage, and the files came from a single extraction of a Google list. There is no documented mechanism for extending the lists, and the reset method only re-reads whatever file is already in data/.

A passable middle ground is the two large lists, English and Spanish, where the term counts are in the hundreds and a filter will catch common cases. The smaller lists are better read as a starting point for a team willing to add its own terms than as a finished filter for those languages. The whole-word and case rules still work, so the engine does not need replacing if a team supplements the data.

Installing the package and running the test suite

Installation is a single command, and the package requires Node.js 16 or newer.

bash
npm install @coffeeandfun/google-profanity-words

The package metadata is short and worth reading before you commit to it.

json
  "name": "@coffeeandfun/google-profanity-words",
  "version": "3.0.7",
  "main": "index.js",
  "types": "index.d.ts",
  "type": "module",

Two things follow from that. The package is ESM only, with type set to module and an exports map that offers types, import and default entries but no require entry, so a CommonJS consumer cannot require it directly and would need a dynamic import or a bundler. And the types are bundled, so no separate @types package is needed.

The engine consumes Node 16 as the floor.

json
  "engines": {
    "node": ">=16"
  },

The README documents a Jest suite with per-language scripts and direct invocation, and the scripts in package.json match it, with the test run using experimental VM modules enabled through NODE_OPTIONS because the source is ESM.

bash
npm test
npm run en
npm run es
npm run engine
npx jest --watch
npx jest --coverage
npx jest path/to/file.test.js

The README says the tests live in __tests__ and run against the real word list files, with no mocking.

A snapshot list under a permissive licence

The last push was on 2026-06-10 and the repository is not archived. The release history shows three versions published on the same day, 2026-04-20: v3.0.5, v3.0.6 and v3.0.7, which suggests a burst of packaging work rather than a steady stream of features. The current version in package.json matches the newest release, and a CHANGELOG.md sits at the top level of the repository for the detail behind those bumps.

The licence is MIT, and the LICENSE file is at the top level. That permits use, modification, distribution and commercial reuse provided the copyright notice travels with the software, which for a word list package is the part to keep in mind when you embed it in a hosted product.

There is a repository level SECURITY.md as well, and the README makes a claim about provenance, saying the library ships with provenance because there is no polynomial regex. Whether that matters to you depends on how much you trust a list extracted from a third party API before it shut down. The data files are plain text in data/, so you can read exactly what your filter will match, which is the practical answer to the trust question.

The upgrade cost is low in effort: the package has no runtime dependencies, so there is nothing to reconcile, and the data files are re-read through reset if you swap them. The cost is coverage, not integration.

Editorial conclusion

google-profanity-words fits a Node.js app that needs a lightweight English or Spanish filter with no dependencies and no regex cost, and it does not fit a product that needs reliable coverage in French, Irish, Arabic or Chinese, or a CommonJS codebase that has to require the package. Before adopting it, check the entry count for the language you actually need and run npm test to confirm the data files load the way the README describes.

Frequently asked questions

How do I install and use google-profanity-words?

Run npm install @coffeeandfun/google-profanity-words, which requires Node.js 16 or newer, then create a ProfanityEngine and await search, hasCurseWords or getCurseWords. The package is ESM only and ships its own types.

Which languages does google-profanity-words support and how large are the lists?

The README lists English at 962 entries, Spanish at 564, Chinese at 30, French at 23, Arabic at 23 and Irish at 15, with the counts described as approximate. The language option takes an ISO code matching a file in data/ and falls back to English with a warning.

Does the profanity filter match words inside longer words?

No, matching is whole-word, so a compound containing a listed term does not trigger. Matching is case-insensitive, punctuation around a term is handled, and Unicode text is segmented with Intl.Segmenter.

What happens if I pass an unsupported language code to google-profanity-words?

The engine falls back to English and prints a warning, unless testMode is set to true, which suppresses console.warn output. The warning is the only signal that the code did not resolve.

Where did the word lists in google-profanity-words come from?

The package description says the words are real profanity words banned by Google, extracted from their hidden API before shutdown. The lists are plain files in the data/ directory, so their contents can be read directly.

Official sources

  1. coffee-and-fun/google-profanity-words on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/coffee-and-fun-google-profanity-words.svg)](https://hysenlabs.com/projects/coffee-and-fun-google-profanity-words)

Was this useful?