Skip to main content

openmoderate

See every upload. Keep only what's safe.
Open-source, self-hosted moderation for images and video: nudity, violence, weapons, hate symbols, drugs and more.

PyPI version Python 3.9 to 3.14 CI Docker image Public benchmark Apache 2.0 license

Documentation · Benchmarks · Discussions · Changelog · API reference

Terminal demo: openmoderate scans a folder, flagging a pistol (weapons 100%), a riot (violence 99%) and a swastika (hate_symbols 100%) while a beach, a dog and a US flag pass as safe; then the REST server flags a cigarette photo as tobacco (0.96).
Every result shown is real output on openly licensed photos.


Quick start

pip install openmoderate
from openmoderate import Moderator

mod = Moderator()                      # or Moderator(preset="accurate")
result = mod.analyze("upload.jpg")     # path, URL, bytes, file upload or PIL image

result.is_safe        # False
result.violations     # [Label(name='weapons', confidence=0.96, parent='violence', threshold=0.5)]

Or from the terminal:

openmoderate scan ./uploads            # images and videos, recursively

Why openmoderate

  • An open alternative to cloud moderation APIs. Comparable to AWS Rekognition's DetectModerationLabels, but it runs inside your app: no cloud account, no per-image fees, and no image ever leaves your servers.
  • Real machine learning, no ML expertise needed. A CLIP deep-learning model does zero-shot classification, so it works out of the box. Fine-tune it on your own labelled images when you need more.
  • Measured, not claimed. Accuracy comes from a public benchmark of 475 hand-reviewed photos with a held-out half and cross-validation, and the limits are published next to the numbers.
  • Your policy, in plain text. Change thresholds, switch categories off, or add new ones like gambling with a few words of YAML. No training data required.
  • Images and video. Videos get timestamped segments ("weapons at 0:03–0:06"), with an early stop for fast rejection.
  • Drops into your stack. One line protects uploads in FastAPI, Flask or Django; a REST API and Docker image serve every other language.
  • Hardened for untrusted input. SSRF-safe URL fetching, size and decoding limits, pickle-free model files, and confidence scores hidden from end users by default.
  • Choose speed or accuracy. Three model sizes, from 6 ms per image to the most accurate.

Install

You want to… Install
Moderate images from Python or the CLI pip install openmoderate
Moderate videos too pip install "openmoderate[video]"
Protect uploads in FastAPI, Flask or Django pip install "openmoderate[fastapi]" · [flask] · [django]
Run the REST API server pip install "openmoderate[server]"
Use Docker, from any language docker run -p 8000:8000 ghcr.io/karammm/openmoderate

Python 3.9–3.14. The model weights (~600 MB) download once on first use; after that everything runs offline, on CPU, NVIDIA GPUs or Apple Silicon. On CPU-only servers, see the install notes to save ~2 GB.

Usage

Python API and policies
from openmoderate import Moderator

mod = Moderator(thresholds={"weapons": 0.3}, exclude=["alcohol", "tobacco"])
results = mod.analyze_batch(["a.jpg", "b.jpg", "c.jpg"])     # faster for many images

Keep the policy in YAML, including your own categories:

# policy.yaml  →  Moderator("policy.yaml")
preset: accurate
thresholds: {weapons: 0.3}
disabled: [alcohol, tobacco]
custom_categories:
  - name: gambling
    prompts: ["a casino slot machine", "people gambling at a poker table"]
    negatives: ["people playing a board game"]   # benign look-alikes

Python guide →

FastAPI, Flask and Django
# FastAPI: violating images get a 422 before your endpoint runs
@app.post("/avatar")
async def avatar(file: ModeratedFile): ...

# Flask
@app.post("/avatar")
@moderate_uploads("avatar")
def avatar(): ...

# Django: works in models, forms, the admin and DRF serializers
avatar = models.ImageField(validators=[ModerationValidator()])

The upload is rewound after the check, so you save it as usual. All integrations share one loaded model. Integrations guide →

Videos
result = Moderator().analyze_video("clip.mp4", fps=1, stop_on_violation=True)
result.segments   # [VideoSegment(name='weapons', start=3.0, end=6.0, max_confidence=0.99, frames=3)]

Video guide →

REST API and Docker
docker run -p 8000:8000 -e OPENMODERATE_API_KEY=change-me ghcr.io/karammm/openmoderate
curl -H "X-API-Key: change-me" -F file=@upload.jpg localhost:8000/v1/moderate

Endpoints for single images, batches, base64, URLs (off by default) and videos, with interactive docs at /docs. Server guide →

