Skip to main content

Overview

Vale’s filter system allows you to dynamically select and modify which rules run using powerful expression syntax. Instead of editing .vale.ini for every rule change, you can:
  • Filter rules by style, severity, or scope
  • Override rule levels on the fly
  • Apply different rules for different scenarios
  • Create reusable filter profiles
Filters use the --filter flag and are evaluated at runtime.

Basic Usage

Filter rules directly from the command line:
vale --filter ".Level == 'error'" docs/
This runs only rules with severity level error.

Filter Options

The --filter flag accepts three input types:
Pass the filter expression directly:
vale --filter ".Level == 'error'" file.md
Good for quick, one-off filtering.

Filter Syntax

Filters use the expr expression language with access to rule properties.

Available Fields

Each rule exposes these fields (see internal/check/definition.go:32-44):
type Definition struct {
    Action      core.Action
    Description string
    Extends     string
    Level       string    // "suggestion", "warning", "error"
    Limit       int
    Link        string
    Message     string
    Name        string    // e.g., "Vale.Spelling"
    Scope       []string  // e.g., ["text", "heading"]
    Selector    Selector
}
Filters operate on the Definition structure, which represents rule metadata.

Basic Expressions

Show only errors:
vale --filter ".Level == 'error'" file.md
Show warnings and errors:
vale --filter ".Level in ['warning', 'error']" file.md
Exclude suggestions:
vale --filter ".Level != 'suggestion'" file.md

How Filters Work

Vale’s filter implementation (see internal/check/filter.go:14-112):
Vale reads the filter from one of three sources:
func filter(mgr *Manager) (map[string]Rule, error) {
    var filter string
    stringOrPath := mgr.Config.Flags.Filter
    
    if system.FileExists(stringOrPath) {
        // Case 1: Valid file path
        b, err := os.ReadFile(stringOrPath)
        filter = string(b)
    } else if found := core.FindAsset(mgr.Config, stringOrPath); found != "" {
        // Case 2: StylesPath reference
        b, err := os.ReadFile(found)
        filter = string(b)
    } else {
        // Case 3: Inline expression
        filter = stringOrPath
    }
    return mgr.rules, nil
}
All loaded rules are exposed to the filter:
env := FilterEnv{}
for _, rule := range mgr.rules {
    env.Rules = append(env.Rules, rule.Fields())
}
The environment contains a Rules array with all rule definitions.
The filter expression is compiled and executed:
code := fmt.Sprintf(`filter(Rules, {%s})`, filter)

program, err := expr.Compile(code, expr.Env(env))
if err != nil {
    return mgr.rules, err
}

output, err := expr.Run(program, env)
The filter() function selects matching rules.
Vale adjusts the minimum alert level if needed:
if strings.Contains(code, ".Level") {
    lvl := core.LevelToInt[rule.Level]
    if lvl < mgr.Config.MinAlertLevel {
        mgr.Config.MinAlertLevel = lvl
    }
}
This ensures filtered rules can actually run.

Filter Pipeline

The order of evaluation is always:
.vale.ini → --filter → final rules
The filter always has the final say. This means:
Filtered results can only be a subset of what .vale.ini would normally load. Filters can’t enable rules that weren’t loaded from your configuration.

Advanced Expressions

Combine multiple criteria:
vale --filter ".Level == 'error' && startsWith(.Name, 'Vale.')" file.md
Use parentheses for clarity:
vale --filter "(.Level == 'error' || .Level == 'warning') && 'heading' in .Scope" file.md

Reusable Filter Files

Store common filters for your team:
# StylesPath structure
StylesPath/
├── config/
   └── filters/
       ├── strict
       ├── quick-check
       └── docs-only
└── ...

Example Filters

.Level in ['warning', 'error']
Usage:
vale --filter strict docs/

CI/CD Integration

Use filters to implement progressive checks:
# .github/workflows/vale.yml
name: Vale
on: [pull_request]

jobs:
  # Quick check for blockers
  errors:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: vale --filter ".Level == 'error'" docs/
  
  # Full check, but only warn
  warnings:
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - uses: actions/checkout@v3
      - run: vale docs/
Use strict filters for PR checks and relaxed filters during development.

Debugging Filters

View Configuration

See which rules would run:
vale --filter ".Level == 'error'" ls-config
Check the Checks section in the output.

Test Expressions

Create a test filter file and iterate:
echo ".Level == 'error'" > test-filter.txt
vale --filter test-filter.txt file.md
Modify test-filter.txt until you get the desired results.

Common Filter Patterns

vale --filter "startsWith(.Name, 'MyCompany.')" docs/
vale --filter "!startsWith(.Name, 'Vale.Spelling')" docs/
Useful for quick iteration.
Start with errors only:
vale --filter ".Level == 'error'" docs/
Then add warnings:
vale --filter ".Level in ['error', 'warning']" docs/
Finally, everything:
vale docs/
Different filters for different doc types:
# API docs: strict technical rules
vale --filter api-docs api/

# Guides: focus on readability
vale --filter readability-focus guides/

Limitations

Filters cannot:
  • Enable rules not loaded by .vale.ini
  • Modify rule patterns or logic
  • Access file content or paths
  • Create new rules dynamically
Filters operate only on rule metadata, not the rules themselves.

Best Practices

Start Simple

Begin with basic level filters before complex expressions.

Store Team Filters

Keep common filters in config/filters/ for everyone to use.

Document Filters

Add comments explaining what each filter does:
# Strict mode: only errors from core styles
.Level == 'error' && startsWith(.Name, 'Vale.')

Test Changes

Always test filter expressions on sample files before using in CI.

Expression Reference

Common expr language features:
OperatorDescriptionExample
==, !=Equality.Level == 'error'
inMembership'text' in .Scope
&&, ``Logic`.Level == ‘error’.Level == ‘warning’`
!Negation!startsWith(.Name, 'Vale.')
startsWith()Prefix matchstartsWith(.Name, 'Google.')
matches()Regex matchmatches(.Name, '^Vale\\.')
See the expr documentation for complete syntax reference.

Build docs developers (and LLMs) love