npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@consilioweb/payload-spellcheck

v0.13.1

Published

Payload CMS spellcheck plugin — LanguageTool + Claude AI fallback, dashboard, sidebar field, auto-check on save

Downloads

129

Readme

[!IMPORTANT]

⚠️ Next.js 16 + Turbopack — Known Issue

If you're using Next.js 16 with Turbopack (default bundler), you may encounter a createContext is not a function error during next build. This is a known Payload CMS issue (#15429, #14330) — not specific to this plugin.

Workaround — Add this to your admin page (src/app/(payload)/admin/[[...segments]]/page.tsx):

export const dynamic = 'force-dynamic'

And ensure all @consilioweb/* packages are in transpilePackages in your next.config.ts:

transpilePackages: ['@consilioweb/seo-analyzer', '@consilioweb/admin-nav', /* ...other @consilioweb packages */],

Next.js 15 works without any workaround.

About

@consilioweb/payload-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 |

Table of Contents

Features

Dashboard (/admin/spellcheck)

  • 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

Sidebar Field

  • 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

Auto-check on Save

  • Non-blocking — Fire-and-forget async (IIFE pattern, does not slow down saves)
  • Upsert results — Stores/updates results in spellcheck-results collection
  • Configurable — Enable/disable via checkOnSave option

LanguageTool Engine

  • 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

Claude AI Fallback (Optional)

  • 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

Dynamic Dictionary

  • 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 .txt file
  • 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)

Lexical JSON Support

  • 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

Installation

# npm
npm install @consilioweb/payload-spellcheck

# pnpm
pnpm add @consilioweb/payload-spellcheck

# yarn
yarn add @consilioweb/payload-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 |

Quick Start

Add the plugin to your Payload config:

// src/plugins/index.ts (or payload.config.ts)
import { spellcheckPlugin } from '@consilioweb/payload-spellcheck'

export default buildConfig({
  plugins: [
    spellcheckPlugin({
      collections: ['pages', 'posts'],
      language: 'fr',
    }),
  ],
})

Then regenerate the import map:

npx payload generate:importmap

That's it! The plugin automatically:

  • Creates spellcheck-results and spellcheck-dictionary collections (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 afterChange hook for auto-checking on save
  • Auto-fixes missing DB columns on init (SQLite/Postgres push:true compatibility)

Configuration

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,

  // ── RBAC (v0.13.0) ────────────────────────────────────


  // Custom access control function (default: admin-only)
  access: ({ req }) => req.user?.role === 'admin',

  // ── Advanced (v0.13.0) ─────────────────────────────────

  // Custom package name for component paths (monorepo support)
  packageName: '@my-scope/spellcheck',

  // Trust x-forwarded-for header for IP-based rate limiting
  trustProxy: false,
})

Options Reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | collections | string[] | ['pages', 'posts'] | Collections à vérifier | | contentField | string | 'content' | Nom du champ rich text à extraire | | language | string | 'fr' | Code langue pour LanguageTool | | checkOnSave | boolean | true | Vérification automatique à la sauvegarde | | addSidebarField | boolean | true | Ajouter le champ sidebar dans l'éditeur | | addDashboardView | boolean | true | Ajouter la vue /admin/spellcheck | | addListColumn | boolean | true | Ajouter la colonne score dans les listes de collection | | endpointBasePath | string | '/spellcheck' | Chemin de base pour les endpoints API | | enableAiFallback | boolean | false | Activer l'analyse sémantique Claude AI | | anthropicApiKey | string | — | Clé API Anthropic pour Claude | | skipRules | string[] | [] | IDs de règles LanguageTool à ignorer | | skipCategories | string[] | [] | Catégories LanguageTool à ignorer | | customDictionary | string[] | [] | Mots à ne jamais signaler comme erreurs | | languageToolUrl | string | 'https://api.languagetool.org/v2/check' | URL de l'API LanguageTool (pour instances auto-hébergées) | | warningThreshold | number | 80 | Score en dessous duquel un avertissement est affiché | | autoFixSchema | boolean | true | Auto-fix missing DB columns on startup | | access | function | Admin-only | Custom access control function for endpoints (v0.13.0) | | packageName | string | '@consilioweb/payload-spellcheck' | Custom package name for component paths — useful in monorepos (v0.13.0) | | trustProxy | boolean | false | Trust x-forwarded-for header for IP-based rate limiting (v0.13.0) | | rateLimits | object | -- | Rate limiting overrides (see below) | | timeouts | object | -- | Timeout and limit overrides (see below) |

rateLimits

| Option | Type | Default | Description | |--------|------|---------|-------------| | rateLimits.validate | number | 30 | Max requêtes par fenêtre pour /validate | | rateLimits.fix | number | 20 | Max requêtes par fenêtre pour /fix | | rateLimits.fixAll | number | 5 | Max requêtes par fenêtre pour /fix-all | | rateLimits.bulk | number | 3 | Max requêtes par fenêtre pour /bulk | | rateLimits.dictionary | number | 60 | Max requêtes par fenêtre pour /dictionary | | rateLimits.windowMs | number | 60000 | Fenêtre de rate limiting en millisecondes |

timeouts

