drupal / ai_recipe_validations_image_safety
Validates image uploads to Media: Image with a vision-capable AI provider. Blocks Restricted content (full nudity, explicit sexual content, gore, graphic violence, graphic medical imagery) and accepts Moderate content (partial nudity, sexually suggestive content, mild violence) with a warning. Uses
Package info
git.drupalcode.org/project/ai_recipe_validations_image_safety.git
Type:drupal-recipe
pkg:composer/drupal/ai_recipe_validations_image_safety
Requires
- drupal/ai: ^1.4
- drupal/ai_validations: ^1.4
- drupal/core: ^10.3 || ^11
- drupal/field_validation: ^3.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Classifies every image uploaded to the Media: Image bundle into one of three Flickr-style safety tiers using AI Validations and any vision-capable AI provider:
- Safe — accepted silently.
- Moderate (partial nudity, sexually suggestive content, mild violence) — accepted, but the uploader sees a warning.
- Restricted (full nudity, explicit sexual content, gore, graphic violence, graphic medical imagery) — blocked with a validation error.
Requirements
- Drupal 10.3+ or 11
- AI module 1.4 or newer (provides the
verifySetupAiconfig action and theai_provider_configurationform element) - AI Validations (standalone) 1.4 or newer — provides the opt-in XSAFE/XWARN/XBLOCK warning tier that this recipe relies on (see #3606131) and resolves the shipped rule's empty provider to the site default both in the rule form ("Default" option) and at validation time (see #3586397).
- Field Validation 3.0 or newer — 3.0.0 is a stable release, so the
recipe installs on projects with the default
minimum-stability: stable(Drupal CMS, Project Browser) without any extra step. - A configured default provider for the
chat_with_image_visionoperation type (OpenAI GPT-4o, Anthropic Claude 3.5 Sonnet, Google Gemini 1.5 Pro, etc.) at/admin/config/ai/settings→ Default Providers
Note: the AI module still bundles a deprecated copy of ai_validations.
When the standalone package is installed, Drupal's extension discovery
automatically prefers it (shallower path wins) — no uninstall/reinstall
is needed on existing sites, just a cache rebuild. Without the standalone
package, the deprecated copy treats the recipe's empty provider as an
error and blocks every upload with "No AI provider specified to do
validation", and it does not know the warning tier at all.
Apply
Set up a vision-capable provider before applying. On a site where the AI module is not yet configured, the recipe's preflight check will abort the apply (see "Recovering from a failed apply" below).
composer require drupal/ai_provider_openai drupal/ai_recipe_validations_image_safety
drush en ai ai_provider_openai key -y
(Substitute ai_provider_openai with the provider module of your
choice — ai_provider_anthropic, ai_provider_google_gemini, etc.)
Then, in the admin UI:
- Add your API key at
/admin/config/system/keys. - Configure the provider at
/admin/config/ai/providers. - Set a default model for the
chat_with_image_visionoperation type at/admin/config/ai/settings→ Default Providers.
Finally, apply the recipe:
php core/scripts/drupal recipe ../recipes/ai_recipe_validations_image_safety
drush cache:rebuild
Recovering from a failed apply
If the apply aborts with "The operation type 'chat_with_image_vision' does not have a default model", the site was rolled back to its pre-apply state — modules reported as installed earlier in the output were reverted as part of the rollback. Recipe application is atomic, and on a site without a configured provider the check cannot pass, because configuring a default model itself requires the AI module. Follow the setup steps above (enable the modules, configure a default model), then re-run the apply.
Upgrading an existing site
The recipe creates the media_image rule set only if it does not exist
yet (createIfNotExists). Re-applying a newer recipe version on a site
that already has the rule set does not change the existing rule.
Either edit the rule by hand as described under "Adjusting the tiers"
(set the prompt's === DECISION === and === OUTPUT FORMAT === blocks
to the XSAFE/XWARN/XBLOCK tokens and fill in the Warning message),
or delete the rule set at Structure → Field Validation and re-apply
the recipe.
What it does
- Applies core's
image_media_typerecipe first (creates the Media: Image bundle if it does not exist). - Installs
ai,ai_validations, andfield_validation. - Creates a field validation rule set
media_imageattached to the Media: Image bundle'sfield_media_imagefield. - Runs the AI image constraint on the
target_idcolumn in entity validation mode — catches UI uploads, JSON:API posts, and programmatic saves alike. - Sends the classification prompt to the vision model and acts on the
single token it returns:
XSAFE— the image is saved, nothing is shown.XWARN— the image is saved and the configured warning message is shown to the uploader (via the messenger) after the save. The warning is also written to theai_validationslogger channel, so headless flows (JSON:API, programmatic saves) keep an audit trail; a warning never blocks those flows.XBLOCK— the save is rejected with the configured error message.- Any other answer, or an image the model cannot classify with confidence, is treated as Restricted and blocked (fail closed).
- Tier boundaries are judged by the model, so an image that sits on the edge of two tiers (for example a classical painting with partially covered nudity) can land in either tier on different uploads. The prompt tells the model to pick the stricter tier whenever two apply, so borderline images err towards a warning or a block, never towards a silent pass.
Adjusting the tiers
The prompt decides which tier an image falls into, and the two messages decide what the uploader sees. To change the defaults:
- Go to Structure → Field Validation → AI image safety (Media: Image)
- Edit the Block restricted imagery, warn on moderate rule
- Change the
=== DECISION ===block of the prompt as needed:- To block Moderate content as well, change its line to:
- If the image is MODERATE, respond with: XBLOCK - To accept Moderate content silently (no warning), change its
line to:
- If the image is MODERATE, respond with: XSAFE - To tighten or loosen a tier, move the relevant bullets between the
SAFE, MODERATE and RESTRICTED sections of
=== SAFETY LEVELS ===.
- To block Moderate content as well, change its line to:
- Edit Message (shown when an image is blocked) and Warning message (shown when a Moderate image is accepted) to suit your site.
Returning to plain blocking (XTRUE/XFALSE)
The warning tier is opt-in in AI Validations: it is active only while the Warning message field is non-empty. To go back to the binary protocol you must change both of the following, otherwise every upload is blocked:
- Empty the Warning message field. With it empty, AI Validations
expects the model to answer
XTRUE(pass) orXFALSE(fail); anXSAFEanswer is then an unrecognised token and fails validation. Rewrite the
=== DECISION ===and=== OUTPUT FORMAT ===blocks of the prompt to useXTRUE/XFALSE, for example:=== DECISION === - If the image is SAFE, respond with: XTRUE - If the image is MODERATE or RESTRICTED, respond with: XFALSE === OUTPUT FORMAT === Respond with exactly one token — either XTRUE or XFALSE — and nothing else. No explanation. No punctuation. No surrounding whitespace.
Conversely, if you only fill in a Warning message without switching
the prompt to the three tokens, an XTRUE answer still passes but an
XFALSE answer blocks, so no warning is ever shown.
Cost note
Every image upload triggers one vision-model API call. Bulk migrations, imports, or high-volume user upload flows will incur proportional cost. Test with your provider's pricing before rolling out to production.
Issue queue
Bugs and feature requests: https://www.drupal.org/project/issues/ai_recipe_validations_image_safety