NSFW Detector + Save Image
The NSFW detector that tags your filenames instead of blocking
- images
- images
- nsfw_level
- nsfw_score
Most NSFW detectors you meet inside ComfyUI are gates. ReActor ships one that blanks out output the moment it sees something it doesn't like, and half the threads about it are people editing the score threshold so their images survive. This node is the opposite animal: NSFW Detector + Save Image never blocks anything. It runs a classifier over every image, then just tells you the verdict - by baking it into the filename. job_00001_.png becomes job_flagged_high_00001_.png. That's the whole trick, and it's surprisingly useful.
The use case that makes sense is batch work. Fire off a long run, come back, and instead of eyeballing two hundred PNGs or running a second pass with a viewer, you have a file listing that already separates the flagged ones. People also use it to keep a content policy honest on a shared/gallery machine, and to triage datasets - the flagged prefix plus the embedded score makes a decent cheap pre-filter before you hand a folder to a captioner. It's a small niche, and the pack is honest about being one node.
How it works
Under the hood it's a Hugging Face image-classification pipeline loaded from Freepik/nsfw_image_detector, an EVA-based model around 86MB. It classifies each image into one of four levels - neutral, low, medium, high - and then, this is the detail to remember, it uses the worst result in the whole batch. One flagged frame in a batch of five and the nsfw_level output is that frame's verdict, and every saved file in the batch gets the _flagged_ prefix. Great for "did anything in this run cross the line," surprising if you expected per-image flags.
The verdict only determines the filename prefix. The save itself is a faithful re-implementation of ComfyUI's own SaveImage: it writes to ComfyUI/output/, uses the same auto-incrementing counter, and keeps the filename_prefix templating (%date:yyyy-MM-dd% and friends) working because it calls the same get_save_image_path helper. It also embeds the usual prompt/workflow chunks plus two extra ones, nsfw_level and nsfw_score, so drag-back-still-works and the classification travels with the file. Keep PNG if that matters - JPEG and WebP can't carry those text chunks.
The inputs that matter
Three required, and you'll mostly touch two:
- images - feed it whatever your sampler outputs, same as
SaveImage. - filename_prefix - base name; flagged files get
_flagged_{level}appended. - nsfw_threshold - where the flagging line sits:
low- flags anything fromlowup. Strictest.medium- flagsmedium/high, letslowpass. Balanced default.high- flags only the worst. Most lenient.
The optional format (png/webp/jpeg) and quality (1–100) mirror ComfyUI's save options; quality only applies to webp/jpeg and is ignored for png. lossless is WebP-only - flip it on and quality becomes compression effort.
Outputs: images (the batch, passed through so you can chain more nodes after), plus nsfw_level (STRING) and nsfw_score (FLOAT, the confidence of the detected level). If you want to branch on the result - route flagged batches to a different save folder, say - wire those into a logic/switch node rather than reading the filename.
Installing it
ComfyUI Manager is the easy path: search "comfyui-nsfw-detector" and install. Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/newraina/comfyui-nsfw-detector.git
pip install -r comfyui-nsfw-detector/requirements.txt
Then restart ComfyUI. First run downloads the model from Hugging Face automatically (needs network), or you can pre-drop config.json and model.safetensors into ComfyUI/models/nsfw_detector/ to skip that.
Where people get burned
The dependencies are the real gotcha. requirements.txt pulls in transformers>=4.40 and timm, which are chunky and install into the same Python environment as every other custom node. If another pack pins a different transformers version, pip resolves whatever it resolves and one of them can quietly break - the classic custom-node dependency fight. If you're on CPU it still runs (it falls back to device=-1), just slower per image, and a full batch is classified image-by-image so big runs add real time.
Keep in mind what this node is not: a content filter. It doesn't delete, blur, or gate anything - it renames files and reports scores. If you need enforcement rather than labeling, this isn't it. For labeling, it's a clean, single-node replacement for SaveImage that does its one job.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| filename_prefix | STRING | ComfyUI | — |
| nsfw_threshold | COMBO | medium | 3 options: low, medium, high |
| formatopt | COMBO | png | 3 options: png, webp, jpeg |
| qualityopt | INT | 901–100 | — |
| losslessopt | BOOLEAN | false | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| nsfw_level | STRING | — |
| nsfw_score | FLOAT | — |