Updated 30 May 2026

When you build a project, you need good technical documentation to go with it. This post walks through setting up DocFX, configuring automated API doc generation from your C# XML comments, and deploying the generated site to GitHub Pages with GitHub Actions.

What is DocFX?

Built and maintained by Microsoft, DocFX is an automatic documentation generator for .NET projects. It supports C#, VB, and F#, and produces static web pages in the same style as Microsoft Learn.

It lets you write hand-authored usage guides in Markdown alongside auto-generated API reference pulled from the projects in your repository. The default templates are good enough to ship, and the templating system is open enough to bring your own branding when you outgrow them.

DocFX sits inside your repository, as close to the source as possible. You’ll have your .NET API docs deploying to GitHub Pages by the end of this post.

Configure your .NET projects to generate documentation

To prepare your projects for DocFX, configure them to emit XML documentation. Add this to each .csproj you want DocFX to pick up:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

When you build, the compiler will emit XML docs from your /// summaries. If you’ve got summaries missing, the build will warn you for every one, useful pressure to get the docs in order before you set DocFX loose on them.

Set up DocFX for your .NET projects

The canonical way to install DocFX today is as a .NET global tool:

dotnet tool install -g docfx

This works the same on Windows, macOS, and Linux as long as you have a current .NET SDK installed. It also makes the CI install trivial, as you’ll see in the workflow below.

If you prefer a package manager, the Chocolatey (Windows) and Homebrew (macOS/Linux) packages are still maintained.

Once installed, create your DocFX project inside your repo:

docfx init -o docs -q

This creates a docs folder and scaffolds the files DocFX needs. Put it wherever fits your repo layout.

Open the generated docs/docfx.json, you’ll need to point it at your source projects and tell it which target framework to use.

Here’s the docfx.json I use on MADE.NET today, which is a good starting point for any multi-project .NET solution:

{
  "metadata": [
    {
      "src": [
        {
          "src": "../",
          "files": [
            "src/**.csproj"
          ],
          "exclude": [
            "**/bin/**",
            "**/obj/**",
            "_site/**"
          ]
        }
      ],
      "dest": "api",
      "properties": {
        "TargetFramework": "net10.0"
      }
    }
  ],
  "build": {
    "content": [
      {
        "files": [
          "api/**.yml",
          "api/index.md"
        ]
      },
      {
        "files": [
          "articles/**.md",
          "articles/**/toc.yml",
          "toc.yml",
          "*.md"
        ]
      }
    ],
    "resource": [
      {
        "files": [
          "images/**"
        ]
      }
    ],
    "overwrite": [
      {
        "files": [
          "apidoc/**.md"
        ],
        "exclude": [
          "obj/**",
          "_site/**"
        ]
      }
    ],
    "dest": "_site",
    "template": [
      "default",
      "modern",
      "templates/custom"
    ],
    "globalMetadata": {
      "_appLogoPath": "images/Logo.png",
      "_appFaviconPath": "images/Logo.png",
      "_appTitle": "My App",
      "_appFooter": "Copyright (c) My Company",
      "_enableSearch": true
    }
  }
}

The bits worth calling out:

  • metadata.src.src is set to "../" because this docfx.json lives in docs/ at the repo root, and the .csproj files live in src/ one level up. Adjust to match your layout.
  • properties.TargetFramework should match a TFM your projects actually build against, DocFX uses it to pick the right output to read XML from. I’m on net10.0 here; set yours accordingly.
  • template lists ["default", "modern", "templates/custom"]. The modern template is shipped with DocFX and is the one you actually want. templates/custom is where I drop site-specific overrides (favicon CSS, header tweaks, that kind of thing).
  • _enableSearch: true is in globalMetadata by default, site-wide search just works.

Swap in your own _appTitle, _appFooter, and _appLogoPath and you’re configured.

A default DocFX hosted .NET docs site

To run the site locally:

docfx docs/docfx.json --serve

This builds the static pages and serves them on http://localhost:8080 for you to explore.

Create a deployment for DocFX site to GitHub Pages

Now push your changes and add a GitHub Actions workflow to build and deploy the site to GitHub Pages.

Create .github/workflows/docs.yml. This is the exact workflow that publishes the MADE.NET docs site today:

name: Docs

