Fix: MkDocs Not Working — Material Theme, Navigation, and Plugin 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.
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. See Sphinx not working.
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.
For Sphinx as the alternative when you need stronger API doc features, see Sphinx not working. For pre-commit hooks that validate MkDocs config syntax before commits, see pre-commit not working.
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.
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.