Skip to main content
Vocabularies let you define project-specific terminology that Vale should accept or reject. They provide a centralized way to maintain custom dictionaries without modifying style rules.

What is a vocabulary?

A vocabulary is a directory containing two text files:
  • accept.txt: Terms that are always correct
  • reject.txt: Terms that should never be used
Vale automatically integrates vocabulary terms with its spelling, substitution, and existence checks.

Creating a vocabulary

Create a directory structure under your StylesPath:
styles/
└── config/
    └── vocabularies/
        └── MyProject/
            ├── accept.txt
            └── reject.txt

accept.txt format

List terms to accept, one per line:
# Technical terms
API
SDK
OAuth
WebSocket

# Product names
MyProduct
FeatureX

# Regex patterns (start with lowercase letter for pattern detection)
(?i)GitHub
[Oo]bservability
[pP]y.*\b

# Comments are ignored
  • Plain text: Exact matches (e.g., GitHub)
  • Regex patterns: Any valid regular expression
  • Case-insensitive: Use (?i) flag (e.g., (?i)GitHub matches “github”, “GITHUB”, “GitHub”)
  • Comments: Lines starting with # are ignored
  • Blank lines: Ignored
From Vale source (testdata/fixtures/vocab/styles/config/vocabularies/Basic/accept.txt):
(?i)GitHub
[Oo]bservability
[pP]y.*\b
# This is a comment
ABC-DEF
ABCDEF
definately
Documentarians
Log4j
PLuG

reject.txt format

List terms to reject, one per line:
# Avoid these phrases
mac OS X

# Outdated terms
master/slave
whitelist/blacklist
From Vale source (testdata/fixtures/vocab/styles/config/vocabularies/Basic/reject.txt):
Mac OS X
Keep reject.txt simple. For sophisticated term replacement, create a substitution rule in your style instead.

Activating a vocabulary

Activate vocabularies in your .vale.ini file:
StylesPath = styles
Vocab = MyProject

[*.md]
BasedOnStyles = Vale
Vale loads terms from styles/config/vocabularies/MyProject/.

Multiple vocabularies

Combine multiple vocabularies:
Vocab = Base, Marketing, Engineering
Vale merges terms from all specified vocabularies. If the same term appears in multiple vocabularies with different statuses (accept vs. reject), the last vocabulary wins.

How vocabularies integrate with rules

Vocabularies automatically affect three built-in Vale rules:
Terms in accept.txt are added to the spelling dictionary.
# Built-in rule (Vale.Spelling)
extends: spelling
message: "Did you really mean '%s'?"
level: error
If your accept.txt contains:
OAuth
WebSocket
gRPC
Vale won’t flag these as misspellings.

Using vocabularies in custom rules

Custom rules can access vocabulary terms via the vocab option:
extends: existence
message: "Avoid using '%s'"
level: warning
vocab: true  # Use vocabulary as exceptions
tokens:
  - regex
  - really
  - just
With vocab: true, terms in your accept.txt are automatically added to the rule’s exceptions list. From internal/check/definition.go:42, most rules default to Vocab: true:
func NewExistence(cfg *core.Config, generic baseCheck, path string) (Existence, error) {
    rule := Existence{Vocab: true}  // Default behavior
    // ...
    re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab)
}
Set vocab: false to prevent vocabulary terms from affecting a rule:
extends: existence
message: "No abbreviations allowed"
vocab: false  # Ignore vocabulary
tokens:
  - API
  - SDK
Now the rule flags “API” and “SDK” even if they’re in accept.txt.

Vocabulary loading

Vale loads vocabularies during initialization:
1

Parse configuration

Vale reads the Vocab key from .vale.ini and identifies which vocabularies to load.
2

Locate directories

For each vocabulary name, Vale searches for config/vocabularies/{name}/ under each StylesPath.
3

Read files

Vale reads accept.txt and reject.txt from each vocabulary directory.From internal/core/ini.go:29-58:
func loadVocab(root string, cfg *Config) error {
    target := ""
    for _, p := range cfg.SearchPaths() {
        opt := filepath.Join(p, VocabDir, root)
        if system.IsDir(opt) {
            target = opt
            break
        }
    }

    err := system.Walk(target, func(fp string, info fs.FileInfo, err error) error {
        name := info.Name()
        if name == "accept.txt" {
            return cfg.AddWordListFile(fp, true)
        } else if name == "reject.txt" {
            return cfg.AddWordListFile(fp, false)
        }
        return nil
    })
}
4

