# NSFW Filter: Local Image Classification in Chrome

> NSFW Filter is a privacy-focused Chrome extension that classifies page images locally with TensorFlow.js. It hides images first, then reveals images considered safe, with controls for strictness, models, display treatment, and site exceptions.

**nsfw-filter/nsfw-filter** — A free, open source, and privacy-focused browser extension to block “not safe for work” content built using TensorFlow.js.

- Repository: https://github.com/nsfw-filter/nsfw-filter
- Website: https://nsfwfilter.com
- Stars: 1,795 · Forks: 186
- Language: TypeScript
- License: GPL-3.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/nsfw-filter-nsfw-filter

## Why NSFW Filter Runs Inside the Browser

NSFW Filter addresses a specific problem: reducing exposure to images classified as not safe for work while browsing the web. Its intended users are people who want automatic image filtering without sending the images to a remote filtering service. The README says that images are classified on the device with TensorFlow.js and that no data leaves the browser. That design differs from a server-based classifier, although the repository does not document network requests beyond the stated local image classification behavior.

The extension works on images encountered when web pages load. Its policy is conservative at the start: images are hidden first. Images classified as safe are shown, while flagged images can be blurred, converted to grayscale, or hidden according to the popup settings. Users can also turn protection off, adjust strictness, select a classification model, and allow particular sites. Those controls matter because an image filter has to fit different browsing situations. A user may want a strict setting on some sites and normal access on others.

The project is aimed at Chrome users because the README provides a Google Chrome Web Store link and the development instructions use Chromium. It is not presented as a general-purpose moderation service, API, or standalone classifier library. Its practical value depends on the browser loading supported page images and on the model producing a useful classification for those images.

## How TensorFlow.js Drives Image Visibility

The repository describes a browser extension whose central data flow is local image classification. When a page loads, NSFW Filter hides all images and sends the relevant image data to a TensorFlow.js model running in the browser. A model returns a safe or not-safe result, and the extension reveals only images classified as safe. The popup controls how flagged images are presented, which gives the user a choice between concealing content and leaving a recognizable but altered version on screen.

The popup also exposes strictness and site-level permissions. This means the filtering decision is not represented as a single fixed switch. The user can choose how aggressively the extension treats the classification result and can exempt specific sites. The README does not document the exact threshold values, image resizing behavior, supported image formats, handling of CSS background images, or the precise behavior for videos and other media. Those omissions limit what can be concluded about coverage.

Two classification models are available from the popup. The default is a small Vision Transformer identified as ViT-384, described by the project as accurate. The alternative is the original NSFWJS MobileNet model. This is a meaningful architecture and product choice. ViT-384 is the default model for safe and not-safe classification, while MobileNet is the older model included for comparison or user preference. The documentation does not publish accuracy results, latency measurements, or a formal explanation of when one model is preferable.

## Installing NSFW Filter and Performing a First Browser Setup

The quickest documented installation path is the Chrome Web Store. The README provides the extension listing under "Download now for Google Chrome." A source installation is also documented for development and local testing. The project requires Node.js version 20.19 or later according to package.json.

Install dependencies from the repository root:

```bash
npm install
```

The README says this installs the project's dependencies. It does not document a separate account, server, or environment-variable setup.

Build the production extension:

```bash
npm run build
```

The build command removes `dist/src` and runs the production webpack configuration. For development with live reload, use:

```bash
npm run dev
```

This starts the development webpack watch build. In a separate terminal, launch Chromium with the built extension:

```bash
npm run start:chrome
```

The command targets Chromium and uses `./dist/` as the extension source directory. The repository also documents loading the build manually. Open `chrome://extensions`, enable Developer mode, choose Load Unpacked, and select the extension directory ending in `dist`. The README does not state a version-specific output path beyond the `dist` directory.

For the first real use, open the extension popup and verify that protection is enabled. Choose the ViT-384 model or the NSFWJS MobileNet model, set strictness, select blur, grayscale, or hide, and add any sites that should be exempt. The README does not document rollback or how to restore a previous configuration, so retain the chosen settings before changing them.

## Source Testing and Release Maintenance Constraints

The package exposes separate unit and end-to-end test commands, plus a combined test script:

```bash
npm run test
```

This runs the unit tests and then the end-to-end tests. The individual commands are:

```bash
npm run test:unit
npm run test:e2e
```

The repository declares Jest 30-related tooling and provides separate Jest configuration files for unit and end-to-end tests. The README does not describe coverage, supported browser versions beyond the Chrome and Chromium workflow, or expected test results, so the existence of these scripts does not by itself establish what behavior is verified.

