Editorial QA for Craft CMS 5. RedPen enforces your house style guide on every entry — banned terms, brand capitalisation, readability, structure, accessibility and typography — and can add real grammar review from a self-hosted LanguageTool server or an LLM you bring your own key for.

It is not a spellchecker. Your browser already does that, for free, in every field. RedPen checks the things a browser cannot: the rules your organisation actually wrote down.

Requirements

Craft CMS 5.3+ and PHP 8.2+.

Editions

Lite (free)Pro
Full rule library
Review panel on entry edit
One default style profile
Multiple profiles, per-section assignment
Save-time validation (warn or block)
Site-wide audit, report and dashboard widget
LanguageTool and LLM backends
Console command for CI
Editorial sign-off

What it checks

Twelve rules ship in the box, across seven categories.

Terminology — banned and preferred terms (utilize => use), brand and product capitalisation (GithubGitHub).

Grammar — repeated words.

Accessibility — missing or useless image alt text, link text that means nothing out of context (click here), empty links, links that go nowhere.

Structure — headings that skip a level, are empty, or repeat.

Typography — double spaces, spaces before punctuation, missing spaces after it.

Inclusivity — gendered job titles, ableist metaphors, and terms with settled neutral equivalents.

Style and readability — weasel words, passive voice, sentence length, Flesch-Kincaid grade level.

These four ship off or as notices, because they are matters of taste and a wall of warnings on day one teaches people to ignore the plugin.

Every rule can be enabled, disabled, re-severitied and configured per profile, and profiles can be scoped to a section, a site, or both.

Severities

  • Notice — recorded and shown, never in the way.
  • Warning — shown prominently, saves anyway.
  • Error — blocks the save, if you have turned that on.

checkOnSave and blockOnError both default to off. A plugin that starts refusing saves the moment it is installed is a support ticket, so switching that on is a deliberate act.

Backends

RedPen runs up to three checkers, in order, and merges their findings:

  1. Style rules — the built-in library. Pure PHP, no network, no cost, works everywhere. Always on.
  2. LanguageTool — point languageToolEndpoint at a self-hosted server (docker run -p 8010:8010 erikvl87/languagetool). RedPen never calls LanguageTool's public API: their terms forbid automated requests, and defaulting to it would break every install at once the first time they enforce that.
  3. LLM — a Claude or OpenAI key of your own. Real grammar rewriting and brand-voice review, billed to you. Suggestions land in a diff with per-item accept/reject; RedPen never rewrites copy silently.

An unconfigured backend reports itself unavailable and is skipped, so a missing endpoint never breaks a check the rule engine could have answered.

Extending it

Three events, all on the plugin's services:

use justinholtweb\redpen\events\RegisterRulesEvent;
use justinholtweb\redpen\services\Rules;
use yii\base\Event;

Event::on(Rules::class, Rules::EVENT_REGISTER_RULES, function(RegisterRulesEvent $event) {
    $event->rules[] = new MyHouseRule();
});

Rules::EVENT_REGISTER_RULES adds or replaces a rule (reuse an id to override a built-in), Extractors::EVENT_REGISTER_EXTRACTORS teaches RedPen to read a custom field type, and Checkers::EVENT_REGISTER_CHECKERS adds a validation backend.

A rule is one small class:

class NoFirstPersonRule extends PhraseRule
{
    public function id(): string { return 'style.no-first-person'; }
    public function label(): string { return 'No first person'; }
    public function category(): string { return Issue::CATEGORY_STYLE; }

    protected function phrases(): array
    {
        return ['I think' => null, 'in my opinion' => null];
    }
}

Rules take a Fragment and return Issues. They touch no database and no Craft services, which is why the whole library is testable without booting an application.

Settings

SettingDefaultNotes
languageen-USLanguage rules evaluate against
checkOnLoadtrueCheck when an edit screen opens
checkOnSavefalseEvaluate on save (Pro)
blockOnErrorfalseLet an error actually block the save (Pro)
backends['rules']Which checkers run, in order
languageToolEndpoint''Self-hosted LanguageTool base URL. Env-var friendly
llmProvideranthropicanthropic or openai
llmApiKey''Env-var friendly
maxFragmentLength20000Characters per field sent to a network backend
cacheDuration300Seconds a check result is cached

languageToolEndpoint, llmApiKey and llmModel all parse env vars, so credentials stay out of project config.

Running it from the command line

php craft redpen/check                                  # everything, summary only
php craft redpen/check --sections=news,blog --verbose    # list every finding
php craft redpen/check --profile=houseStyle --limit=50   # pin a profile, sample the site
php craft redpen/check --fail-on=error                   # exit non-zero if anything is broken
php craft redpen/check --store                           # keep the run as an audit in the CP

--fail-on is the one that matters in CI: it exits non-zero when anything at that severity or worse is found, so a pipeline can refuse to deploy copy that breaks the style guide. Pro only.

Development

composer install
composer test      # Pest
composer phpstan   # level 5
composer ecs       # coding standards; ecs-fix to apply

The suite runs without booting Craft — no database, no application, ~0.2s. That is deliberate: the text layer, the rule library and the response parsing are plain PHP over plain value objects, and the network backends sit behind HttpClientInterface, so the parts that carry real logic are directly reachable.

The parts that genuinely need Craft — extractors reading field values, project-config profile storage, save-time validation, the audit query — are verified by integration runs against a local Craft install instead, because faking an element well enough to be meaningful costs more than it proves.

To exercise the LanguageTool backend locally:

docker run -d -p 8010:8010 erikvl87/languagetool

then point languageToolEndpoint at it (http://host.docker.internal:8010 from inside DDEV).

Installation Instructions

To install this plugin, copy the command above to your terminal.

Reviews

This plugin doesn't have any reviews.

Active Installs
0
Version
5.0.0
License
Craft
Compatibility
Craft 5
Last release
July 26, 2026