Fix: MkDocs Not Working — Material Theme, Navigation, and Plugin Errors
Part of: Python Errors
Quick Answer
How to fix MkDocs errors — Config value 'theme' must be a string or dict, mkdocstrings not generating API docs, navigation pages missing, mkdocs serve port already in use, GitHub Pages deploy failed, and broken links not detected.
The Error
You configure MkDocs Material and the build fails with a confusing message:
ERROR - Config value 'theme': Unrecognised theme name: 'material'.
The theme has to be installed.Or mkdocstrings doesn’t generate API docs from your Python code:
::: mypackage.MyClass$ mkdocs build
# Output: literally renders "::: mypackage.MyClass" as textOr your nav entries don’t appear in the sidebar:
nav:
- Home: index.md
- Guide: guide.md
- API: api.mdBut the sidebar shows nothing — or shows different pages than expected.
Or mkdocs serve says the port is in use:
[I 250424 10:00:00 server:335] OSError: [Errno 48] Address already in useOr GitHub Pages deploy works locally but fails in CI:
Permission denied (publickey).
fatal: Could not read from remote repository.MkDocs is the Markdown-first alternative to Sphinx — simpler config, faster builds, great for project docs and tutorials. The Material theme (mkdocs-material) is the de facto standard, providing a polished UI with search, dark mode, and navigation. But the plugin ecosystem and configuration grammar create specific failure modes. This guide covers each.
Why This Happens
MkDocs has a small core and relies on plugins and themes installed as separate packages. The theme: material line in mkdocs.yml references a theme by name — but the package providing it must be installed separately. Same for plugins (mkdocstrings, mike, awesome-pages). MkDocs doesn’t install dependencies for you.
The navigation is built from either explicit nav: entries in mkdocs.yml or auto-discovered from the file structure. Mixing the two patterns confuses MkDocs — it uses your explicit nav but ignores files not listed.
Fix 1: Installing MkDocs and Material
# Core MkDocs
pip install mkdocs
# Material theme (recommended)
pip install mkdocs-material
# Common plugins
pip install mkdocstrings[python] # API doc generation from Python source
pip install mkdocs-awesome-pages-plugin # Automatic nav from file structure
pip install mike # Version managementMinimal mkdocs.yml:
site_name: My Project
site_url: https://myproject.example.com/
repo_url: https://github.com/me/myproject
repo_name: me/myproject
theme:
name: material
features:
- navigation.tabs
- navigation.sections
- navigation.expand
- content.code.copy
- content.code.annotate
- search.highlight
palette:
- scheme: default
primary: indigo
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: indigo
toggle:
icon: material/brightness-4
name: Switch to light mode
icon:
repo: fontawesome/brands/github
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets
- attr_list
- md_in_html
- tables
- toc:
permalink: true
plugins:
- search
- mkdocstrings:
handlers:
python:
options:
show_source: true
show_root_heading: true
heading_level: 2Build and serve:
mkdocs serve # Start dev server at http://127.0.0.1:8000
mkdocs build # Build static HTML to ./site/
mkdocs build --strict # Fail on warnings (use for CI)Common Mistake: Copying theme: name: material config from a tutorial without pip install mkdocs-material. The error message is helpful (“theme has to be installed”), but it’s still the #1 first-time MkDocs issue. Always check that every theme and plugin in mkdocs.yml has a matching pip install.
Fix 2: Project Layout and File Discovery
my-project/
├── mkdocs.yml
├── docs/
│ ├── index.md
│ ├── guide/
│ │ ├── installation.md
│ │ ├── quickstart.md
│ │ └── advanced.md
│ └── api/
│ └── reference.md
└── site/ (build output, gitignored)MkDocs auto-discovers everything under docs/ and builds a nav from the file structure. To customize, declare an explicit nav:
nav:
- Home: index.md
- Guide:
- Installation: guide/installation.md
- Quickstart: guide/quickstart.md
- Advanced: guide/advanced.md
- API Reference: api/reference.mdDefault nav rules (when nav: is omitted):
- Files are sorted alphabetically
index.mdbecomes the section landing page- Underscores and hyphens are converted to spaces, title-cased
- File names like
01-intro.mdlose the prefix
mkdocs-awesome-pages-plugin — control nav from per-folder config:
pip install mkdocs-awesome-pages-plugin# mkdocs.yml
plugins:
- awesome-pages# docs/guide/.pages
title: User Guide
nav:
- installation.md
- quickstart.md
- advanced.md
- troubleshooting.mdFar easier than maintaining mkdocs.yml nav when you have many files.
Fix 3: mkdocstrings — Auto API Docs
pip install mkdocstrings[python]# mkdocs.yml
plugins:
- mkdocstrings:
handlers:
python:
paths: [src] # Where to find your Python code
options:
show_source: true
show_root_heading: true
members_order: source
docstring_style: google
show_signature_annotations: true
separate_signature: true
line_length: 80Use in Markdown:
# API Reference
## MyClass
::: mypackage.MyClass
options:
heading_level: 3
show_root_full_path: false
## Utility Functions
::: mypackage.utils
options:
members:
- fetch
- parseCommon docstring styles (set via docstring_style):
| Style | Format |
|---|---|
google | Args:, Returns:, Raises: |
numpy | Parameters\n----------, Returns\n------- |
sphinx | :param x:, :returns:, :raises: |
Pro Tip: Pick a docstring style and enforce it project-wide. Mixed styles render inconsistently — some sections show fields nicely, others render as plain text. The docstring_style: google is the most readable in source code; the numpy style is preferred in scientific Python.
Common Mistake: Omitting paths: and finding mkdocstrings can’t import your package. Without paths:, it looks in the default Python path — which often misses your project source. Set paths: [src] (or wherever your package lives) explicitly.
Fix 4: Navigation Features (Material Theme)
theme:
name: material
features:
- navigation.tabs # Top-level tabs
- navigation.tabs.sticky # Tabs always visible
- navigation.sections # Expand top-level
- navigation.expand # Expand all by default
- navigation.indexes # Section index pages
- navigation.top # "back to top" button
- navigation.footer # Prev/next page footer links
- navigation.tracking # Update URL with anchor as you scroll
- toc.follow # TOC scrolls with content
- search.suggest # Search suggestions
- search.highlight # Highlight search matches
- content.code.copy # Copy button on code blocks
- content.code.annotate # Inline code annotations
- content.tabs.link # Linked content tabsSection index pages with navigation.indexes:
docs/
├── guide/
│ ├── index.md ← This becomes the "Guide" section landing page
│ ├── installation.md
│ └── quickstart.mdWhen navigation.indexes is enabled, clicking “Guide” in the nav shows guide/index.md instead of expanding the section without content.
Hide a page from nav while keeping it accessible:
nav:
- Home: index.md
- Guide: guide.md
# changelog.md exists but not in nav — still accessible at /changelog/Or via front matter:
---
hide:
- navigation
- toc
---
# Standalone PageFix 5: mkdocs serve Port Conflicts
OSError: [Errno 48] Address already in useDefault port 8000 is taken. Use a different port:
mkdocs serve --dev-addr 127.0.0.1:8001
mkdocs serve -a 0.0.0.0:8080 # Bind to all interfaces (for VM/Docker)Find what’s using port 8000:
lsof -i :8000 # macOS/Linux
netstat -ano | findstr :8000 # Windows
# Kill the process
kill $(lsof -ti :8000)For port conflict patterns, see port 3000 already in use.
mkdocs serve doesn’t auto-reload on config changes by default — only Markdown changes. Force a full reload:
mkdocs serve --watch mkdocs.yml --watch src/
# Now changes to mkdocs.yml or your source code trigger rebuildsFix 6: Admonitions and Code Block Features
Material supports a wide range of callouts via pymdownx.details:
!!! note "Optional Title"
This is a note callout.
!!! warning
This is a warning.
!!! danger "Important"
This is critical.
!!! tip
Pro tip content.
??? abstract "Collapsible"
This admonition is collapsible (starts collapsed).
???+ info "Expanded by default"
Starts expanded.Admonition types: note, abstract, info, tip, success, question, warning, failure, danger, bug, example, quote.
Code blocks with syntax highlighting and titles:
```python title="example.py" linenums="1" hl_lines="2-3"
def hello():
name = "World"
print(f"Hello, {name}!")
**Code block with line annotations:**
```markdown
```python
def fetch(url):
response = requests.get(url, timeout=30) # (1)!
return response.json() # (2)!- Always set a timeout for HTTP requests.
- Assumes the response is JSON.
The `(1)!` syntax creates clickable annotations — hovering shows the explanation.
**Content tabs:**
```markdown
=== "Python"
```python
print("Hello")
```
=== "JavaScript"
```javascript
console.log("Hello");
```
=== "Go"
```go
fmt.Println("Hello")
```Fix 7: GitHub Pages Deployment
mkdocs gh-deploy
# Builds, commits to gh-pages branch, pushesCommon deploy errors:
fatal: Could not read from remote repository.
Permission denied (publickey).The local git user can’t push. Either:
- Use HTTPS:
git remote set-url origin https://github.com/me/repo.git - Configure SSH keys (most reliable for CI)
- Use a personal access token
Deploy via GitHub Actions (recommended):
# .github/workflows/docs.yml
name: Deploy MkDocs
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git-revision-date plugin
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install mkdocs-material mkdocstrings[python] mkdocs-awesome-pages-plugin
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Deploy
run: mkdocs gh-deploy --forceSet GitHub Pages source to “Deploy from a branch” → gh-pages branch in repo settings.
Common Mistake: Setting up GitHub Pages but forgetting to enable it in repo settings. The Actions workflow succeeds and pushes to gh-pages, but the page returns 404. Always verify Settings → Pages → Source is set to gh-pages branch (or main /docs if you prefer).
Fix 8: Custom Domain and Versioning
Custom domain:
# mkdocs.yml
site_url: https://docs.myproject.com/# docs/CNAME (one line, no quotes)
docs.myproject.comThe CNAME file is automatically copied to the build output and GitHub Pages reads it.
Multi-version docs with mike:
pip install mike# Deploy v1.0 docs
mike deploy --push --update-aliases 1.0 latest
# Deploy v2.0 — make it the new default
mike deploy --push --update-aliases 2.0 latest
mike set-default --push latest# mkdocs.yml — add version selector
extra:
version:
provider: mike
default: latestmike keeps multiple versions of your docs on gh-pages — users can switch between them via a version dropdown.
Pro Tip: Use mike from day one if you’re documenting a library that follows semver. Switching from single-version to multi-version mid-project means rewriting URLs and breaking external links. Setting up mike for v1 is trivial; migrating later is painful.
Platform Differences: MkDocs vs Material vs ReadTheDocs vs Sphinx
The “MkDocs not working” symptom often depends on which theme and hosting platform you pair it with. Pick the wrong combination and entire features stop functioning.
Plain MkDocs theme (mkdocs) — The built-in theme. Light, no JavaScript-heavy widgets, no dark mode, no instant navigation. Features like navigation.tabs, content.code.copy, and palette from the Material config are silently ignored. If you copied a tutorial that uses theme.features, the build still succeeds but the UI looks like 2014.
Material for MkDocs (mkdocs-material) — A separate PyPI package, not a built-in theme. It ships its own search index, instant navigation (XHR-based page swaps), and a JavaScript runtime. Most plugins like mkdocs-glightbox, mkdocs-git-revision-date-localized-plugin, and the social plugin assume Material — they break or do nothing on the default theme.
ReadTheDocs theme (mkdocs-rtd-dropdown and friends) — Mimics the classic Sphinx RTD look. Useful when migrating from Sphinx, but Material-specific features (admonition icons, tabs syntax ===) won’t render. Use pymdownx.superfences cautiously; some custom fences need Material’s CSS.
Sphinx — Different beast entirely. Uses reStructuredText by default, supports intersphinx, and has stronger autodoc for cross-referenced Python APIs. If you need cross-project linking or automodule over deeply nested packages, see Sphinx not working for the migration tradeoffs.
GitHub Pages vs ReadTheDocs.io vs Cloudflare Pages:
- GitHub Pages uses
mkdocs gh-deployto push togh-pages. Free, but every push rebuilds publicly. Custom domains need aCNAMEfile indocs/. - ReadTheDocs.io builds in their CI based on
.readthedocs.yaml. Supports versioned docs natively (nomikeneeded) but their build container is older — newermkdocs-materialversions sometimes fail until you pin Python 3.11+. - Cloudflare Pages / Netlify treats it as a static site. Build command
mkdocs buildand publish directorysite/. Fastest CDN butmkdocs gh-deploywon’t work — disable it and let the platform run the build.
Windows path issues — mkdocs serve on Windows occasionally fails to detect file changes in WSL-mounted directories (/mnt/c/...). Run MkDocs inside WSL on a native Linux path, or use PowerShell directly with mkdocs serve --dirty to skip the broken watcher.
Monorepo deployments — For multi-project repos, use mkdocs-monorepo-plugin instead of mounting subdirectories manually. It merges multiple mkdocs.yml files into one site without symlinks, which keeps Windows builds happy.
Still Not Working?
MkDocs vs Sphinx
- MkDocs — Markdown-first, smaller, faster builds. Best for prose-heavy project docs and tutorials.
- Sphinx — More features (intersphinx, complex API doc generation), RST-first. Best for cross-referenced Python library docs.
For Python library docs with extensive API references, Sphinx is more mature. For everything else (project docs, tutorials, marketing-style sites), MkDocs Material is faster to set up and modify.
Search Configuration
Default search works but is basic. Configure for better results:
plugins:
- search:
separator: '[\s\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
lang:
- en
- jaFor multilingual docs, list all languages in lang: — search separately indexes content for each.
Includes and Snippets
Reuse content across pages:
markdown_extensions:
- pymdownx.snippets:
base_path: docs/snippets# Main Page
--8<-- "common-warning.md"This includes docs/snippets/common-warning.md inline at build time.
Internal Link Validation
mkdocs build --strict
# Fails on any warning (including broken internal links)Use in CI to catch broken links before deploy. For broader link checking (external URLs), use lychee or linkcheck as a separate step.
Combining with Other Frameworks
If your docs include Python code that’s also tested, use mkdocstrings together with pytest --doctest-modules to keep examples accurate. For pytest patterns that integrate with docs, see pytest fixture not found.
If your CI builds docs via GitHub Actions and the workflow fails on permission errors, see github-actions permission denied for the permissions: contents: write fix that mkdocs gh-deploy requires.
Diagrams with Mermaid
Material supports Mermaid diagrams natively:
markdown_extensions:
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format```mermaid
graph LR
A[Request] --> B{Auth?}
B -->|Yes| C[Handler]
B -->|No| D[401]
C --> E[Response]
The diagram renders in the browser via Mermaid.js — no separate build step. Great for architecture overviews and flow diagrams.
### Social Cards
Material can auto-generate Open Graph images for each page:
```yaml
plugins:
- social:
cards: true
cards_layout_options:
background_color: "#0066CC"
color: "#FFFFFF"Requires pip install "mkdocs-material[imaging]" and ImageMagick — but produces shareable OG cards for every page automatically.
Insiders Edition Drift
Squidfunk’s mkdocs-material-insiders ships features (privacy plugin, blog plugin extras) months before they land in the public version. If you copy a config from official Material docs and a feature like plugins: - privacy fails with “Unknown plugin,” you’re on the public version and need the sponsorware package. Don’t downgrade public Material to match the docs — instead use https://squidfunk.github.io/mkdocs-material/insiders/ to verify which features are gated.
Strict Mode Hides Real Errors
mkdocs build --strict upgrades every warning into a failure — useful in CI, but it also masks the first error in a flood of “page not in nav” warnings. Run mkdocs build once without --strict to see the actual stack trace, fix the real issue, then re-enable strict mode. This is the single most common workflow mistake when CI starts failing after a config change.
Theme Override Cascade
Material lets you override individual templates by placing files in overrides/. The path must match the original template path exactly: overriding the footer requires overrides/partials/footer.html. A common trap is editing overrides/footer.html directly — Material ignores the file because it doesn’t match the partials path, and the change silently has no effect.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Sphinx Not Working — Autodoc Import Errors, Theme Issues, and Cross-References
How to fix Sphinx errors — autodoc cannot import module, theme not found, intersphinx links broken, ReadTheDocs build failed, MyST markdown not rendering, and unknown directive in conf.py.
Fix: joblib Not Working — Parallel Backends, Memory Cache, and Pickling Errors
How to fix joblib errors — Parallel n_jobs slower than expected, Memory cache miss, backend loky vs threading vs multiprocessing, pickling lambda not supported, dump load file size, and pytest interference.
Fix: Marshmallow Not Working — Schema Errors, Load vs Dump, and Field Validation
How to fix Marshmallow errors — Schema not validated on dump, ValidationError messages format, unknown field handling, missing vs default, post_load object construction, and Marshmallow 3 to 4 migration.
Fix: Pipenv Not Working — Lock File Generation, Shell Activation, and Dependency Resolution
How to fix Pipenv errors — pipenv lock takes forever, Pipfile.lock not generated, shell activation broken, no virtualenv created, dependency conflict, and migration to uv or Poetry.