# How the GitHub Docs Content Linter Validates Documentation Quality

> Discover how the GitHub Docs content linter validates documentation quality through a seven-stage pipeline using markdownlint, custom rules, and auto-fixing.

- Repository: [GitHub/docs](https://github.com/github/docs)
- Tags: internals
- Published: 2026-06-01

---

**The GitHub Docs content linter is an opinionated wrapper around markdownlint that validates Markdown files in the `content/` and `data/` directories (plus markdown strings inside YAML) through a seven-stage pipeline involving file discovery, custom rule application, severity-based reporting, and optional auto-fixing.**

The content linter ensures every page in the GitHub Docs repository meets strict editorial and technical standards before publication. Operating as a specialized validation layer atop markdownlint, it executes a curated blend of native rules and custom GitHub-specific checks against documentation source files, categorizing findings by severity and providing actionable fixes.

## The Validation Pipeline Architecture

The linter orchestrates validation through a precise sequence of operations defined in [`src/content-linter/scripts/lint-content.ts`](https://github.com/github/docs/blob/main/src/content-linter/scripts/lint-content.ts). Each stage handles specific aspects of documentation quality assurance.

### 1. File Discovery and Categorization

The process begins in `getFilesToLint` (lines 80-108 of [`lint-content.ts`](https://github.com/github/docs/blob/main/lint-content.ts)), which traverses the repository and resolves caller-supplied paths (defaulting to changed files). The implementation separates targets into three distinct groups:

- **content** – Standard Markdown pages requiring full validation
- **data** – Partial templates stored under `data/` with specialized rules  
- **yml** – Markdown strings extracted from lintable YAML files via [`get-lintable-yml.ts`](https://github.com/github/docs/blob/main/get-lintable-yml.ts)

This categorization enables context-specific rule application, ensuring data partials and YAML front matter receive appropriate scrutiny without false positives.

### 2. Rule Configuration and Severity Levels

All rule metadata resides in [`src/content-linter/style/github-docs.ts`](https://github.com/github/docs/blob/main/src/content-linter/style/github-docs.ts). The configuration architecture uses three distinct objects:

- **`githubDocsConfig`** – Primary custom rules for content validation
- **`githubDocsFrontmatterConfig`** – Rules requiring only front-matter line numbers  
- **`searchReplaceConfig`** – Simple string and regex pattern checks

Each rule specifies a default severity (`error` or `warning`) and an optional `precommitSeverity` that overrides the default during pre-commit hooks, allowing teams to block commits on critical violations while permitting warnings in CI.

### 3. Custom Rule Registration

Custom rules live as separate TypeScript modules in `src/content-linter/lib/linting-rules/`, such as [`liquid-data-tags.ts`](https://github.com/github/docs/blob/main/liquid-data-tags.ts) which validates Liquid template syntax. The index file at [`src/content-linter/lib/linting-rules/index.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/linting-rules/index.ts) aggregates all rule modules into a `rules` array imported by the main configuration.

This modular architecture allows developers to implement domain-specific validation logic—such as checking for internal link consistency or validating reusable content tags—while maintaining clean separation of concerns.

### 4. Multi-Pass Linting Execution

[`lint-content.ts`](https://github.com/github/docs/blob/main/lint-content.ts) invokes markdownlint four times sequentially (lines 71-89), once per file group, with specialized configurations:

1. **Content rules** – Full validation for standard documentation pages
2. **Data-partial rules** – Checks specific to reusable content fragments
3. **Front-matter-only rules** – Metadata validation using line number constraints
4. **YML-string rules** – Markdown content extracted from YAML structures

The raw results from each pass merge into a single `results` object for unified processing, ensuring comprehensive coverage across all documentation sources.

### 5. Severity Handling and Result Formatting

After markdownlint execution, the pipeline enriches each violation with contextual metadata. The `formatResult` helper constructs a `FormattedResult` structure containing:

- The rule's configured severity level
- Contextual information about the violation  
- A "fixable" flag indicating presence of `fixInfo` objects

This enrichment enables precise CLI output, GitHub Actions annotations via [`print-annotation-results.ts`](https://github.com/github/docs/blob/main/print-annotation-results.ts), and JSON report generation for downstream tooling.

### 6. Auto-Fix and Reporting

When invoked with the `--fix` flag, the linter calls `applyFixes` from `markdownlint-rule-helpers` (lines 31-44 of [`lint-content.ts`](https://github.com/github/docs/blob/main/lint-content.ts)) to rewrite files containing automatically correctable violations.

The reporting layer utilizes [`pretty-print-results.ts`](https://github.com/github/docs/blob/main/pretty-print-results.ts) to generate colorized CLI summaries. During pre-commit execution, the process aborts if any `error`-level violations remain, while CI pipelines receive structured annotations for inline PR feedback.

## Working with the Content Linter

### Running the Linter via CLI

The command-line interface supports targeted validation and automatic correction:

```bash

# Lint all changed files (default behavior)

npm run lint-content

# Lint specific files with automatic fixing

npm run lint-content -- --paths content/github/using-actions.md --fix

```

CLI options are defined in the `program` setup within [`lint-content.ts`](https://github.com/github/docs/blob/main/lint-content.ts) (lines 95-125), supporting path specifications, fix modes, and output format selection.

### Creating Custom Rules

New rules follow a standard interface exposing metadata and validation logic. Here is a template enforcing no trailing whitespace:

```typescript
// src/content-linter/lib/linting-rules/no-trailing-whitespace.ts
import { addError } from 'markdownlint-rule-helpers'

export const noTrailingWhitespace = {
  names: ['GHD999', 'no-trailing-whitespace'],
  description: 'Lines must not end with trailing whitespace',
  tags: ['format'],
  parser: 'markdownit',
  function: (params, onError) => {
    params.lines.forEach((line, index) => {
      if (/\s+$/.test(line)) {
        addError(onError, index + 1, 'Trailing whitespace detected', line, [line.length - 1, 1])
      }
    })
  },
}

```

After implementation, register the rule in [`src/content-linter/lib/linting-rules/index.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/linting-rules/index.ts) and add a severity configuration entry in [`src/content-linter/style/github-docs.ts`](https://github.com/github/docs/blob/main/src/content-linter/style/github-docs.ts).

### Programmatic Integration

The linter exposes its configuration and execution logic for integration with external tools:

```typescript
import { markdownlint } from 'markdownlint'
import { getMarkdownLintConfig } from '@/content-linter/scripts/lint-content'

const files = ['content/github/markdown.md']
const { config, configuredRules } = getMarkdownLintConfig(false, undefined)

const results = await markdownlint.promises.markdownlint({
  files,
  config: config.content,
  customRules: configuredRules.content,
})
console.log(results)

```

The `getMarkdownLintConfig` function (lines 135-147 of [`lint-content.ts`](https://github.com/github/docs/blob/main/lint-content.ts)) assembles merged configurations appropriate for each file category, enabling consistent validation outside the standard CLI workflow.

## Key Source Files

Understanding the codebase requires familiarity with these specific modules:

| Purpose | File Path |
|---|---|
| High-level overview and contribution guide | [`src/content-linter/README.md`](https://github.com/github/docs/blob/main/src/content-linter/README.md) |
| CLI entry point and orchestration logic | [`src/content-linter/scripts/lint-content.ts`](https://github.com/github/docs/blob/main/src/content-linter/scripts/lint-content.ts) |
| Rule registration and severity configuration | [`src/content-linter/style/github-docs.ts`](https://github.com/github/docs/blob/main/src/content-linter/style/github-docs.ts) |
| Custom rule implementation example (Liquid validation) | [`src/content-linter/lib/linting-rules/liquid-data-tags.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/linting-rules/liquid-data-tags.ts) |
| YAML markdown extraction utilities | [`src/content-linter/lib/helpers/get-lintable-yml.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/helpers/get-lintable-yml.ts) |
| Results formatting and display logic | [`src/content-linter/scripts/pretty-print-results.ts`](https://github.com/github/docs/blob/main/src/content-linter/scripts/pretty-print-results.ts) |
| Default markdownlint engine options | [`src/content-linter/lib/default-markdownlint-options.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/default-markdownlint-options.ts) |
| Unit test template for custom rules | [`src/content-linter/tests/unit/liquid-data-tags.ts`](https://github.com/github/docs/blob/main/src/content-linter/tests/unit/liquid-data-tags.ts) |

## Summary

- The **content linter** wraps markdownlint to enforce GitHub-specific documentation standards across the `content/` and `data/` directories plus YAML files.
- Validation occurs through **four specialized passes**: content rules, data-partial rules, front-matter-only rules, and YML-string rules.
- **Severity configuration** in [`github-docs.ts`](https://github.com/github/docs/blob/main/github-docs.ts) supports distinct levels for CI and pre-commit contexts, enabling `error` blocking and `warning` tolerance strategies.
- **Custom rules** reside in `src/content-linter/lib/linting-rules/` and integrate via a centralized index, supporting domain-specific validation like Liquid tag verification.
- The **auto-fix capability** leverages `markdownlint-rule-helpers` to correct violations automatically when invoked with the `--fix` flag.

## Frequently Asked Questions

### What is the difference between error and warning severity in the content linter?

**Errors** block commits during pre-commit hooks and fail CI builds, while **warnings** alert contributors without stopping the workflow. Each rule in [`src/content-linter/style/github-docs.ts`](https://github.com/github/docs/blob/main/src/content-linter/style/github-docs.ts) defines both a default severity and an optional `precommitSeverity`, allowing teams to enforce stricter standards at commit time than in continuous integration.

### How does the linter handle markdown content inside YAML files?

The **YML file group** uses [`get-lintable-yml.ts`](https://github.com/github/docs/blob/main/get-lintable-yml.ts) to extract markdown strings from YAML structures before running them through markdownlint. This occurs as a separate execution pass with distinct rules from standard content files, ensuring front matter and data files receive appropriate validation without syntax conflicts.

### Can I run the content linter on only specific files during development?

Yes. The CLI accepts a `--paths` argument to target specific files or directories rather than scanning all changed files. For example: `npm run lint-content -- --paths content/actions/index.md data/reusables/actions/*.md`. This targeting accelerates local validation cycles when working on isolated documentation sections.

### Where should I add a new validation rule for GitHub Docs content?

Create the rule implementation in [`src/content-linter/lib/linting-rules/your-rule-name.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/linting-rules/your-rule-name.ts), following the template established by existing rules like [`liquid-data-tags.ts`](https://github.com/github/docs/blob/main/liquid-data-tags.ts). Export the rule from [`src/content-linter/lib/linting-rules/index.ts`](https://github.com/github/docs/blob/main/src/content-linter/lib/linting-rules/index.ts), then register its severity and metadata in [`src/content-linter/style/github-docs.ts`](https://github.com/github/docs/blob/main/src/content-linter/style/github-docs.ts). Include unit tests in `src/content-linter/tests/unit/` to verify behavior against edge cases.