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.srcis set to"../"because thisdocfx.jsonlives indocs/at the repo root, and the.csprojfiles live insrc/one level up. Adjust to match your layout.properties.TargetFrameworkshould match a TFM your projects actually build against, DocFX uses it to pick the right output to read XML from. I’m onnet10.0here; set yours accordingly.templatelists["default", "modern", "templates/custom"]. Themoderntemplate is shipped with DocFX and is the one you actually want.templates/customis where I drop site-specific overrides (favicon CSS, header tweaks, that kind of thing)._enableSearch: trueis inglobalMetadataby default, site-wide search just works.
Swap in your own _appTitle, _appFooter, and _appLogoPath and you’re configured.

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-latestbecause 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.0andnet10.0, and DocFX needs both SDKs available to read the right XML outputs. Trim or adjust this for your TFMs. dotnet restoreruns 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).concurrencyblock cancels superseded runs on PRs. Useful when you’re iterating on docs and don’t want six builds queued up.permissions: contents: writeis required foractions-gh-pagesto push to thegh-pagesbranch. GitHub tightened the defaultGITHUB_TOKENpermissions since the original post and this is now explicit rather than implicit.peaceiris/actions-gh-pages@v4still the best maintained option for this pattern.force_orphan: truekeeps thegh-pagesbranch 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.

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.



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?