Fast, offline, private grammar and spelling checking for the Joplin Markdown editor on desktop and mobile, powered by Harper. Per-kind coloured underlines, a suggestion card with one-click fixes, add-to-dictionary, ignore and disable-rule, plus American, British, Australian and Canadian dialects. The dictionary can live in a synced Joplin note so words follow you across devices.
| Links: | |
|---|---|
| Maintainers: | pmslava |
| Version: | 1.3.2 |
| Minimum app version: | 3.1 |
| Downloads: This version: | 6 |
| Last updated: | 2026-08-24T21:48:35Z |
Grammar and spell checking for Joplin's Markdown editor, on desktop and Android, powered by Harper — the fast, private grammar checker from Automattic.
Harper runs entirely on your machine. There is no cloud service, no account, and no network request: your notes never leave your computer. The whole checker (a compiled WebAssembly engine) ships inside the plugin, so it works fully offline.
As you type in the Markdown editor, Harper underlines spelling and grammar issues with a coloured squiggle, and clicking (or tapping) an underline opens a small card with the problem, one-click fixes, and follow-up actions.


Per-kind coloured underlines — a red spelling squiggle, an orange typo, a green word-choice suggestion:

By default, words you add via Add to dictionary are stored in the plugin's own private word list on that device. Two settings turn it into one dictionary shared everywhere:
Dictionary note (desktop and mobile). Run the command Harper: Create dictionary note and the plugin creates a regular Joplin note called "Harper Dictionary" — one word per line — and remembers it in the Dictionary note setting. From then on, added words go into that note, and because it is an ordinary note it syncs to every device through your normal Joplin sync. This is also the only dictionary mechanism on mobile. You can edit the note by hand; deleting a line removes the word everywhere.
External dictionary file (desktop only). Point External dictionary file at a plain-text dictionary file of your own — one word per line. When that path is set:
Deleting a word is the one case where the plugin writes more than a new line, so it does as little as it can: it drops only the lines for words you removed, appends genuinely new words at the end, and leaves every other line, comments included, exactly where it was. Surviving lines are never reordered or rewritten, and the file is left completely untouched when nothing changed. Writes go through a temporary file in the same directory and an atomic rename, and if the file changes while the plugin is working on it, the write is abandoned and retried on the next pass, so a sync client writing at the same moment is never clobbered.
This is deliberately just a flat text file with no special format. That makes it easy to keep the
file wherever you like and sync it between machines with your own tooling (a synced folder, a
version-controlled dotfile, rsync/rclone, and so on) — or to share the same word list with
another tool that reads a plain word-per-line dictionary, such as harper-ls in Zed, Neovim, or
VS Code.
Using both together is the full setup: with the note and the file both configured on desktop, the plugin keeps them in sync with each other. A word added on your phone reaches the note through Joplin sync, then the file — and from there any external tool that reads it. Deletions travel the same way, in every direction.
Open Tools → Options → Harper on desktop, or Configuration → Plugins → Harper on mobile.
| Setting | Default | What it does |
|---|---|---|
| Enable Harper grammar checking | On | Master switch. When off, no underlines are shown. |
| Dictionary note | (empty) | The Joplin note that holds your shared word list. Set automatically by the Harper: Create dictionary note command. See Your dictionary. |
| English dialect | American | Which English variety Harper checks against: American, British, Australian, or Canadian. |
| Lint debounce (ms) | 500 |
How long the editor waits after you stop typing before re-checking, in milliseconds (0–10000). Changes apply immediately. |
| Underline style | Squiggly | How findings are underlined: Squiggly (default) for Harper's wavy underline, or Solid line for a straight 2 px line with a light tint. Either way the colour is the issue type's. Changes apply immediately. |
| External dictionary file | (empty) | Desktop only. Absolute path to a plain-text dictionary (one word per line). See Your dictionary. |
| Rule overrides (JSON) | (empty) | Advanced. A JSON object of {"RuleName": true | false} applied on top of the defaults, e.g. {"SpelledNumbers": false}. Invalid JSON is ignored. |
In Joplin desktop, go to Tools → Options → Plugins, search for Harper, and click Install. Restart Joplin when prompted.
Joplin mobile's plugin support is marked Beta and is off by default. In Configuration →
Plugins, enable plugin support first, then search for Harper and install — or use
Install from file with the .jpl from the releases page. Restart the app when prompted.
io.github.pmslava.harper.jpl from the
latest release..jpl..jpl roughly 21 MB. That is large for a plugin, but it is the whole checker, downloaded once.Harper runs locally and makes no network calls. Your note text is passed to the bundled WebAssembly engine inside Joplin and nowhere else — nothing is uploaded, and there is no telemetry. The external dictionary, if you use one, is a file on your own disk; the dictionary note is an ordinary Joplin note that travels only through your own Joplin sync, like every other note.
npm install
npm test # builds the plugin and runs the harness suite (incl. performance budgets)
npm run test:e2e # full end-to-end suite: launches a real Joplin desktop under Xvfb (Linux)
npm run dist # build only -> publish/io.github.pmslava.harper.jpl
test:e2e starts a real Joplin, so it first takes a machine-wide lock
(~/.cache/joplin-plugin-e2e.lock) shared with the author's sibling Joplin plugin repos: exactly one
E2E run exists at a time, and a run that finds the lock held waits its turn rather than piling a
second Joplin onto the machine (E2E_LOCK_WAIT_MS sets the budget, default 10 minutes; 0 fails
fast). The rest of the resource discipline — orphan sweep, RAM gate, signal teardown — lives in
e2e/guard.ts.
Architecture in one line: the grammar checker (harper.js LocalLinter, WASM) runs in the
plugin main process, and a CodeMirror 6 content script draws the underlines and the suggestion card,
talking to it over Joplin's postMessage bridge.
For the full design, the WASM loading approach, the UI spec, and the research behind the plugin, see
docs/ — in particular docs/SPEC.md.
The plugin is licensed under the MIT License.
It embeds and is powered by Harper (via the
harper.js package), which is developed by Automattic
and licensed under the Apache License 2.0. All credit for the grammar-checking engine goes to the
Harper project — this plugin only integrates it into Joplin.