Installs into .claude/skills of the current project.
Are you the author of Content Moderation Patterns?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/softspark-content-moderation-patterns)
---
name: content-moderation-patterns
description: "Content moderation with Claude: pre-filter vs LLM-classify, categories, thresholds, HITL. Triggers: moderation, safety filter, policy enforcement, content classifier."
effort: medium
user-invocable: false
allowed-tools: Read
---
# Content Moderation Patterns
Apply a versioned product policy with deterministic checks, a structured
classifier, and a review path. Select the model using labeled workload results.
No model family has a universal accuracy or cost advantage for moderation.
## Architecture
```text
input → size/format checks → policy checks → structured classifier → decision
├─ allow
├─ reject
└─ human review
```
Treat submitted text as data, including any instructions it contains. Keep the
classification policy in the system message. Request a short policy-grounded
reason, not hidden reasoning.
## Deterministic checks
Use configured size limits and exact parsed hostname checks for URL policies.
A prefix regex can mistakenly accept `allowed.example.attacker.test`.
```python
from urllib.parse import urlsplit
def is_allowed_url(value, allowed_hosts):
try:
parsed = urlsplit(value)
host = parsed.hostname
port = parsed.port
except ValueError:
return False
return (
parsed.scheme == "https"
and parsed.username is None
and parsed.password is None
and host is not None
and host.casefold() in allowed_hosts
and port in (None, 443)
)
```
This checks an already-extracted URL against normalized exact hostnames. It is
not a general URL extractor or an SSRF defense. Evaluate false positives from
keyword filters instead of assuming a fixed percentage of input should be blocked.
## Structured classifier
Use native `output_config.format`. Supply the selected model, policy and output
budget from application configuration. The following taxonomy is an example;
change its enum and routing thresholds together to match the product policy.
```python
import json
MODERATION_SCHEMA = {
"type": "object",
"properties": {
"categories": {"type": "array", "items": {
"type": "string", "enum": ["clean", "needs_review", "spam", "harassment"],
}},
"confidence": {"type": "number"},
"reason": {"type": "string"},
},
"required": ["categories", "confidence", "reason"],
"additionalProperties": False,
}
def classify(client, model, policy, text, max_tokens):
response = client.messages.create(
model=model,
max_tokens=max_tokens,
system=policy,
output_config={"format": {"type": "json_schema", "schema": MODERATION_SCHEMA}},
messages=[{"role": "user", "content": text}],
)
if response.stop_reason != "end_turn":
raise ValueError(f"Classification incomplete: {response.stop_reason}")
blocks = [block.text for block in response.content if block.type == "text"]
if len(blocks) != 1:
raise ValueError("Expected one classification")
return json.loads(blocks[0])
```
Apply local validation before routing. Refusal, truncation, invalid JSON or an
API failure produces a review/error outcome, never an implicit allow.
See `json-mode-patterns` for schema limitations and response checks.
A repeated `system` string is not automatically cached. If policy size and reuse
justify it, explicitly configure caching as in `prompt-caching-patterns`.
Do not generate heartbeat traffic to keep a cache warm.
## Categories and decision routing
Define categories and blocking behavior in the product policy. Keep `clean`
exclusive: a result containing both `clean` and a violation is inconsistent.
Use `needs_review` for uncertainty. Thresholds come from calibration and policy,
not the model's claim that its confidence is reliable.
```python
import math
def route(classification, block_thresholds, allow_threshold):
if not isinstance(classification, dict) or set(classification) != {"categories", "confidence", "reason"}:
return "human_review"
if not isinstance(classification["reason"], str):
return "human_review"
confidence = classification.get("confidence")
categories = classification.get("categories")
if (type(confidence) not in (int, float)
or not 0 <= confidence <= 1 or not math.isfinite(confidence)):
return "human_review"
if not isinstance(categories, list) or not categories or not all(isinstance(c, str) for c in categories):
return "human_review"
categories = {category.casefold() for category in categories}
if categories - (set(block_thresholds) | {"clean", "needs_review"}):
return "human_review"
if "needs_review" in categories or ("clean" in categories and len(categories) != 1):
return "human_review"
if categories == {"clean"}:
return "pass" if confidence >= allow_threshold else "human_review"
if any(confidence >= block_thresholds[category] for category in categories):
return "reject"
return "human_review"
```
Validate configuration thresholds as finite numbers in [0, 1] at startup.
The example's category thresholds are policy-specific; it does not decide
which categories your product must reject.
## Evaluation and review
Use held-out labeled examples covering language, context, quoted material, benign
mentions and adversarial inputs. Track precision, recall, appeal outcomes and
per-category error cost. Neither false positives nor false negatives are always
cheaper; the product policy determines that trade-off.
Send ambiguous cases to human review. Store decision metadata, policy/model
versions and the minimum evidence needed for review under the application's
retention and access controls. Do not indiscriminately log raw sensitive input.
Refresh evaluations when the policy, model or input distribution changes.
Run an offline comparison before deploying a new route or threshold.
## Sources and related skills
Reviewed 2026-09-23:
- [Content moderation](https://platform.claude.com/docs/en/about-claude/use-case-guides/content-moderation)
- [Structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)
- [Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
Use `security-patterns` for application input security, `model-routing-patterns`
for model evaluation and `prompt-caching-patterns` for policy caching.