Search by

drupal / ai_recipe_validations

Drupal-Infrastructure

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

Homepage

Type:drupal-recipe

pkg:composer/drupal/ai_recipe_validations

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

1.0.x-dev 2026-11-05 09:50 UTC

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 verifySetupAi config 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 chat operation 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:

  1. Add your API key at /admin/config/system/keys.
  2. Configure the provider at /admin/config/ai/providers.
  3. Set a default model for the chat operation 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

InputDefaultMeaning
bundlepageMachine name of the content type to validate.
field_namefield_contentMachine 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_validation and node.
  • Checks that the chat operation type has a default model, then creates the node_<bundle> field validation rule set (node_page by default) with one AI text prompt constraint rule on the field given by the field_name input (field_content on Drupal CMS, body on 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:

  1. Go to Structure → Field validation → AI validations (the rule set of your content type).
  2. Edit the Enforce the editorial content policy rule for your field.
  3. 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:

  • CoreBasicPageTest uses core's Basic page, whose text field is body, 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.
  • DrupalCmsCompatibilityTest uses Drupal CMS (field_content). It runs in the GitLab CI "Drupal CMS" job, which .gitlab-ci.yml turns 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