drupal / ai_recipe_validations
Adds AI-powered validation to Drupal CMS: Basic page content is checked against an editorial policy, using AI Validations and any chat-capable AI provider.
Package info
git.drupalcode.org/project/ai_recipe_validations.git
Type:drupal-recipe
pkg:composer/drupal/ai_recipe_validations
Requires
- drupal/ai: ^1.4
- drupal/ai_validations: ^1.4
- drupal/core: ^10.4 || ^11
- drupal/field_validation: ^3.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-05 09:51:34 UTC
README
Adds AI-powered validation to the editorial workflow of a Drupal CMS site, so AI enforces content quality before content is saved instead of after it is published. A chat-capable AI provider checks the text of every Basic page against an editorial content policy. The save is rejected, with a clear message to the editor, when the text contains:
- personal data about a private individual (personal email address or phone number, home address, date of birth, ID / passport / social security number, bank or card number, health information, …),
- offensive language (profanity, slurs, threats, harassment, or content that demeans people based on a protected characteristic), or
- unfinished placeholder text ("lorem ipsum", "TODO", "TBD", "insert text here", …).
Everything else passes, including critical or controversial content that is expressed respectfully. Published contact details of an organization (a departmental email address, a public office phone number) are allowed.
Uses AI Validations on top of Field Validation, and works with any AI provider (OpenAI, Anthropic, Google Gemini, Mistral, Ollama, …). The recipe validates text only; see "Image uploads" below for the companion recipe that checks images.
Requirements
- Drupal 10.4+ or 11
- AI module 1.4 or newer (provides the
verifySetupAiconfig action used for the preflight check) - AI Validations (standalone) 1.4 or newer. The shipped rule stores an empty provider, which this version resolves to the site default for the operation type; 1.4 also ships the config schema for its rules and skips empty text.
- Field Validation 3.0 or newer
- A Basic page content type (
page). Drupal CMS 1.x and 2.x provide it with a Content field (field_content); a site installed from the Standard profile provides it with a Body field (body). The recipe validates the one text field you name when applying it (see "Recipe inputs"). It does not create the content type itself: the Drupal CMS recipes that do (drupal_cms_page) are abandoned and cannot be installed next to Drupal CMS 2.x. - A default model for the
chatoperation type 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 save with "No AI provider specified to do validation".
Apply
Set up a provider before applying. On a site where the AI module is not yet configured, the recipe's preflight check aborts the apply (see "Recovering from a failed apply" below).
composer require drupal/ai_provider_openai drupal/ai_recipe_validations
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. On a Drupal CMS
site where the AI recipe is applied, a provider is already configured;
check that the chat operation type has a default model.)
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
chatoperation type at/admin/config/ai/settings→ Default Providers.
Finally, apply the recipe from the Drupal root (the web directory in a
Composer project). The recipe validates one text field, which you choose
with the field_name input. The defaults are the page content type and
the Drupal CMS field field_content, so on Drupal CMS no input is needed:
php core/scripts/drupal recipe ../recipes/ai_recipe_validations
drush cache:rebuild
On a site installed from the Standard profile, whose Basic page text field
is body, pass it explicitly:
php core/scripts/drupal recipe ../recipes/ai_recipe_validations \
--input=ai_recipe_validations.field_name=body
drush cache:rebuild
Recipe inputs
| Input | Default | Meaning |
|---|---|---|
bundle | page | Machine name of the content type to validate. |
field_name | field_content | Machine name of the text field to validate: field_content (Drupal CMS) or body (Standard profile). |
You are not limited to body or field_content: any text field works
(string, string_long, text, text_long, text_with_summary). The
recipe checks that the field exists on the content type, not its type: a
field of another type is accepted, but its rule then validates nothing
useful.
Pass them as --input=ai_recipe_validations.bundle=... and
--input=ai_recipe_validations.field_name=.... In an interactive terminal
without --input you are asked for them, with the defaults offered;
non-interactive runs use the defaults. A UI built on core's recipe input
form shows the same two fields. Core's installer does not collect recipe
input at all (the placeholders would stay unreplaced), so apply this recipe
after installation. The field must exist on the content type, otherwise
the apply aborts.
Recovering from a failed apply
If the apply aborts with "The operation type 'chat' does not have a default model", the recipe created no validation rule: its rule set is only created after the preflight succeeds, so a failed apply cannot leave Basic pages impossible to save. Configure the default model, then re-run the apply.
What it does
- Installs
ai,ai_validations,field_validationandnode. - Checks that the
chatoperation type has a default model, then creates thenode_<bundle>field validation rule set (node_pageby default) with one AI text prompt constraint rule on the field given by thefield_nameinput (field_contenton Drupal CMS,bodyon the Standard profile), so a page save costs one AI call. The recipe also checks that the field exists on the content type; the apply aborts otherwise. The rule set is created by a config action that runs after the check, not shipped as config, so a failed check leaves nothing behind. - Rules run in entity validation mode, so they cover the node form, JSON:API posts and programmatic saves alike.
- Re-applying the recipe is safe: a rule set that already exists is left alone, including one an administrator has disabled.
Field Validation keys rule sets by entity type and bundle (node_page):
there is one rule set per bundle. If the rule set for the chosen bundle
(node_page by default) already exists on the site (another recipe or a
site builder created it), the recipe's one is skipped. Add the rule by hand
in that case: Structure → Field validation → (Basic page rule set) → Add
rule → AI text prompt constraint, and copy the prompt and message from the
field_validation.rule_set.node_${bundle} action in recipe.yml.
Image uploads
This recipe validates text only. To also check the images uploaded to the
Image media type with a vision-capable AI provider, apply the
AI image safety validation
recipe next to this one. It blocks Restricted imagery (nudity, explicit
sexual content, gore, graphic violence, graphic medical imagery), accepts
Moderate content (partial nudity, sexually suggestive content, mild
violence) with a warning, and needs a default model for the
chat_with_image_vision operation type:
composer require drupal/ai_recipe_validations_image_safety
php core/scripts/drupal recipe ../recipes/ai_recipe_validations_image_safety
The two recipes are independent: apply them in either order, or only one of them. The image policy and how to adjust it are documented in that recipe's README.
Adjusting the policy
The policy lives entirely in the rules' prompts, so it can be changed in the UI without code:
- Go to Structure → Field validation → AI validations (the rule set of your content type).
- Edit the Enforce the editorial content policy rule for your field.
- Change the numbered items under
=== POLICY ===(for example, drop item 3 to allow drafts with placeholder text, or add a tone-of-voice rule), and update the Message so editors know what to fix.
Keep the === DECISION === and === OUTPUT FORMAT === sections: a
constraint passes only when the model answers with the token XTRUE.
To validate another text field or content type, add a rule to that bundle's rule set with the same plugin and prompt. The prompt is written for text with HTML markup, so it works for both plain and formatted text fields.
Cost note
Every save of a node of the chosen content type triggers one chat API call with the full text of the validated field; an empty field costs nothing (AI Validations 1.4 skips empty values). Bulk migrations, imports, or large editorial teams will incur proportional cost. Long pages also need a model with a sufficient context window.
Tests
The functional tests in tests/src/Functional apply the recipe with a
scripted AI provider (tests/modules/ai_recipe_validations_test) whose
XTRUE/XFALSE answer each test sets:
CoreBasicPageTestuses core's Basic page, whose text field isbody, so it applies the recipe with--input=ai_recipe_validations.field_name=body. It covers the preflight failure without a chat model and that it leaves no rule set behind, the editor-facing message in the node form, and re-applying the recipe.DrupalCmsCompatibilityTestuses Drupal CMS (field_content). It runs in the GitLab CI "Drupal CMS" job, which.gitlab-ci.ymlturns on.
The tests keep core's strict config schema checking on; AI Validations 1.4 ships the schema for its rule plugins.
Issue queue
Bugs and feature requests: https://www.drupal.org/project/issues/ai_recipe_validations