Docs as code is a way of writing documentation with the same tools and workflow as software. The docs are plain-text files, usually Markdown, kept in Git next to the code. Changes go through pull requests and review, automated checks test them, and a build publishes them as a website. The Write the Docs community describes it as writing documentation “with the same tools as code” and names five: issue trackers, version control, plain-text markup, code reviews and automated tests.
A developer renames a setting from timeout to timeout_seconds. With docs as code, the reference can change in the same pull request, so the old name doesn't make it into the next release. That change is followed below from branch to published page, then come the tools, an eight-step setup and the cases where the workflow is more than a team needs.
One change, start to finish
The project is acme-cli, an invented command-line tool. Its docs are Markdown files in a docs folder inside the same Git repository as its code. Git is the version control system that records every change to every file, with who made it and a message saying what it was for. Here is how the rename travels:
- A branch. The developer works on a branch, a separate line of changes in Git, and edits the code, the configuration reference and the changelog together. She also adds an upgrade page.
- A pull request. She opens pull request #482, a request to merge the branch into the main line. It shows every changed line, code and docs side by side, and collects the review comments.
- Reviewers by path. A
CODEOWNERSfile, which maps folders and files to the people responsible for them, asks the CLI maintainers and the docs team for a review without anyone choosing them. - Automated checks. A continuous integration workflow, here GitHub Actions, runs on the pull request. markdownlint checks the Markdown formatting, Vale applies the team's style rules to the prose, lychee checks every link, and MkDocs, the static site generator that turns the docs folder into a website, builds the site in strict mode, which fails on any warning.
- Merge and release. With the checks green and both teams' approval, the pull request merges. The release is tagged 2.5, and the published site picks up the new pages.
Here is the pull request description that carried it:
What changes. The timeout option becomes timeout_seconds in version 2.5. The old name keeps working until 3.0 and logs a deprecation warning.
Code. src/config.py, tests/test_config.py.
Docs. docs/reference/configuration.md (option renamed, new example), docs/how-to/upgrade-to-2.5.md (new page), CHANGELOG.md (Deprecated section).
Checks. markdownlint: passed. Vale: one error, “e-mail” in configuration.md, fixed in the second commit. lychee: one broken link, a typo in the upgrade page's link to the configuration reference, fixed. Build: passed in strict mode.
Review. Approved by the CLI maintainers and the docs team. The docs reviewer asked for the 3.0 deadline in the first paragraph of the upgrade page.
Why it works. The rename, the reference page, the upgrade guide and the changelog entry merge as one unit, so there is never a week when the docs describe an option the code no longer has. The checks caught two mechanical problems before anyone read the change, and the human review went to the one thing a machine can't judge: whether a reader upgrading will notice the deadline. The changelog entry uses the Deprecated heading from Keep a Changelog, and the changelog examples show the format. When 2.5 ships, its docs are a tag; the 2.4 docs stay as they were for anyone who hasn't upgraded.
What docs as code changes
In a wiki or a help-center editor, documentation lives apart from the product, and a renamed setting can stay wrong on the page until a reader complains. Docs as code moves the page next to the code it describes, so one pull request can change both, the same reviewers see both and the same release ships both.
Write the Docs lists what teams gain: writers fit in better with development teams, developers often write a first draft, and a team can block the merge of a new feature that arrives without documentation. Three more gains come from Git itself:
- Every change has a history. Who changed a sentence, when, and in which pull request, with the discussion attached.
- Docs are versioned with the product. The docs for version 2.4 stay as they were when 2.5 ships, because each release is a tag in the same repository.
- Machines do the dull checks. Broken links, banned words and malformed tables fail the build instead of reaching readers.
For the approach at book length, the Write the Docs guide recommends Anne Gentle's Docs Like Code.
The docs-as-code toolchain
Each job in the workflow has a few common tools. Most teams pick one per row and keep the set small.
| Job | Common tools | What it does for the writer |
|---|---|---|
| JobWrite | Common toolsMarkdown (CommonMark), reStructuredText, AsciiDoc | What it does for the writerPlain text that any editor opens and Git can compare line by line |
| JobStore and version | Common toolsGit, hosted on a platform such as GitHub or GitLab | What it does for the writerA history of every change, and branches for work in progress |
| JobReview | Common toolsPull requests (merge requests on GitLab), a CODEOWNERS file | What it does for the writerThe right reviewers are asked automatically, with comments on the exact line |
| JobCheck | Common toolsmarkdownlint, Vale, lychee, a strict build | What it does for the writerFormatting, style and link problems fail before a person reviews the change |
| JobBuild | Common toolsMkDocs, Sphinx, Docusaurus, Hugo, Jekyll, Antora | What it does for the writerTurns a folder of text files into a website with navigation |
| JobPublish | Common toolsGitHub Pages, Read the Docs | What it does for the writerPuts the site online when changes merge; Read the Docs also keeps a version per release |
Plain-text markup
John Gruber published Markdown in 2004 so that a document would be “publishable as-is, as plain text”, readable before anything converts it. Its weakness is dialects: tables, footnotes and warning boxes work differently from one tool to the next. CommonMark, “a strongly defined, highly compatible specification of Markdown”, settles the core syntax, and site builders add their own extensions on top. Sphinx reads reStructuredText and, through MyST, Markdown; Antora uses AsciiDoc. Both of those formats have features basic Markdown lacks, such as includes and admonitions.
Git and pull requests
Branches and pull requests work for prose exactly as they do for code. On GitHub, the people a CODEOWNERS file names are asked to review automatically when a pull request touches their files (draft pull requests excepted), and a branch protection rule can require a code owner's approval before the merge. The file can live in .github/, the repository root or docs/; GitHub uses the first one it finds, in that order.
Site builders
A static site generator turns the folder of text files into a website. The common choices differ mostly in markup and ecosystem:
- MkDocs builds a site from Markdown files and a single YAML configuration file,
mkdocs.yml. - Sphinx takes reStructuredText or MyST Markdown and outputs HTML, LaTeX for PDF, ePub and more. Python's own documentation is built with it.
- Docusaurus is built on React, lets writers embed components in Markdown through MDX, and has document versioning built in.
- Hugo, written in Go, is a general site builder widely used for documentation; the Kubernetes docs are Markdown with custom Hugo shortcodes.
- Jekyll has built-in support on GitHub Pages.
- Antora builds AsciiDoc from one or more Git repositories, which suits a product documented across several.
Automated checks
- markdownlint checks Markdown against numbered rules, such as MD001 (heading levels should go up one at a time) and MD040 (fenced code blocks should name their language).
- Vale, “a command-line linter for prose”, checks text against style rules you write in YAML or against published styles such as Microsoft's, Google's or Red Hat's. GitLab keeps the Vale rules for its documentation in the repository, under
doc/.vale, and enforces the error-level rules in its pipelines. - lychee is a link checker written in Rust that finds broken links in Markdown, HTML and other files.
- A strict build catches the rest:
mkdocs build --strictaborts the build on any warning, so a problem nobody would scroll back to read stops the merge.
Set up a docs-as-code workflow
The setup below uses MkDocs, GitHub and GitHub Actions for a small command-line tool called acme-cli. The steps are the same with other tools; only the file names change.
- Put the docs next to the code
Create a
docsfolder in the product's repository, with one Markdown file per page and subfolders that match the site's sections. Use a separate docs repository only when one set of docs covers several codebases.Repository layout
acme-cli/ ├── src/ ├── tests/ ├── docs/ │ ├── index.md │ ├── get-started/ │ │ └── install.md │ ├── how-to/ │ │ └── upgrade-to-2.5.md │ ├── reference/ │ │ └── configuration.md │ └── concepts/ │ └── how-caching-works.md ├── styles/ │ └── Acme/ │ └── Terms.yml ├── .github/ │ ├── CODEOWNERS │ └── workflows/ │ └── docs.yml ├── .vale.ini ├── mkdocs.yml └── CHANGELOG.md - Choose one markup and write down the house rules
Pick Markdown unless your site builder needs something else, then write a short style guide: terms, headings, code samples, warning boxes. Keep it in the same repository, so a rule change is reviewed like a page change. The style guide examples show what published technical guides cover, and the style guide template gives you the sections.
- Configure the site builder
In MkDocs, one file sets the site name, the navigation and the link back to the repository. With
repo_urlandedit_uriset, themes that support it show an edit link on every page, which opens that page's source file.mkdocs.yml
site_name: acme-cli documentation repo_url: https://github.com/your-org/acme-cli edit_uri: edit/main/docs/ nav: - Home: index.md - Get started: - Install: get-started/install.md - How-to guides: - Upgrade to 2.5: how-to/upgrade-to-2.5.md - Reference: - Configuration: reference/configuration.md - Concepts: - How caching works: concepts/how-caching-works.mdRun
mkdocs serveto preview the site at http://127.0.0.1:8000/ while you write.mkdocs buildwrites the finished site to a folder namedsite. - Write on branches, one change per pull request
Small pull requests get reviewed; a forty-page rewrite on one branch collects merge conflicts. Put the docs change in the same pull request as the code change it describes.
- Assign reviewers with CODEOWNERS
Technical review and docs review are different jobs. The engineer checks that the page is true; the writer checks that a reader can follow it. When several lines match a file, the last one wins, so the configuration page below goes to both teams.
.github/CODEOWNERS
# The docs team reviews every change to the docs and the style rules /docs/ @your-org/docs-team /styles/ @your-org/docs-team .vale.ini @your-org/docs-team # The configuration page also goes to the engineers who own the code /docs/reference/configuration.md @your-org/cli-maintainers @your-org/docs-team - Automate the checks
Start Vale with errors only. A first run over an old docs folder can bury a team in warnings, so set
MinAlertLeveltoerror, fix those, then lower the threshold. Your own rules go in a folder underStylesPath; to add a published style, list it underPackagesand runvale sync, which Vale's documentation also recommends running in CI before linting. The Vale action in the workflow below runs it for you..vale.ini
StylesPath = styles MinAlertLevel = error [*.md] BasedOnStyles = Vale, Acmestyles/Acme/Terms.yml
extends: substitution message: "Consider using '%s' instead of '%s'" level: error ignorecase: true swap: e-mail: email log in to: sign in to config file: configuration fileThen run every check on every pull request. This workflow lints the Markdown, applies the Vale rules, checks the links and builds the site in strict mode; any failure blocks the merge once the check is required.
.github/workflows/docs.yml
name: Docs checks on: pull_request jobs: docs: runs-on: ubuntu-latest steps: # Action versions current as of November 2026 - uses: actions/checkout@v7 - name: Lint Markdown run: npx --yes markdownlint-cli2 "docs/**/*.md" - name: Check style with Vale uses: vale-cli/vale-action@v3 with: files: docs fail_on_error: true - name: Check links uses: lycheeverse/lychee-action@v2 with: args: --no-progress "docs/**/*.md" fail: true - uses: actions/setup-python@v7 with: python-version: "3.12" - name: Build the site in strict mode run: | pip install "mkdocs<2" mkdocs build --strict - Publish on merge and version with releases
GitHub Pages can publish when changes are pushed to a branch, from the root or a
/docsfolder, or from a GitHub Actions workflow, andmkdocs gh-deploybuilds the docs and commits them to agh-pagesbranch. For versioned docs, Read the Docs, which hosts “any tool that can output HTML”, turns Git tags and branches into versions you activate. Itslatestfollows the default branch andstabletracks the newest stable release. - Make docs part of done
Add a docs checkbox to the pull request template and agree which changes need documentation: new settings, changed behavior, new error messages. A feature isn't finished until its page is, and the reviewer who approves the code checks that the box is ticked honestly.
When docs as code fits
It fits when
- Writers and developers work on the same product and release schedule
- The docs must match specific versions of the software
- Some reference text sits in the code itself, such as API descriptions or command help
- Changes need review and a traceable history
- Contributors already use Git, or can learn the basics
It struggles when
- Most contributors are support agents, lawyers or marketers who never touch Git
- Reviewers expect Word files with tracked changes
- A large content set depends on heavy reuse and translation workflows built into structured authoring tools
- Nobody owns the build, so one broken check blocks every fix
- Writers spend more time on merge conflicts than on sentences
It can also be more than a team needs. A single README, or a small help center that one person updates a few times a year, gets little from a site builder, three linters and a pipeline someone has to keep running. Keep that README in the repository, review its changes in pull requests as you already do, and add the build and the checks when there are enough pages for links and terms to drift.
You don't have to move everything. Reference pages and developer guides can live in the repository while a support team keeps its help center in the tool it already uses, with links between the two. A README is often the first page a team moves, and the product documentation template helps plan which pages follow it into the docs folder.
Common docs-as-code mistakes
- Moving the files but not the habit. Docs in Git that are still written after the release are a wiki with extra steps. The gain comes from changing the docs in the same pull request as the code.
- Turning on every check at once. Hundreds of warnings on day one teach people to ignore the checks. Start with errors and raise the bar as the backlog clears.
- Treating a green build as a review. A linter knows that “e-mail” is off-style. It doesn't know whether step 3 is true. Keep a human technical review and a human edit.
- Locking yourself into a dialect. Every builder-specific extension, such as tabs or custom shortcodes, is something to convert if you ever change builders. Use them where they earn their place.
- Long-lived docs branches. A rewrite kept on a branch for six weeks collides with every small fix made in the meantime. Merge it in pieces.
- Shutting out people who don't use Git. Support, product and legal know things the docs need. Give them the edit link, or a simple way to send changes to a writer.
- No owner for the pipeline. When the build breaks and nobody owns it, every docs fix waits. Name an owner, as you would for any other service.
The writing itself doesn't change with the tooling. The principles in what technical writing is apply to a Markdown file exactly as they do to a printed manual.
If you draft long pages in Strokes, import the Markdown files from the docs folder into a project and export finished pages back to Markdown before you commit. For a rename like #482, agent mode can update every page in the project that uses the old name, showing each step, before Vale and your reviewers see the pull request.
Frequently asked questions
Is docs as code only for software documentation?
No. The workflow suits any documentation that changes often and benefits from review and history: internal handbooks, policies, style guides, course materials and even books. What it needs is contributors willing to use Git, or a writer who handles Git for them. Content reviewed mainly in Word by people outside the team is usually easier to manage elsewhere.
Do technical writers need to know Git?
For docs as code, yes, at least the basics: cloning a repository, creating a branch, committing, pushing, opening a pull request and responding to review. Resolving a simple merge conflict is the next skill to learn. Writers don't need to program, but being able to read the code a page documents helps them check that the page is right.
What is the difference between docs as code and a wiki?
A wiki stores pages in its own system and usually lets people edit them directly, with no review before a change goes live. Docs as code keeps pages as files in a Git repository next to the product, so every change can go through review and automated checks and be tied to a specific release. A wiki is quicker to edit; docs as code is easier to keep accurate.