Merge and compile

Vale merges terms from all vocabularies and compiles them into regular expressions for efficient matching.From internal/check/definition.go:341-373:
func updateExceptions(previous []string, current []string, vocab bool) (*regexp2.Regexp, error) {
    if vocab {
        previous = append(previous, current...)
    }

    // Sort by length for greedy alternation
    sort.Slice(previous, func(p, q int) bool {
        return len(previous[p]) > len(previous[q])
    })

    // Add (?-i) to terms without (?i) flag
    for i, term := range previous {
        if !strings.HasPrefix(term, "(?i)") {
            previous[i] = fmt.Sprintf("(?-i)%s", term)
        }
    }

    regex := fmt.Sprintf(regex, strings.Join(previous, "|"))
    return regexp2.CompileStd(regex)
}

Examples

styles/config/vocabularies/MyApp/
├── accept.txt
└── reject.txt
accept.txt:
# API terms
REST
GraphQL
WebSocket
OAuth2
JWT

# Product names
MyApp
FeatureX
ModuleY

# Technical abbreviations
(?i)API
SDK
CLI
reject.txt:
# Outdated terms
REST API endpoint
master branch
slave server
Configuration:
StylesPath = styles
Vocab = MyApp

[*.md]
BasedOnStyles = Vale, Microsoft

Best practices

Separate vocabularies by domain:
  • Base: Common terms across all content
  • API: API-specific terminology
  • Legal: Legal and compliance terms
  • Marketing: Brand and marketing language
This makes vocabularies reusable across projects and easier to maintain.
Regex patterns are powerful but can slow down Vale’s performance:
# Good: specific pattern
(?i)GitHub

# Avoid: overly broad pattern
.*[Tt]ech.*
Use plain text entries when possible.
Add comments explaining why terms are included:
# Product names (trademarked)
MyProduct
FeatureX

# Industry-standard abbreviations
API
SDK

# Internal codenames (temporary - review quarterly)
ProjectPhoenix
Use reject.txt for terms that are universally wrong in your context. For nuanced replacements, create substitution rules:
# Better as a rule:
extends: substitution
message: "Use '%s' instead of '%s'"
swap:
  utilize: use
  leverage: use
Commit vocabularies to version control alongside your styles:
git add styles/config/vocabularies/
git commit -m "Add OAuth to accepted terms"
This keeps terminology in sync with your content.

Troubleshooting

Check the directory structure:
vale ls-config
Look for “AcceptedTokens” and “RejectedTokens” in the output. If empty, Vale isn’t finding your vocabulary.Common issues:
  • Wrong directory name (must match Vocab value in config)
  • Missing config/vocabularies/ intermediate directories
  • Files named incorrectly (must be accept.txt and reject.txt)
If Vale still flags terms in accept.txt:
  1. Check the rule: Some rules set vocab: false
  2. Verify the term format: Ensure proper escaping for special characters
  3. Check rule scope: The rule might not apply to your content’s scope
  4. Reload Vale: Restart your editor or Vale server after changing vocabulary files
Test your regex pattern:
# Create a test file
echo "GitHub" > test.txt

# Add a test rule
cat > styles/Test/Pattern.yml <<EOF
extends: existence
message: "Found '%s'"
tokens:
  - '(?i)GitHub'
EOF

# Test
vale test.txt
If the pattern works in the rule but not in the vocabulary, check for special characters that need escaping.

Advanced usage

Use different vocabularies for different file types:
StylesPath = styles

[docs/**/*.md]
Vocab = Base, Technical

[blog/**/*.md]
Vocab = Base, Marketing

[legal/**/*.md]
Vocab = Base, Legal
Use a shared styles directory:
# Project A
StylesPath = /shared/vale-styles
Vocab = CompanyWide, ProjectA

# Project B
StylesPath = /shared/vale-styles
Vocab = CompanyWide, ProjectB
Both projects share CompanyWide vocabulary while maintaining project-specific terms.
Implementation reference: internal/core/ini.go:29-58, internal/core/config.go:324-346, internal/check/definition.go:341-373

Build docs developers (and LLMs) love