on:
  push:
    branches:
    - main
    paths:
    - src/**
    - docs/**
    - .github/workflows/docs.yml
  pull_request:
    branches:
    - main
    paths:
    - docs/**
    - .github/workflows/docs.yml
  workflow_dispatch:

concurrency:
  group: docs-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions:
  contents: write

jobs:
  generate-docs:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6

    - name: Setup .NET 8.0
      uses: actions/setup-dotnet@v5
      with:
        dotnet-version: 8.0.x

    - name: Setup .NET 10.0
      uses: actions/setup-dotnet@v5
      with:
        dotnet-version: 10.0.3xx

    - name: Install DocFX
      run: dotnet tool install -g docfx

    - name: Restore projects
      run: dotnet restore MADE.NET.sln

    - name: Build DocFX site
      working-directory: docs
      run: docfx docfx.json
      continue-on-error: false

    - name: Publish DocFX site
      if: github.event_name == 'push'
      uses: peaceiris/actions-gh-pages@v4
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: docs/_site
        force_orphan: true

A few things that are different from the workflow I originally posted in 2022, and why:

  • Ubuntu, not Windows. The original used windows-latest because we were installing DocFX via Chocolatey. Now we install via the .NET CLI, so Ubuntu works fine and is faster and cheaper.
  • DocFX is installed as a .NET tool. No more Chocolatey action, no more PowerShell wrapper. One line: dotnet tool install -g docfx.
  • Both .NET 8 and .NET 10 SDKs are installed. MADE.NET multi-targets net8.0 and net10.0, and DocFX needs both SDKs available to read the right XML outputs. Trim or adjust this for your TFMs.
  • dotnet restore runs before the DocFX build. With multi-project solutions, DocFX needs the project references resolved, otherwise you’ll get partial or empty API output. Point this at your .sln (or the specific projects you’re documenting).
  • concurrency block cancels superseded runs on PRs. Useful when you’re iterating on docs and don’t want six builds queued up.
  • permissions: contents: write is required for actions-gh-pages to push to the gh-pages branch. GitHub tightened the default GITHUB_TOKEN permissions since the original post and this is now explicit rather than implicit.
  • peaceiris/actions-gh-pages@v4 still the best maintained option for this pattern. force_orphan: true keeps the gh-pages branch clean instead of accumulating history forever.

Commit it. The first successful run will create the gh-pages branch.

If you’re not already using GitHub Pages on the repo, finish the setup in your repo’s settings.

Updating GitHub repo settings to setup GitHub Pages for DocFX

Once the workflow has run once, set the source of GitHub Pages to the gh-pages branch. That’s where the static site was published. Don’t delete that branch.

Your .NET API docs are now building and deploying on every push to main.

What to do with your DocFX site next?

You’ve got the API reference auto-publishing. Time to make the rest of the docs experience match.

Hand-written articles

Alongside the generated API docs, you can author Markdown articles in the articles/ folder for installation guides, tutorials, and conceptual content. Update articles/toc.yml when you add files so the navigation reflects them.

Bring your own style with a custom template

Don’t ship a docs site that looks like everyone else’s. The modern template included in DocFX is a strong baseline, and you layer your own overrides on top via a custom template entry in docfx.json.

DocFX maintains a list of community templates you can start from, and the authoring docs cover building one from scratch.

Site branding (logo, favicon, title, footer) is set via globalMetadata, the example docfx.json above already shows the pattern.

Improve the local DocFX experience with npm-watch

By default, when you serve the site locally, edits don’t hot reload. npm-watch fixes that.

Add a package.json to your DocFX folder. This is the one I use on MADE.NET, note that the build_serve script runs docfx metadata before docfx build --serve, which means edits to your C# XML docs also pick up on the next reload, not just edits to Markdown:

{
  "name": "my-project-docs",
  "version": "1.0.0",
  "scripts": {
    "docfx": "npm-watch build_serve",
    "build_serve": "docfx metadata .\\docfx.json && docfx build .\\docfx.json --serve"
  },
  "watch": {
    "build_serve": {
      "patterns": [
        "templates/**/*",
        "api/**/*",
        "articles/**/*",
        "./*.md"
      ],
      "extensions": "md,yml,css,partial",
      "quiet": true,
      "legacyWatch": true,
      "delay": 500,
      "runOnChangeOnly": false
    }
  },
  "devDependencies": {
    "npm-watch": "^0.11.0"
  }
}

Run npm install then npm run docfx and you’ve got a live-reloading local docs site.

A DocFX site hosted on GitHub Pages in action

If you want a closer look at this setup running in production, both of these GitHub repos use the workflow and config from this post:

If you’ve got questions on getting DocFX set up for your repo, drop a comment below or reach out on socials.


Discover more from James Croft

Subscribe to get the latest posts sent to your email.

James Croft

Author James Croft

James is a senior software engineer at Microsoft with over 10 years experience designing and building large-scale, distributed, cloud-native systems. He's deeply experienced in C#, Python, and TypeScript, with specialization in AI agent architectures, retrieval-augmented generation (RAG), and production-grade Azure systems.

More posts by James Croft

Join the discussion One Comment

  • ashu says:

    thanks article is useful but how to read docfx.json from git repo?
    we have our docfx.json ready which is not at local it is at git repo ?

    how to read that and exec docfx.exe using that git path?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.