How to Customize Automattic/harper: Rules, Configuration, and Dictionaries
You can customize Automattic/harper by editing harper-core/default_config.json to toggle linting rules, supplying custom configuration files via the --config CLI flag or harper.lintConfigPath setting, adding personal dictionaries, or authoring new rules in Rust using the ExprLinter framework.
Harper is a modular grammar-checking system built around a core engine (harper-core) with multiple front-ends including CLI tools, language servers, and desktop applications. You can customize Automattic/harper to match specific writing standards or project requirements by modifying JSON configurations, creating custom rule sets, or extending the engine with new linting logic written in Rust.
Edit the Default Lint Configuration
Harper’s default rule set lives in harper-core/default_config.json. This file contains the master list of all available linting rules, where each entry is a Bool toggle that enables or disables a specific check.
To change a rule globally, edit the JSON file and re-compile the relevant crate. Each rule entry follows this structure:
{
"Bool": {
"name": "OxfordComma",
"state": false,
"label": "Oxford Comma"
}
}
Setting "state": false disables the rule across all Harper instances that use this configuration file.
Apply Custom Configurations at Runtime
Instead of rebuilding the repository, you can point the CLI or language server at a custom JSON configuration file at runtime.
Using the CLI:
harper-cli lint myfile.txt --config path/to/my_config.json
Using harper-ls (VS Code, Neovim, etc.):
Set the lintConfigPath in your editor configuration:
{
"harper.lintConfigPath": "/abs/path/to/my_config.json"
}
The CLI reads the --config flag directly, while harper-ls processes the lintConfigPath entry through its configuration handler in harper-ls/src/config.rs.
Author Custom Linting Rules
Harper supports two distinct styles for creating new rules:
- Weir rules – Declarative rules written in
.weirfiles underharper-core/src/linting/weir_rules. - ExprLinter (Rust) rules – Imperative rules compiled as Rust code using the
ExprLintertrait.
To add a Rust-based rule:
- Copy a skeleton from
harper-core/expr_linter_skeleton.rs(or the commented version) intoharper-core/src/linting/my_rule.rs. - Rename the struct (e.g.,
ExprLinterSkeleton→MyRule). - Edit the
default()method to define theExprpattern you want to match. - Implement
match_to_lintto return aLintobject when the pattern is found. - Add the rule name to
default_config.jsonto enable it by default.
Here is a complete example that detects double spaces:
// src/linting/double_space.rs
use harper_core::{Expr, ExprLinter, Lint, Token};
pub struct DoubleSpace {
expr: Box<dyn Expr>,
}
impl Default for DoubleSpace {
fn default() -> Self {
// The expression matches two consecutive spaces
let expr = harper_core::expr::seq(vec![
harper_core::expr::literal(" "),
harper_core::expr::literal(" "),
]);
Self { expr: Box::new(expr) }
}
}
impl ExprLinter for DoubleSpace {
fn expr(&self) -> &dyn Expr { self.expr.as_ref() }
fn match_to_lint(&self, _matched: &[Token], _source: &[char]) -> Option<Lint> {
Some(Lint::new("Avoid double spaces").with_fix("Replace with a single space"))
}
fn description(&self) -> &'static str {
"Detects two consecutive space characters"
}
}
After adding the rule to default_config.json, run cargo test inside harper-core to verify the implementation. See the full authoring guide at packages/web/src/routes/docs/contributors/author-a-rule/+page.md.
Manage Personal Dictionaries
Harper uses a merged dictionary system that combines the curated wordlist with user-provided entries. You can extend the dictionary in several ways:
- Desktop application: Add words through the UI, which persists them in
harper-desktop/src-tauri/src/config.rsviaMutableDictionary::new(). - CLI usage: Create a plain-text file (
dictionary.txt) and pass it using the--dictionaryflag. - JavaScript API: Use the harper-js client to add words at runtime:
import { Client } from "harper-js";
await Client.addToDictionary("Bardot");
This call updates the persistent dictionary used by both the CLI and the desktop overlay.
Configure Editor Integrations
All editor integrations (VS Code, Neovim, Helix, Emacs, Zed, Sublime) ultimately use harper-ls. The language server reads configuration from your editor's settings file under the "Harper" section.
Key settings include:
harper.lintConfigPath– Absolute path to a customdefault_config.jsonfile.harper.dictionaryPath– Path to a personal dictionary file.harper.enabledRules– Array of rule names to enable, overriding the JSON configuration.
VS Code example (settings.json):
{
"harper.lintConfigPath": "/home/user/.config/harper/my_config.json",
"harper.dictionaryPath": "/home/user/.config/harper/dictionary.txt"
}
After saving, reload the window (Developer: Reload Window) to apply changes. See the VS Code integration documentation at packages/web/src/routes/docs/integrations/visual-studio-code/+page.md for specific syntax details.
Customize the Desktop Highlighter
The desktop highlighter runs as a separate process and persists its configuration in a config.json file inside the system configuration directory. You can modify settings such as:
mutable_dictionary– User-added words specific to the desktop app.lint_config– Rule set used by the overlay highlighter.
The application reads this configuration via Config::load_from_system() as implemented in harper-desktop/src-tauri/src/config.rs.
Summary
- Rule customization happens in
harper-core/default_config.json, where each lint rule is a boolean toggle that can be enabled or disabled. - Runtime configuration allows you to specify custom JSON files via
--config(CLI) orharper.lintConfigPath(editors) without recompiling. - Personal dictionaries can be added via text files, the desktop UI, or the JavaScript API using
Client.addToDictionary(). - Custom rules are authored in Rust using the
ExprLintertrait or declaratively using Weir files. - Editor settings are controlled through
harper-lsconfiguration options specific to each IDE.
Frequently Asked Questions
How do I disable a specific rule in Harper?
You can disable a rule by setting its "state" to false in either the global harper-core/default_config.json file or in a custom configuration file that you pass via the --config CLI flag or the harper.lintConfigPath editor setting. Each rule is represented as a Bool object with a name field corresponding to the rule identifier.
Can I add custom words to Harper's dictionary?
Yes. Harper supports personal dictionaries through multiple interfaces. For CLI usage, pass a text file via --dictionary. For the desktop application, add words through the UI which updates MutableDictionary in harper-desktop/src-tauri/src/config.rs. For JavaScript environments, use Client.addToDictionary("word") from the harper-js package.
What is the difference between Weir rules and ExprLinter rules?
Weir rules are declarative patterns written in .weir files that define linting logic without compiling Rust code. ExprLinter rules are imperative Rust implementations that use the ExprLinter trait to define complex matching logic and custom Lint generation. Weir rules are suitable for simple pattern matching, while ExprLinter rules offer full programmatic control for sophisticated checks.
How do I configure Harper for VS Code?
Configure VS Code through the settings.json file using Harper-specific keys under the harper namespace. Set harper.lintConfigPath to point to your custom rule configuration and harper.dictionaryPath to your personal word list. These settings are processed by harper-ls, which powers the VS Code extension.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →