How it works
The linter uses markdownlint as its framework. On top of the standard markdownlint rules, GitHub Docs adds custom rules that check Markdown and Liquid syntax specific to this repository. Rules are defined in four layers:- Native markdownlint rules — configured in
src/content-linter/style/base.ts - GitHub markdown linters — from markdownlint-github
- Search-replace checks — simple string/regex patterns in
src/content-linter/style/github-docs.ts - Custom docs rules — individual files in
src/content-linter/lib/linting-rules/
GHD### format (for example, GHD038). Rules from markdownlint use the MD### format (for example, MD011).
Severity levels
Every rule has one of two severities:| Severity | Effect |
|---|---|
error | Prevents the commit from completing. Must be fixed before merging. |
warning | Reported but does not block a commit or merge. Should be fixed when possible. |
Running the linter
Automatically on pre-commit
When you commit staged files from the command line, the linter runs automatically via a pre-commit hook (powered bylint-staged). Both warnings and errors are reported, but only errors prevent the commit from completing.
If errors are reported, fix them, re-add the changed files, and commit again.
When editing files in the GitHub UI, the pre-commit hook does not run. CI will still report any
error-severity violations.Manually on staged and changed files
Run the linter on all files you have staged or changed:error-severity violations (skip warnings):
On specific files or directories
Run the linter on one or more specific paths. Separate multiple paths with a space:Auto-fix errors
Some rules are fixable automatically. When a rule hasfixable: true in its description, you can apply fixes automatically.
Fix staged and changed files:
Run specific rules
To test a particular rule or set of rules, pass their names with--rules. You can use either the short name (MD001) or the long name (heading-increment):
View all options
What the linter checks
The custom rules insrc/content-linter/lib/linting-rules/ cover these categories:
Images
Images
image-alt-text-end-punctuation— Alt text must end with punctuation.image-alt-text-exclude-start-words— Alt text must not start with “Image” or “Graphic.”image-alt-text-length— Alt text must be between 40 and 150 characters.image-file-kebab-case— Image filenames must use kebab-case.image-no-gif— Animated GIFs are not allowed in the docs.
Links
Links
internal-links-no-lang— Internal links must not include a language code prefix like/en/.internal-links-old-version— Internal links must not reference deprecated version paths.internal-links-slash— Internal links must start with a forward slash.link-punctuation— Punctuation must not be included inside link brackets.link-quotation— Links must not be wrapped in quotation marks.
Liquid and versioning
Liquid and versioning
liquid-data-tags— Liquid{% data %}tags must reference valid data paths.liquid-ifversion-versions—{% ifversion %}conditions must use valid version names.liquid-quoted-conditional-arg— Liquid conditional arguments must not be quoted.liquid-syntax— Liquid tags must be syntactically valid.liquid-tag-whitespace— Liquid tags must have proper internal whitespace.liquid-versioning— Liquid versioning blocks must follow correct patterns.table-liquid-versioning— Liquid versioning inside tables must be properly formed.
Frontmatter
Frontmatter
frontmatter-schema— Frontmatter must conform to the required schema.frontmatter-versions-whitespace— Frontmatter version values must not have extraneous whitespace.frontmatter-curly-quotes— Frontmatter must not contain curly quotes.frontmatter-content-type— ThecontentTypefrontmatter field must use a valid content type.frontmatter-hidden-docs— Hidden docs must follow the correct pattern.frontmatter-children— Thechildrenfrontmatter on index pages must be valid.frontmatter-hero-image— Hero images on landing pages must meet requirements.frontmatter-intro-links— Intro text must not contain links.frontmatter-landing-carousels— Landing page carousel data must be valid.frontmatter-video-transcripts— Video transcript frontmatter must be valid.
Code
Code
code-annotation-comment-spacing— Code annotation comments must have correct spacing.code-annotations— Code annotation syntax must be valid.yaml-scheduled-jobs— Scheduled workflow examples must not run on congested times (e.g., on the hour).third-party-action-pinning— Third-party actions must be pinned to a full-length commit SHA.third-party-actions-reusable— Third-party action disclaimers must use the approved reusable.github-owned-action-references— First-party actions must use reusables instead of hardcoded version tags.
Single-source and variables
Single-source and variables
hardcoded-data-variable— Product names must use Liquid variables, not plain text strings.outdated-release-phase-terminology— Release phase terms must use current approved language.early-access-references— Early access content must not appear in public docs.
Journey tracks
Journey tracks
journey-tracks-guide-path-exists— Every guide path referenced injourneyTracksmust exist.journey-tracks-liquid— Journey track titles and descriptions must use valid Liquid syntax.journey-tracks-unique-ids— Journey trackidvalues must be unique within a page.
Responsible AI (RAI)
Responsible AI (RAI)
rai-app-card-structure— RAI app card components must follow the correct structure.rai-reusable-usage— RAI content must use the approved reusables.
Tables
Tables
table-column-integrity— Table columns must have consistent widths and separators.table-liquid-versioning— Liquid versioning inside tables must be properly formed.
Content calls to action (CTAs)
Content calls to action (CTAs)
ctas-schema— CTA frontmatter must conform to the required schema.
Expiring content
Expiring content
expired-content— Warns 14 days before an expiration date and flags content that has passed its expiration date.
Tagging content with an expiration date
When you must include content that will become outdated, wrap it in expiration HTML comments. The linter will warn you 14 days before the date and flag it for remediation on or after the date.Suppressing rules
Occasionally you may need to document something that intentionally violates a lint rule. Suppress only the specific rules needed, not all rules.| Comment | Effect |
|---|---|
<!-- markdownlint-disable-line MD011 --> | Disable specific rule for the current line |
<!-- markdownlint-disable-next-line MD011 --> | Disable specific rule for the next line |
<!-- markdownlint-disable MD011 --> | Disable rule from this point |
<!-- markdownlint-enable MD011 --> | Re-enable the rule |
<!-- markdownlint-disable --> | Disable all rules from this point |
<!-- markdownlint-enable --> | Re-enable all rules |
The
search-replace plugin does not support disabling individual rules. To suppress a search-replace violation, disable the entire plugin with <!-- markdownlint-disable-line search-replace -->.Bypassing the pre-commit hook
If the linter catches errors that you did not introduce (for example, in a file you only partially modified), you can bypass the git pre-commit hook with--no-verify:
Custom rule locations
| Location | Purpose |
|---|---|
src/content-linter/lib/linting-rules/ | Individual TypeScript files for each custom rule |
src/content-linter/lib/linting-rules/index.ts | Imports and exports all custom rules |
src/content-linter/style/github-docs.ts | Configures all rules, sets severities |
src/content-linter/style/base.ts | Configures native markdownlint rules |
src/content-linter/tests/unit/ | Unit tests for custom rules |
GHD03X range are candidates for upstreaming to the markdownlint community.