@consilioweb/spellcheck is a Payload CMS 3 plugin that adds real-time spelling and grammar checking to your admin panel. Powered by LanguageTool with optional Claude AI semantic analysis.
| Feature | Description |
|---|---|
| Dashboard | Full admin view at /admin/spellcheck with bulk scanning |
| Sidebar Field | Real-time spellcheck score + issues in the editor |
| Auto-check | Fire-and-forget hook checks content on every save |
| One-click Fix | Apply corrections directly in Lexical JSON |
| LanguageTool | Grammar, spelling, punctuation via free API |
| Claude AI | Optional semantic analysis (coherence, tone, phrasing) |
| Custom Dictionary | Whitelist tech terms, brand names, proper nouns |
| Dynamic Dictionary | Add/remove words from admin UI, persists in DB |
| Offset-based Fix | Precise corrections using LanguageTool offsets |
| Ignore Issues | Dismiss false positives (persists across reloads) |
| i18n | French and English UI translations |
- Features
- Installation
- Quick Start
- Configuration
- Admin Views
- Dynamic Dictionary
- API Endpoints
- Engine
- Package Exports
- Uninstall
- Changelog
- License
- Tabbed interface — Results tab + Dictionary tab
- Selective scan — Check specific pages or all documents at once
- Checkbox selection — Pick individual documents to scan
- Collection filter — Filter by collection (pages, posts, etc.)
- Sortable table — Sort by score, issues, word count, last checked
- Expandable rows — Click a document to see all issues inline
- Before/After diff — Visual comparison of original vs corrected text
- Multiple suggestions — Dropdown to choose between alternative corrections
- One-click fix — Apply corrections directly (issue removed from UI + DB)
- Ignore button — Dismiss false positives (persists in DB across reloads)
- Add to dictionary — Whitelist a word directly from an issue card
- Summary cards — Total documents, average score, issues count
- Score badge — Color-coded score (green/yellow/red) in the editor sidebar
- Issue list — All issues with context, suggestions, and fix buttons
- Manual check — "Vérifier" button for on-demand analysis
- Auto-check — Results loaded automatically from last check
- Non-blocking — Fire-and-forget async (IIFE pattern, does not slow down saves)
- Upsert results — Stores/updates results in
spellcheck-resultscollection - Configurable — Enable/disable via
checkOnSaveoption
- Free API — No API key required (public LanguageTool API)
- Rate-limited — 3-second delay between requests for bulk scans
- 18K char limit — Automatic text truncation for API compliance
- Smart filtering — Skip premium rules, typography, style-only issues
- Custom dictionary — Whitelist words that shouldn't be flagged
- Semantic analysis — Checks coherence, tone, phrasing, missing words
- Complementary — Does NOT duplicate LanguageTool (no spelling/grammar)
- Cost-efficient — Uses Claude Haiku for fast, cheap analysis
- Opt-in — Disabled by default, enable via
enableAiFallback: true
- Admin UI — Manage dictionary from the "Dictionnaire" tab in the dashboard
- Add words — Single word or comma-separated bulk input
- Import — Paste a list of words (one per line or comma-separated)
- Export — Download all dictionary words as a
.txtfile - Search & filter — Find words in the dictionary
- Bulk delete — Select and remove multiple words at once
- Merged sources — Config
customDictionary(defaults) + DB dictionary (dynamic) - 5-min cache — Dictionary loaded from DB with in-memory TTL cache
- Auto-schema — Plugin auto-creates missing DB columns on init (SQLite/Postgres)
- Recursive extraction — Traverses Lexical AST to extract plain text
- Code block skip — Ignores code blocks (not natural language)
- Offset-based fixes — Precise corrections using LanguageTool offsets (v0.8.0+)
- Legacy fallback — Substring search for backwards compatibility
- Multi-field — Extracts from hero, content, layout blocks, columns
# npm
npm install @consilioweb/spellcheck
# pnpm
pnpm add @consilioweb/spellcheck
# yarn
yarn add @consilioweb/spellcheck| Peer Dependency | Version |
|---|---|
payload |
^3.0.0 |
@payloadcms/next |
^3.0.0 |
@payloadcms/ui |
^3.0.0 |
react |
^18.0.0 || ^19.0.0 |
Add the plugin to your Payload config:
// src/plugins/index.ts (or payload.config.ts)
import { spellcheckPlugin } from '@consilioweb/spellcheck'
export default buildConfig({
plugins: [
spellcheckPlugin({
collections: ['pages', 'posts'],
language: 'fr',
}),
],
})Then regenerate the import map:
npx payload generate:importmapThat's it! The plugin automatically:
- Creates
spellcheck-resultsandspellcheck-dictionarycollections (hidden from admin nav) - Registers API endpoints (
validate,fix,bulk,status,dictionary) - Adds a sidebar field to your target collections
- Creates a dashboard view at
/admin/spellcheck(Results + Dictionary tabs) - Adds an
afterChangehook for auto-checking on save - Auto-fixes missing DB columns on init (SQLite/Postgres
push:truecompatibility)
spellcheckPlugin({
// Target collections (default: ['pages', 'posts'])
collections: ['pages', 'posts'],
// Rich text field name (default: 'content')
contentField: 'content',
// LanguageTool language (default: 'fr')
language: 'fr',
// Auto-check on save (default: true)
checkOnSave: true,
// Sidebar field in editor (default: true)
addSidebarField: true,
// Dashboard view at /admin/spellcheck (default: true)
addDashboardView: true,
// Base path for API endpoints (default: '/spellcheck')
endpointBasePath: '/spellcheck',
// ── Filtering ──────────────────────────────────────
// LanguageTool rule IDs to skip
skipRules: ['FR_SPELLING_RULE', 'WHITESPACE_RULE'],
// LanguageTool categories to skip
skipCategories: ['TYPOGRAPHY', 'STYLE'],
// Words to never flag as errors
customDictionary: [
'Next.js', 'Payload', 'TypeScript', 'SEO',
'Corrèze', 'Limoges', 'ConsilioWEB',
],
// Minimum score threshold for warnings (default: 80)
warningThreshold: 80,
// ── Claude AI Fallback (optional) ──────────────────
// Enable semantic analysis via Claude (default: false)
enableAiFallback: false,
// Anthropic API key (required if enableAiFallback is true)
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
})| Option | Type | Default | Description |
|---|---|---|---|
collections |
string[] |
['pages', 'posts'] |
Collections to check |
contentField |
string |
'content' |
Rich text field name |
language |
string |
'fr' |
LanguageTool language code |
checkOnSave |
boolean |
true |
Auto-check on document save |
addSidebarField |
boolean |
true |
Add sidebar field in editor |
addDashboardView |
boolean |
true |
Add /admin/spellcheck view |
endpointBasePath |
string |
'/spellcheck' |
Base path for API endpoints |
enableAiFallback |
boolean |
false |
Enable Claude AI semantic analysis |
anthropicApiKey |
string |
— | Anthropic API key for Claude |
skipRules |
string[] |
[] |
LanguageTool rule IDs to skip |
skipCategories |
string[] |
[] |
LanguageTool categories to skip |
customDictionary |
string[] |
[] |
Words to never flag |
warningThreshold |
number |
80 |
Score below which a warning is shown |
The dashboard provides a complete overview of your content's spelling quality:
- Summary cards — Document count, average score, total issues, error-free count
- Sortable table — Click column headers to sort by score, issues, words, date
- Expandable rows — Click any row to see detailed issues with context and suggestions
- Bulk scan — "Scanner tout" analyzes all published documents sequentially
- One-click fix — Apply a correction directly from the expanded issue view
The sidebar field appears in the editor for every target collection:
- Score badge — Color-coded (green ≥95, yellow ≥80, red <80)
- Stats bar — Word count, issue count, last check time
- Issue cards — Each issue shows message, context with highlighted error, suggestion
- Fix button — Applies the suggestion directly in the Lexical JSON
- Ignore button — Removes the issue from the current view
All endpoints require authentication (Payload admin user).
| Endpoint | Method | Description |
|---|---|---|
/api/spellcheck/validate |
POST |
Check a single document or raw text |
/api/spellcheck/fix |
POST |
Apply a correction in Lexical JSON (offset-based) |
/api/spellcheck/bulk |
POST |
Scan all documents (sequential, rate-limited) |
/api/spellcheck/status |
GET |
Get current bulk scan progress |
/api/spellcheck/dictionary |
GET |
List all dictionary words |
/api/spellcheck/dictionary |
POST |
Add word(s) to dictionary |
/api/spellcheck/dictionary |
DELETE |
Remove word(s) from dictionary |
// Check a document by ID
{ "id": "123", "collection": "pages" }
// Check raw text
{ "text": "Ceci est une test.", "language": "fr" }Response:
{
"docId": "123",
"collection": "pages",
"score": 85,
"issueCount": 2,
"wordCount": 450,
"issues": [
{
"ruleId": "GRAMMAR",
"category": "GRAMMAR",
"message": "Le déterminant « une » ne correspond pas...",
"context": "Ceci est une test.",
"original": "une",
"replacements": ["un"],
"source": "languagetool"
}
],
"lastChecked": "2025-02-22T20:30:00.000Z"
}{
"id": "123",
"collection": "pages",
"original": "une test",
"replacement": "un test",
"offset": 42,
"length": 8
}
offsetandlengthenable precise offset-based targeting (v0.8.0+). Falls back to substring search if omitted.
Response:
{ "words": [{ "id": "1", "word": "typescript", "addedBy": { "email": "admin@example.com" }, "createdAt": "..." }], "count": 1 }// Single word
{ "word": "TypeScript" }
// Multiple words
{ "words": ["TypeScript", "Next.js", "Payload"] }// Single
{ "id": "abc123" }
// Multiple
{ "ids": ["abc123", "def456"] }// Scan all configured collections
{}
// Scan a specific collection
{ "collection": "posts" }The plugin extracts text from Payload documents by recursively traversing:
- Title — Document title
- Hero —
hero.richText(Lexical JSON) - Content — Main content field (Lexical JSON)
- Layout blocks — Each block's
richTextandcolumns[].richText
Code blocks are automatically skipped (not natural language).
- API:
POST https://api.languagetool.org/v2/check(free, no auth) - Limit: 18,000 characters per request (auto-truncated)
- Rate: 3-second delay between bulk requests
- Timeout: 30 seconds per request
Issues are filtered through multiple layers:
- Premium rules — Skipped (free API only)
- Configured rules —
skipRulesoption - Configured categories —
skipCategoriesoption - Custom dictionary — Case-insensitive word matching
- Single-character — Skipped (often punctuation false positives)
Score = max(0, 100 - (issues / words * 1000))
- 100 — No issues
- 90+ — Excellent (green)
- 80+ — Good (yellow)
- <80 — Needs work (red)
When enableAiFallback: true, the plugin also sends text to Claude Haiku for:
- Inconsistent tone or register
- Incoherent statements or contradictions
- Awkward phrasing
- Missing words that change meaning
Claude issues are tagged with source: 'claude' and category COHERENCE, TONE, PHRASING, or MISSING_WORD.
The plugin auto-creates two collections:
| Collection | Slug | Description |
|---|---|---|
| SpellCheck Results | spellcheck-results |
Stores check results per document |
| SpellCheck Dictionary | spellcheck-dictionary |
Dynamic dictionary (one doc per word) |
Results fields: docId, collection, title, slug, score, issueCount, wordCount, issues (JSON), lastChecked
Dictionary fields: word (text, unique, indexed), addedBy (relationship to users)
Both collections are hidden from the admin nav. The dictionary is managed via the Dashboard's "Dictionnaire" tab or the REST API.
// Plugin
export { spellcheckPlugin } from './plugin'
// Types
export type { SpellCheckPluginConfig, SpellCheckIssue, SpellCheckResult } from './types'
// Engine (for programmatic use)
export { extractTextFromLexical, countWords } from './engine/lexicalParser'
export { checkWithLanguageTool } from './engine/languagetool'
export { checkWithClaude } from './engine/claude'
export { filterFalsePositives, calculateScore } from './engine/filters'
// Dictionary cache
export { loadDictionaryWords, invalidateDictionaryCache } from './endpoints/dictionary'
// i18n
export { getTranslations, getScoreLabel } from './i18n'export { SpellCheckField } from './components/SpellCheckField'
export { SpellCheckDashboard } from './components/SpellCheckDashboard'
export { IssueCard } from './components/IssueCard'export { SpellCheckView } from './views/SpellCheckView'- Node.js >= 18
- Payload CMS 3.x
- React 18.x or 19.x
- Any Payload DB adapter (SQLite, PostgreSQL, MongoDB)
npx spellcheck-uninstallThis will:
- Remove all
@consilioweb/spellcheckimports and plugin calls from your source files - Drop the
spellcheck_resultstable and indexes from your database - Remove the npm package
- Regenerate the import map
Use
--keep-datato preserve the database table.
- Remove the plugin from your config
- Run
npx payload generate:importmap - (Optional) Drop the database table:
-- SQLite
DROP INDEX IF EXISTS `spellcheck_results_doc_id_idx`;
DROP INDEX IF EXISTS `spellcheck_results_collection_idx`;
DROP INDEX IF EXISTS `spellcheck_results_last_checked_idx`;
DROP TABLE IF EXISTS `spellcheck_results`;- Fix: Corrections now remove the issue from the UI immediately (optimistic update)
- Fix: "Ignorer" persists in DB across page reloads (was local state only)
- Fix: Auto-fix missing DB columns on init (
push:truecompatibility for SQLite/Postgres)
- New: Dynamic dictionary — manage words from the admin dashboard (add/remove/import/export)
- New:
spellcheck-dictionarycollection (one document per word, merged with config dictionary) - New: Dictionary REST API (GET/POST/DELETE at
/api/spellcheck/dictionary) - New: Offset-based corrections — precise fix targeting using LanguageTool offsets
- New: "Add to dictionary" button on issue cards (+ Dico)
- New: "Ignore" button to dismiss false positives
- New: Dictionary tab in the dashboard with search, bulk delete, import/export
- New: In-memory cache (5-min TTL) for dictionary DB queries
- Changed:
filterFalsePositivesis now async (merges config + DB dictionaries) - Changed: Fix endpoint accepts
offsetandlengthparameters (falls back to substring search)
- Custom dictionary config, contextual multi-word filtering, background scan
- Lexical ghost space fix, extended French dictionary
- Contextual offset, manual edit input, repetition filter
MIT License - see LICENSE for details.
Made with ❤️ by ConsilioWEB