Command line and CI
openmoderate scan ./uploads --json                 # one JSON object per file
openmoderate scan ./uploads --fail-on-violation    # exit code 1 if anything is flagged
openmoderate scan clip.mp4 --fps 2 --preset accurate

CLI guide →

Train on your own images
openmoderate train ./dataset -o my_head.omh        # dataset/<label>/*.jpg + dataset/safe/
openmoderate evaluate ./holdout --head my_head.omh
mod = Moderator(head="my_head.omh")

Training guide →

How it works

trim borders → embed image (CLIP) → compare with category prompts → calibrated score → your threshold

openmoderate is built on OpenCLIP, an open implementation of CLIP: a deep-learning model with a Vision Transformer image encoder and a text encoder, trained on LAION-2B (about 2 billion image–text pairs). CLIP places images and sentences in the same vector space, so each category is described in plain words ("a photo of a gun", "a nazi swastika flag"). An image is flagged when it matches a category's prompts clearly better than everyday safe prompts and known look-alikes such as kitchen knives, national flags and medicine.

This is zero-shot classification: no training data is needed, and a new category is a few lines of text. With labelled images, openmoderate train adds supervised learning on top: a class-balanced logistic-regression head on frozen CLIP embeddings.

Preset CLIP model Parameters Download CPU, per image Apple GPU, per image
fast (default) ViT-B/32 ~150 M ~600 MB 10 ms 6 ms
balanced ViT-B/16 ~150 M ~600 MB 32 ms 16 ms
accurate ViT-L/14 ~430 M ~1.7 GB 129 ms 68 ms

How it works, in depth →

Accuracy

Measured on 475 openly licensed photos from Wikimedia Commons, each reviewed by eye, of which 215 are safe, including deliberate look-alikes. Reproduce it with python benchmark/run.py --preset <name> --split all.

Recall per category fast (default) balanced accurate
Alcohol 76% 82% 87%
Drugs 85% 83% 94%
Hate symbols 65% 65% 73%
Tobacco 68% 67% 72%
Violence 87% 78% 87%
Weapons 84% 88% 80%
Average recall 77.5% 77% 82%
Safe images flagged 6.5% 4.7% 4.2%
Cross-validated average recall 73.5% 79.5% 82.4%

Every model is calibrated with one rule: the highest recall with at most ~5% of safe images flagged. Because a single tuning/test split proved unreliable at this size, the last row scores each half of the images with the calibration chosen on the other half; it is the fairest estimate of real-world performance. Precision on the default model's held-out half is 100% for alcohol, hate symbols, violence and weapons, and 82–89% for drugs and tobacco. Method, licences and full results →

Quality

Every push is checked in CI with:

  • Unit and integration tests on Python 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14, plus a test against the real model.
  • Lint, formatting and type checks (ruff, mypy); the package ships type information (py.typed).
  • The Docker image built and smoke-tested end to end, moderating a real image and a real video inside the container.
  • A strict docs build, so broken links and docstrings fail the build.

Releases are automatic: when a version bump passes CI on main, it is tagged and published to PyPI (through trusted publishing, with no stored tokens), GitHub Releases and the GitHub container registry.

Community

Responsible use

openmoderate is meant to help keep platforms safe. Do not use it to surveil individuals or to profile people by protected characteristics. Moderation models make mistakes, so give users a way to appeal automated decisions.

License

Apache 2.0 © openmoderate contributors. Benchmark images keep their own licences (see benchmark/manifest.csv).

Metadata

Release files for openmoderate 1.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openmoderate 1.1.2
File Size Uploaded
openmoderate-1.1.2.tar.gz 427.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openmoderate 1.1.2
File Interpreter ABI Platform
openmoderate-1.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 482.2 kB

Release files / openmoderate-1.1.2.tar.gz

Download URL openmoderate-1.1.2.tar.gz
Size 427.8 kB
Tags Source
SHA-256 checksum
How to use checksums
79cd843e0c652a2f42bc60abe0178d1056b2779327049806a8ff78f1a3d72b68
BLAKE2b-256 checksum
How to use checksums
ed296bf99994c6f5c73efa4183dc7eae3d53ef532f2ffd28b9d7c7474e574263
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / openmoderate-1.1.2-py3-none-any.whl

Download URL openmoderate-1.1.2-py3-none-any.whl
Size 54.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e2911a55274ac15bed7d341804647413fce1723485a181fa97d940d167789644
BLAKE2b-256 checksum
How to use checksums
36bf057d480dde5df73ff0035eb38e23ebb329ae84a6a923f8f7e242abfaeab5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.2 This release

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page