# obsidian-languagetool-plugin: grammar checking that respects the vault

> This Obsidian plugin checks spelling and grammar with LanguageTool without sending your notes to a third party you did not choose, and it is unusually careful about what it checks: code blocks, mathematical notation, frontmatter and embeds are skipped, an aliased link is checked only in its visible part, and a per-note frontmatter key can pin the language or switch automatic checking off for a single file.

**Clemens-E/obsidian-languagetool-plugin** — languagetool plugin for obsidian

- Repository: https://github.com/Clemens-E/obsidian-languagetool-plugin
- Stars: 576 · Forks: 49
- Language: TypeScript
- License: AGPL-3.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/clemens-e-obsidian-languagetool-plugin

## What the plugin does

The plugin checks grammar and spelling with LanguageTool without leaving Obsidian. You can run a check over the whole document or over the selection, or let the plugin check as you type. Mistakes are underlined in the editor, and clicking one opens a panel with the message, up to three suggested replacements, and a button to ignore it.

That last part is the interesting detail, because the ignore button for a spelling error is not merely a dismiss. It adds the word to Obsidian's own spellcheck dictionary, so that both this plugin and the editor's built-in checker accept it from then on. The plugin therefore writes into the editor's dictionary rather than keeping a list of its own, which means one decision about a word applies everywhere in the vault rather than only inside this plugin.

Three back ends are supported. There is the free public API, which is rate limited to twenty requests per minute for each address, with texts up to twenty thousand characters. There is the paid API, which needs a username and a key. And there is a self-hosted server, so that the text never leaves your own infrastructure. The plugin does not choose between them for you, and the endpoint is a setting.

The way a check is triggered is ordinary Obsidian: a command in the palette, a hotkey bound in the editor's own hotkey settings, or an optional ribbon icon. With no selection the whole document is checked, and with a selection only that text is. The underlines disappear on their own when the text underneath them is edited, so a check does not leave stale markers behind after you fix a sentence, and pressing escape closes the panel without changing anything while leaving the underline in place so that you can come back to it.

And there is a way to accept a correction without opening the panel at all: with the cursor inside an underline you can apply the first, second or third suggested replacement directly. For a writer working through a document, that is the difference between a spelling tool and a nuisance, because the panel is one click per match and the keyboard path is one keypress.

## What it refuses to check

The most careful part of the documentation is the list of what is not checked, and it is worth reading because it shows the author thought about what a vault actually contains.

Text inside code blocks, mathematical notation, frontmatter and similar regions is never underlined. That is the obvious part: a code block is not prose, and a grammar checker that flags a variable name is noise.

The interesting case is links. In an aliased link, only the alias is checked, because that is the text you actually see on the page, and the link target is treated as markup. That is a small decision with a large effect on the results: a note that links to a file with an unusual name would otherwise have that name checked as if it were a sentence. Embeds are skipped entirely.

There is also a way to exclude a file from everything, including manual checks: add a tag to its frontmatter, and the file is left alone. The tag is a specific one for this plugin, and it lives in the note, so it travels with the file when the file is copied or synced, which is the right place for a decision about a file.

Taken together, the exclusions are the difference between a grammar checker that a writer keeps enabled and one that is turned off after a week. The plugin does not merely avoid checking code; it avoids checking the parts of a note that are not the author's prose, and it gives the author an explicit way to exclude a note entirely when the note is not prose at all.

## Automatic checking, and the per-note override

Automatic checking is a setting, and the design of it is the part of the plugin that shows the most thought about how people actually write.

When it is enabled, the plugin waits until you stop typing for a configured delay and then checks only the lines you changed. Both halves of that matter. The delay is what keeps the plugin from sending a request on every keystroke, which matters enormously when the back end is the free public endpoint with its rate limit. And checking only the changed lines is what keeps the request small, so a long note does not get resent in full every time you pause.

A single note can then deviate from the global setting, in either direction, through a frontmatter key.

```yaml
---
lt-autocheck: false
---
``` Setting it to false keeps that note out of automatic checking even when the setting is on globally; setting it to true checks that note automatically even when the setting is off. Manual checks are unaffected either way, so the key only ever governs the automatic behaviour.