The repository is not archived. Its last push was on 2026-10-03, and releases listed include `v3.1.1` on 2026-09-06, `v3.1.0` on 2026-09-05, and `v3.0.0` on 2026-07-03. These facts show recent release activity in the repository record. They do not prove that every browser, TensorFlow.js backend, or website behavior is maintained.

The project uses `GPL-3.0-only` in package.json and lists GPL-3.0 as its license. Anyone distributing a modified build or incorporating the project into another distributed product should review the license text and comply with its terms. This article does not provide legal advice. The practical upgrade cost includes rebuilding the TypeScript and webpack project, checking TensorFlow.js compatibility, and reviewing changes to model behavior or Chrome extension permissions after an upgrade.

## Why a Local NSFW Classifier Can Still Misclassify Content

The central limitation is that a binary image classifier cannot establish that every image is safe or unsafe. The README describes image-level classification but does not document precision, recall, confidence thresholds, test datasets, or guarantees about content types. The user therefore has to choose an operating point rather than rely on a perfect safety decision.

The initial behavior, hiding images until they are classified, is a meaningful safety measure, but it can also affect the visible page during classification and whenever an image cannot be processed. The repository does not explain fallback behavior for failed downloads, unsupported image formats, dynamically inserted images, pages that block extension content scripts, or videos. Those are concrete boundary cases rather than claims that the extension fails in them.

The two-model choice illustrates the trade-off. ViT-384 is the default and is described as small and accurate. NSFWJS MobileNet is the original alternative model, with a different architecture and likely different resource characteristics. The repository does not provide benchmark data, so users cannot use the README alone to choose based on measured accuracy, startup time, or memory use. A product requiring guaranteed moderation coverage would need a separate, documented review process.

Compared with a remote moderation API, the local model keeps images in the browser, as the project states, but it also places classification responsibility and resource use on the browser extension. Compared with manual review, it can process page images automatically, but it still depends on model output and user settings. Compared with a browser's own content controls, it offers model selection, strictness, and visual treatments, though the README does not claim compatibility with those controls.

## ViT-384 Versus NSFWJS MobileNet

The clearest alternative exposed by NSFW Filter is the choice between its default Vision Transformer and the original NSFWJS MobileNet model. This is not a comparison with an unrelated product. It is a direct choice between two model paths offered by the same extension.

The ViT-384 path is positioned as the default and as a small, accurate classifier for safe or not-safe results. The NSFWJS MobileNet path preserves the project's original model and is identified as NSFWJS version 4.3.0 in package.json. The package also includes `@tensorflow/tfjs` 4.22.0 and `@tensorflow/tfjs-backend-wasm` 4.22.0, indicating that the extension has browser-side TensorFlow.js and WebAssembly backend dependencies.

The documentation does not quantify the difference in speed, memory, confidence behavior, or accuracy. It also does not give a migration procedure between models beyond selecting one in the popup. For evaluation, the meaningful first check is not a generic preference for one architecture. Check the behavior on the kinds of pages you intend to visit, then inspect whether the selected strictness and treatment settings produce an acceptable page experience. The repository's release notes may contain model-specific changes, but the README does not reproduce them.

## Conclusion

Adopt NSFW Filter if you want browser-side image filtering that keeps page images on the device and you accept model-based classification limits. Do not treat it as a guaranteed content-security boundary. Before adopting it, verify that the Chrome Web Store version, source build process, model choice, site exceptions, and GPL-3.0-only distribution terms match your requirements.

## FAQ

### Does NSFW Filter upload images to a server?

The README states that images are classified on the user's device with TensorFlow.js and that no data leaves the browser. The README does not describe a server-side image upload.

### Which browsers can run NSFW Filter?

The README provides installation through the Google Chrome Web Store and development instructions that launch Chromium. The README does not document Firefox, Safari, or other browser support.

### Can I choose which NSFW Filter model is used?

Yes. The popup lets users switch between the default ViT-384 model and the original NSFWJS MobileNet model.

### How do I load NSFW Filter from source?

Run `npm install`, build with `npm run build`, open `chrome://extensions`, enable Developer mode, and select the `dist` directory with Load Unpacked. The repository also provides `npm run start:chrome` for launching Chromium with the build.

## Sources

- [License: GPL-3.0](https://github.com/nsfw-filter/nsfw-filter/blob/master/LICENSE)
- [nsfw-filter/nsfw-filter on GitHub](https://github.com/nsfw-filter/nsfw-filter)
- [Project website](https://nsfwfilter.com)
- [README](https://github.com/nsfw-filter/nsfw-filter/blob/master/README.md)
- [Releases](https://github.com/nsfw-filter/nsfw-filter/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nsfw-filter-nsfw-filter