| Option | Type | Default | Description | |--------|------|---------|-------------| | timeouts.languageTool | number | 30000 | Timeout de l'API LanguageTool en ms | | timeouts.claude | number | 60000 | Timeout de l'API Claude en ms | | timeouts.maxTextLengthLanguageTool | number | 18000 | Longueur max du texte envoyé à LanguageTool (caractères) | | timeouts.maxTextLengthClaude | number | 8000 | Longueur max du texte envoyé à Claude (caractères) | | timeouts.bulkRateLimitDelay | number | 3000 | Délai entre les appels API LanguageTool lors d'un scan bulk (ms) | | timeouts.bulkStaleTimeout | number | 600000 | Timeout pour les jobs bulk stale (ms) |

Admin Views

Dashboard (/admin/spellcheck)

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

Sidebar Field

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

API Endpoints

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 |

POST /api/spellcheck/validate

// 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"
}

POST /api/spellcheck/fix

{
  "id": "123",
  "collection": "pages",
  "original": "une test",
  "replacement": "un test",
  "offset": 42,
  "length": 8
}

offset and length enable precise offset-based targeting (v0.8.0+). Falls back to substring search if omitted.

GET /api/spellcheck/dictionary

Response:

{ "words": [{ "id": "1", "word": "typescript", "addedBy": { "email": "[email protected]" }, "createdAt": "..." }], "count": 1 }

POST /api/spellcheck/dictionary

// Single word
{ "word": "TypeScript" }

// Multiple words
{ "words": ["TypeScript", "Next.js", "Payload"] }

DELETE /api/spellcheck/dictionary

// Single
{ "id": "abc123" }

// Multiple
{ "ids": ["abc123", "def456"] }

POST /api/spellcheck/bulk

// Scan all configured collections
{}

// Scan a specific collection
{ "collection": "posts" }

Engine

Text Extraction

The plugin extracts text from Payload documents by recursively traversing:

  1. Title — Document title
  2. Herohero.richText (Lexical JSON)
  3. Content — Main content field (Lexical JSON)
  4. Layout blocks — Each block's richText and columns[].richText

Code blocks are automatically skipped (not natural language).

LanguageTool

  • 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

Filtering

Issues are filtered through multiple layers:

  1. Premium rules — Skipped (free API only)
  2. Configured rulesskipRules option
  3. Configured categoriesskipCategories option
  4. Custom dictionary — Case-insensitive word matching
  5. Single-character — Skipped (often punctuation false positives)

Scoring

Score = max(0, 100 - (issues / words * 1000))

  • 100 — No issues
  • 90+ — Excellent (green)
  • 80+ — Good (yellow)
  • <80 — Needs work (red)

Claude AI (Optional)

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.

Collections

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.

Package Exports

Main Entry (@consilioweb/payload-spellcheck)

// 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'

Client Entry (@consilioweb/payload-spellcheck/client)

export { SpellCheckField } from './components/SpellCheckField'
export { SpellCheckDashboard } from './components/SpellCheckDashboard'
export { IssueCard } from './components/IssueCard'

Views Entry (@consilioweb/payload-spellcheck/views)

export { SpellCheckView } from './views/SpellCheckView'

Requirements

  • Node.js >= 18
  • Payload CMS 3.x
  • React 18.x or 19.x
  • Any Payload DB adapter (SQLite, PostgreSQL, MongoDB)

Uninstall

Automatic (recommended)

npx spellcheck-uninstall

This will:

  1. Remove all @consilioweb/payload-spellcheck imports and plugin calls from your source files
  2. Drop the spellcheck_results table and indexes from your database
  3. Remove the npm package
  4. Regenerate the import map

Use --keep-data to preserve the database table.

Manual

  1. Remove the plugin from your config
  2. Run npx payload generate:importmap
  3. (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`;

Changelog

v0.13.0

  • New: RBAC with configurable access function in plugin config
  • New: packageName option for custom package name in component paths
  • New: useSpellcheckI18n hook for component localization
  • New: i18n integration in SpellCheckField and IssueCard components
  • New: Client-side score cache (30s TTL) in SpellCheckScoreCell
  • New: Collection injection protection on validate, fix, fixAll endpoints
  • New: IP spoofing protection with trustProxy option
  • Changed: fixAll calls fix logic directly instead of HTTP self-fetch (eliminates SSRF)
  • Changed: URL built from NEXT_PUBLIC_SERVER_URL, not Origin header
  • Changed: Score formula alignment between client and server

v0.8.1

  • 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:true compatibility for SQLite/Postgres)

v0.8.0

  • New: Dynamic dictionary — manage words from the admin dashboard (add/remove/import/export)
  • New: spellcheck-dictionary collection (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: filterFalsePositives is now async (merges config + DB dictionaries)
  • Changed: Fix endpoint accepts offset and length parameters (falls back to substring search)

v0.5.0 — v0.7.0

  • Custom dictionary config, contextual multi-word filtering, background scan
  • Lexical ghost space fix, extended French dictionary
  • Contextual offset, manual edit input, repetition filter

Roadmap

  • Bulk scan with AI (Claude) integration
  • Export spell check results as CSV/JSON
  • Webhook / notification on new issues found
  • Per-field spellcheck configuration
  • Custom dictionaries per collection
  • Grammar rules beyond spelling (style, tone, consistency)
  • Auto-correct suggestions with one-click apply
  • Integration with external APIs (Grammarly, LanguageTool Cloud)

☕ Support

If this plugin saves you time, consider buying me a coffee!

License

MIT License - see LICENSE for details.