# How to Customize Automattic/harper: Rules, Configuration, and Dictionaries

> Customize Automattic harper by editing the default config file, adding custom dictionaries, or authoring new rules in Rust. Learn how to tailor harper to your project.

- Repository: [Automattic/harper](https://github.com/Automattic/harper)
- Tags: how-to-guide
- Published: 2026-07-27

---

**You can customize Automattic/harper by editing [`harper-core/default_config.json`](https://github.com/Automattic/harper/blob/main/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`](https://github.com/Automattic/harper/blob/main/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:

```json
{
  "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:**

```bash
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:

```json
{
  "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`](https://github.com/Automattic/harper/blob/main/harper-ls/src/config.rs)**.

## Author Custom Linting Rules

Harper supports two distinct styles for creating new rules:

- **Weir rules** – Declarative rules written in `.weir` files under `harper-core/src/linting/weir_rules`.
- **ExprLinter (Rust) rules** – Imperative rules compiled as Rust code using the `ExprLinter` trait.

To add a Rust-based rule:

1. Copy a skeleton from **[`harper-core/expr_linter_skeleton.rs`](https://github.com/Automattic/harper/blob/main/harper-core/expr_linter_skeleton.rs)** (or the commented version) into [`harper-core/src/linting/my_rule.rs`](https://github.com/Automattic/harper/blob/main/harper-core/src/linting/my_rule.rs).
2. Rename the struct (e.g., `ExprLinterSkeleton` → `MyRule`).
3. Edit the `default()` method to define the `Expr` pattern you want to match.
4. Implement `match_to_lint` to return a `Lint` object when the pattern is found.
5. Add the rule name to [`default_config.json`](https://github.com/Automattic/harper/blob/main/default_config.json) to enable it by default.

Here is a complete example that detects double spaces:

```rust
// 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`](https://github.com/Automattic/harper/blob/main/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.rs`](https://github.com/Automattic/harper/blob/main/harper-desktop/src-tauri/src/config.rs) via `MutableDictionary::new()`.
- **CLI usage:** Create a plain-text file ([`dictionary.txt`](https://github.com/Automattic/harper/blob/main/dictionary.txt)) and pass it using the `--dictionary` flag.
- **JavaScript API:** Use the harper-js client to add words at runtime:

```javascript
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 custom [`default_config.json`](https://github.com/Automattic/harper/blob/main/default_config.json) file.
- **`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`](https://github.com/Automattic/harper/blob/main/settings.json)):**

```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`](https://github.com/Automattic/harper/blob/main/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`](https://github.com/Automattic/harper/blob/main/harper-desktop/src-tauri/src/config.rs)**.

## Summary

- **Rule customization** happens in [`harper-core/default_config.json`](https://github.com/Automattic/harper/blob/main/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) or `harper.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 `ExprLinter` trait or declaratively using Weir files.
- **Editor settings** are controlled through `harper-ls` configuration 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`](https://github.com/Automattic/harper/blob/main/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`](https://github.com/Automattic/harper/blob/main/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`](https://github.com/Automattic/harper/blob/main/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.