The readme is explicit about why this works reliably: the setting is remembered every time the note is opened, because it lives in the note itself rather than in the editor's settings. That is the point of the design. A note in a synced vault carries its own behaviour with it, so a file that is a template does not start sending requests on somebody else's machine, and a file that needs checking does not stop being checked because it was opened somewhere else.

And there is a command, and a status bar menu entry, that sets the key without opening the frontmatter by hand. Toggling back to the global behaviour removes the key entirely, so the file returns to following whatever the editor is set to do. That is a tidy piece of state management: the note has an override or it does not, and there is no third state where the key says something that no longer applies.

The command list also includes a way to jump to the next or previous suggestion, selecting it and scrolling to it. For a long note, that turns the underlines from a list to scan into a sequence to work through, which is a different and better way to interact with them.

## Pinning a language, and the three keys

LanguageTool detects the language of the text as a whole, and the readme describes exactly how that goes wrong: a note that mixes languages, such as an English template filled with German prose, can end up checked in the wrong one.

The fix is a frontmatter key that pins the language for a single note, using a LanguageTool code such as the German locale, the American English locale, or just the bare language code. And here is the detail that makes the feature usable rather than annoying: the keys for the generic language and the short language are accepted as well, so a note that already carries one of those for a different plugin needs no extra key. That is a small courtesy with a real effect, because notes in a real vault carry frontmatter for several tools.

A pinned language wins over the static language setting in the plugin's own settings, and a note can also opt back into detection with the value that means automatic, so the global setting does not lock every note into one language. The readme also notes a limit that comes from the service rather than from the plugin: the language varieties offered in the settings apply only to notes that are auto-detected, because LanguageTool does not combine them with a fixed language.

That is the whole feature in three behaviours: a per-note override, an explicit escape hatch back to automatic, and a stated limitation inherited from the back end. And the commands list carries the counterpart to the autocheck key: a way to jump between suggestions, accept one of the three replacements with the cursor inside an underline, ignore a match for the rest of the session, and add the word under the cursor to the personal dictionary, with a notice offering an undo for that last one.

## Two small interface decisions, and the mobile case

Two pieces of interface exist because of a problem with the editor itself, and they are worth describing because they are the kind of detail that separates a plugin that was used from one that was written.

The first is the ribbon icon, which is off by default. When it is enabled it appears in the ribbon, the strip on the left on the desktop and the drawer on mobile, and it checks the current note. The reason for the default is the second reason it exists: on mobile the status bar does not exist, so the ribbon icon is the only tap target for a manual check besides the command palette.

The second is the status bar button, a small label that shows a sync icon while a check is running. Clicking it opens a menu with the check action, the two autocheck toggles, and a way to clear all the suggestions without touching the text.

So the two controls are not redundant. On the desktop, the status bar is where your eye already is and the ribbon is a distraction, and the ribbon is a choice you make once. On mobile, the status bar is simply absent and the ribbon is the only thing that can be tapped. A plugin that assumed one layout would work on one platform and be unusable on the other, and this one accounts for both.

The clearing action is also more than a convenience. Underlines are a visual layer over the note, and a note that is full of them is a note that is hard to read, so being able to remove them without changing a single character is a way of switching the plugin off for the view while keeping the setting.

And the last detail in the installation section is a good one for a plugin that sends your writing to a service: the release assets are signed with GitHub's artifact attestations, and the readme gives the exact command to verify them. That is provenance for the binary rather than for the note, and for a plugin that runs inside your editor it is the right thing to provide.

## The repository, the build, and the version

The repository is a conventional Obsidian plugin project, and the manifest is where the interesting decisions sit.

The build is two steps: a type check with no output and skipping the library checks, then a bundler invocation in production mode. The linting is split into two commands, one for the TypeScript and one for the stylesheet, each with a fix variant, and the formatting is a single tool with its options written out: tabs, single quotes, a print width of a hundred and twenty characters, trailing commas, and line endings forced to the Unix style. Those options sit in the package file's scripts rather than in a separate configuration file, which is a slightly unusual choice and a convenient one, since a contributor sees the project's conventions in the same place they see the dependencies.

The test setup is worth noticing. The end to end tests run through a browser automation runner with the mocha framework and a local runner, so the plugin is driven in a real editor rather than tested against mocks. For a plugin whose whole behaviour is underline rendering, popovers and status bar icons, a unit test would have to invent all of that, and a test that invents it tests the invention.

The dependencies show the shape of the work. The editor's own type definitions are there, along with the state and view packages of the code editor that Obsidian is built on, and a plugin for the editor's linting rules. The rest is tooling: the bundler, the linters, the formatter, and the test runner. There is no runtime dependency on the grammar service, which is the right call for a plugin that talks to a service over the network.

Two files at the top level are unusual and worth a note. There is a versions file, which is the map from plugin versions to the editor versions they support, and a script that bumps the version. Together they are the machinery for releasing a plugin that has to work across a range of editor versions, which is the same problem every Obsidian plugin has and which most solve by hand.

The version in the manifest is a good deal higher than the newest published release, which tells you that the development tree is ahead of what has been shipped. And the licence is the Affero General Public Licence, which is the most demanding of the common open source licences and is a deliberate choice for a plugin that other people distribute on their own vaults.

## Conclusion

obsidian-languagetool-plugin fits a writer who keeps everything in a vault and does not want a private document sent to a public service, and a writer who works in more than one language and needs the language of a note pinned rather than guessed. It does not fit somebody who wants a spellchecker with no configuration at all, since the settings are detailed. Verify first which server you point it at, because the free public endpoint is rate limited to twenty requests a minute and texts of twenty thousand characters, that the notes you care about carry the frontmatter keys you expect, and that the version you install matches the manifest the plugin expects, since the releases are versioned separately from the development build.

## FAQ

### What does the obsidian-languagetool-plugin do?

It checks spelling and grammar with LanguageTool inside Obsidian. You can check the whole document or the selection, or let it check automatically after you stop typing, and matches are underlined in the editor with a panel offering up to three replacements, an ignore action, and a way to add a word to Obsidian's own personal dictionary.

### Does the Obsidian LanguageTool plugin send my notes to a cloud service?

Only if you choose to. It works with the free public API, which is rate limited to twenty requests a minute with texts up to twenty thousand characters, with the paid API, or with a self-hosted server, so that the text stays inside your own infrastructure. The endpoint is a setting.

### How do I turn automatic checking off for a single note?

Add the autocheck key to the note's frontmatter with the value that disables it, which keeps that note out of automatic checking even when the global setting is on, and the opposite value checks it automatically even when the global setting is off. Manual checks are unaffected, and a command sets or removes the key without editing the frontmatter by hand.

### How do I stop the plugin from checking a note in the wrong language?

Pin the language for that note with a frontmatter key holding a LanguageTool code such as a locale or a bare language code. The generic language and short language keys are accepted as well, so a note that already carries one for another plugin needs no extra key, and the value that means automatic re-enables detection for that note even when a static language is set globally.

### What does the Obsidian LanguageTool plugin deliberately not check?

Text in code blocks, mathematical notation and frontmatter is never underlined. In an aliased link only the visible alias is checked and the target is treated as markup, and embeds are skipped entirely. A file tagged with the plugin's ignore tag is excluded from every check, including manual ones.

## Sources

- [Clemens-E/obsidian-languagetool-plugin on GitHub](https://github.com/Clemens-E/obsidian-languagetool-plugin)
- [Issues](https://github.com/Clemens-E/obsidian-languagetool-plugin/issues)
- [License: AGPL-3.0](https://github.com/Clemens-E/obsidian-languagetool-plugin/blob/master/LICENSE)
- [README](https://github.com/Clemens-E/obsidian-languagetool-plugin/blob/master/README.md)
- [Releases](https://github.com/Clemens-E/obsidian-languagetool-plugin/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/clemens-e-obsidian-languagetool-plugin
