Fix: Sphinx Not Working — Autodoc Import Errors, Theme Issues, and Cross-References
Part of: Python Errors
Quick Answer
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.
The Error
You run sphinx-build and autodoc can’t import your module:
WARNING: autodoc: failed to import module 'mypackage'; the following exception was raised:
No module named 'mypackage'Or the theme you configured silently falls back:
# conf.py
html_theme = "furo"$ make html
# Builds with the default "alabaster" theme instead — no errorOr cross-references to external docs (:py:class:\numpy.ndarray“) don’t link:
Unknown interpreted text role "py:class".
WARNING: undefined label: 'numpy.ndarray'Or ReadTheDocs builds locally but fails on the platform:
Could not import extension myst_parser (exception: No module named 'myst_parser')
ERROR: Service unavailableOr MyST markdown files render as plain text:
# index.md (in a Sphinx project)
Hello *world*Output: Hello *world* instead of italicized “world”.
Sphinx is the universal documentation tool for Python — RST or Markdown source, beautiful HTML output, cross-referenced API docs from docstrings, hosted on ReadTheDocs. But the configuration is intricate: extensions need to be both installed and listed in conf.py, themes need proper imports, MyST parser needs explicit setup, and the build environment must match exactly between local and CI. This guide covers each common failure.
Why This Happens
Sphinx is a Python program — autodoc imports your modules to extract docstrings. If your package isn’t on the Python path (or has import errors), autodoc silently skips it with a warning. The “build succeeded with warnings” message hides serious problems.
ReadTheDocs runs sphinx-build in an isolated environment that doesn’t include your dev dependencies by default. You must explicitly tell RTD what to install — usually via a requirements.txt for docs or a section in .readthedocs.yaml.
Fix 1: Project Setup and conf.py
# Initialize a Sphinx project
mkdir docs && cd docs
sphinx-quickstart
# Or use the full directory layout
sphinx-quickstart --sep --quiet \
--project="MyProject" \
--author="Your Name" \
--release="1.0.0" \
--language="en"Resulting structure:
docs/
├── source/
│ ├── conf.py
│ ├── index.rst
│ └── _static/
├── build/ (gitignored)
└── MakefileBuild:
cd docs
make html # On macOS/Linux
make.bat html # On Windows
# Output: build/html/index.htmlMinimal conf.py:
# docs/source/conf.py
import os
import sys
# Add project root to Python path so autodoc can import it
sys.path.insert(0, os.path.abspath("../.."))
project = "MyProject"
author = "Your Name"
release = "1.0.0"
extensions = [
"sphinx.ext.autodoc", # Generate API docs from docstrings
"sphinx.ext.napoleon", # Support Google/NumPy docstring styles
"sphinx.ext.intersphinx", # Cross-reference other projects' docs
"sphinx.ext.viewcode", # Add "view source" links
"myst_parser", # Markdown support
]
# Source file types
source_suffix = {
".rst": "restructuredtext",
".md": "markdown",
}
# Theme
html_theme = "furo" # or "sphinx_rtd_theme", "pydata_sphinx_theme", etc.
html_static_path = ["_static"]
# Cross-references to other projects
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"numpy": ("https://numpy.org/doc/stable", None),
"pandas": ("https://pandas.pydata.org/docs", None),
}Common Mistake: Forgetting sys.path.insert(0, ...) for autodoc. Sphinx runs from docs/source/; your package is at the project root. Without the path insert, autodoc can’t import your modules and silently produces empty documentation. The warning is in the build log but easy to miss.
Fix 2: Autodoc — Generating API Docs From Docstrings
.. autoclass:: mypackage.MyClass
:members:
:undoc-members:
:show-inheritance:
.. automodule:: mypackage.utils
:members:Common autodoc options:
| Option | Behavior |
|---|---|
:members: | Document public members |
:undoc-members: | Include members without docstrings |
:private-members: | Include _name members |
:special-members: __init__ | Include specific dunders |
:exclude-members: foo, bar | Hide specific members |
:show-inheritance: | Show base classes |
:inherited-members: | Include members from parent classes |
Auto-summary (cleaner API docs):
# conf.py
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
]
autosummary_generate = True # Auto-generate stub files.. autosummary::
:toctree: api/
:recursive:
mypackageThis generates separate page per module/class — much cleaner than one giant API page.
Napoleon for Google/NumPy docstring formats:
def fetch(url: str, timeout: float = 30.0) -> dict:
"""Fetch a URL and return JSON.
Args:
url: The URL to fetch.
timeout: Request timeout in seconds.
Returns:
Parsed JSON response.
Raises:
ConnectionError: If the host is unreachable.
ValueError: If the response isn't valid JSON.
Example:
>>> data = fetch("https://api.example.com/users")
>>> data["count"]
42
"""Without Napoleon, that docstring renders as plain text. With Napoleon, it becomes a beautifully formatted API doc with separated Arguments/Returns/Raises sections.
Fix 3: Markdown Support with MyST
Sphinx defaults to reStructuredText. For Markdown:
pip install myst-parser# conf.py
extensions = [
"myst_parser",
]
source_suffix = {
".rst": "restructuredtext",
".md": "markdown",
}Now .md files work:
# My Page
Some content with **bold** and *italic*.
Links: [Click me](other.md)
Code:
```python
def hello():
return "world"
**MyST adds extensions** for Sphinx-specific features in Markdown:
```python
# conf.py
myst_enable_extensions = [
"colon_fence", # ::: for directives
"deflist", # Definition lists
"fieldlist", # :field: lists
"linkify", # Auto-link URLs
"substitution", # ${variable} substitution
"tasklist", # GitHub-style [ ] [x] task lists
]Directives in MyST (colon fence syntax):
:::{note}
This is a note callout.
:::
:::{warning}
This is a warning.
:::
:::{admonition} Custom Title
:class: tip
Content with a custom title.
:::Cross-references in MyST:
See {ref}`my-section` or {py:func}`mypackage.utils.fetch`.
(my-section)=
## My Section
This section is referenceable.For a Markdown alternative when you don’t need Sphinx’s full power, see MDX not working for MDX patterns in Astro/Next.js docs sites.
Fix 4: Themes
# conf.py
html_theme = "furo"Install the theme first:
# Furo — modern, clean (highly recommended)
pip install furo
# Read the Docs (classic, used by many projects)
pip install sphinx-rtd-theme
# PyData (used by NumPy, Pandas, scikit-learn)
pip install pydata-sphinx-theme
# Book theme (Jupyter Book style)
pip install sphinx-book-themeWithout the install, Sphinx silently falls back to alabaster (built-in default) and emits a warning. The “theme not found” warning is the tell — fix it by installing.
Pro Tip: Furo is the modern default for new Sphinx projects — clean design, great mobile support, dark/light mode toggle, syntax highlighting that works out of the box. Use sphinx-rtd-theme only if you specifically want the ReadTheDocs aesthetic or are matching an existing project.
Theme configuration:
# conf.py — Furo
html_theme = "furo"
html_title = "MyProject Docs"
html_logo = "_static/logo.png"
html_favicon = "_static/favicon.ico"
html_theme_options = {
"source_repository": "https://github.com/me/myproject",
"source_branch": "main",
"source_directory": "docs/source/",
"light_logo": "logo-light.png",
"dark_logo": "logo-dark.png",
}html_static_path for custom CSS:
html_static_path = ["_static"]
html_css_files = ["custom.css"]/* docs/source/_static/custom.css */
:root {
--color-brand-primary: #0066CC;
}Fix 5: Cross-References (intersphinx)
Link to other projects’ docs without hardcoding URLs:
# conf.py
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"numpy": ("https://numpy.org/doc/stable", None),
"pandas": ("https://pandas.pydata.org/docs", None),
"django": ("https://docs.djangoproject.com/en/stable", "https://docs.djangoproject.com/en/stable/_objects/"),
}Reference syntax (RST):
The :py:class:`numpy.ndarray` is the core array type.
See :py:func:`numpy.array` for construction.
The :py:meth:`pandas.DataFrame.groupby` method ...MyST equivalent:
The {py:class}`numpy.ndarray` is the core array type.
See {py:func}`numpy.array` for construction.Common roles:
| Role | Target |
|---|---|
:py:class: | Python class |
:py:func: | Python function |
:py:meth: | Python method |
:py:mod: | Python module |
:py:attr: | Python attribute |
:py:exc: | Python exception |
:py:obj: | Generic Python object |
:ref: | Section ref in your own docs |
:doc: | Other doc page |
Common Mistake: Writing :class:\numpy.ndarray`(withoutpy:prefix). Without the explicit domain, Sphinx defaults tostd, which doesn't know about Python classes. Always use:py:class:` for Python references.
Check what intersphinx knows:
python -m sphinx.ext.intersphinx https://docs.python.org/3/objects.inv
# Prints every available referenceFix 6: Building and Iterating
cd docs
# Standard build
make html
# Watch mode — rebuild on changes (requires sphinx-autobuild)
pip install sphinx-autobuild
sphinx-autobuild source build/html --open-browser
# Clean rebuild (force full rebuild)
make clean && make html
# Build with warnings as errors (for CI)
sphinx-build -W -b html source build/htmlsphinx-autobuild is essential for iterative writing — it serves the docs on localhost, watches for changes, and auto-refreshes the browser.
Build with parallelism:
sphinx-build -j auto -b html source build/html
# -j auto uses all CPU coresMultiple output formats:
make html # HTML (default)
make epub # EPUB e-book
make latex # LaTeX (PDF prep)
make latexpdf # Builds LaTeX then runs pdflatex
make man # Unix man pages
make linkcheck # Validate all external linksmake linkcheck is invaluable in CI — catches broken external links before users do.
Fix 7: ReadTheDocs Configuration
ReadTheDocs runs Sphinx on every push. Configuration goes in .readthedocs.yaml:
# .readthedocs.yaml (at repo root)
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.12"
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs
sphinx:
configuration: docs/source/conf.py
fail_on_warning: false # Set to true to enforce warning-free buildsdocs/requirements.txt lists doc dependencies:
sphinx>=7
furo>=2024.1
myst-parser>=2
sphinx-autobuild>=2024.1Or via pyproject.toml extras:
[project.optional-dependencies]
docs = [
"sphinx>=7",
"furo>=2024.1",
"myst-parser>=2",
]# .readthedocs.yaml
python:
install:
- method: pip
path: .
extra_requirements: [docs]Common Mistake: Building locally with pip install -e . (editable), then on RTD only installing doc deps. Autodoc can’t find your package because it’s not actually installed. Either install the package in .readthedocs.yaml (via method: pip with path: .) or add sys.path.insert(0, ...) in conf.py.
Test the RTD build locally:
# Mirror RTD's build environment
docker run --rm -it -v $PWD:/docs python:3.12 bash
cd /docs
pip install -r docs/requirements.txt
pip install .
sphinx-build -b html docs/source docs/buildFix 8: Common Build Errors
ERROR: Could not import extension myst_parserThe extension isn’t installed in the build environment. Add to your docs requirements.
WARNING: autodoc: failed to import module 'mypackage'Either:
- Module not on Python path (add
sys.path.insert(0, ...)in conf.py) - Module has import errors (run
python -c "import mypackage"to verify) - C extensions can’t be built in the docs environment (use
autodoc_mock_imports)
Mock C extensions and heavy dependencies:
# conf.py
autodoc_mock_imports = [
"torch",
"tensorflow",
"cv2", # OpenCV (large)
"MyProject._extension", # Internal C extension
]Sphinx replaces these imports with mocks — autodoc generates docs without actually loading them. Useful when those deps don’t make sense in the docs environment.
WARNING: undefined label: 'my-section'You used :ref:\my-section“ but didn’t define the label. Add a label above the section:
.. _my-section:
My Section
----------WARNING: 'page.rst' isn't included in any toctreeThe page exists but isn’t reachable from the top-level index.rst. Add it to a toctree:
.. toctree::
:maxdepth: 2
page
other-pageOr suppress the warning for orphan pages:
:orphan:
My Standalone Page
==================Platform Differences and Tooling Choices
Sphinx is the default for serious Python documentation, but the broader landscape (MkDocs, Docusaurus, VitePress) covers different priorities. The autodoc family alone has three competing implementations, and ReadTheDocs has its own opinionated build environment. Picking the right combination upfront avoids weeks of migration later.
Sphinx vs MkDocs. Sphinx is reStructuredText-first with Markdown support via MyST; MkDocs is Markdown-first with no RST. Sphinx has autodoc, intersphinx, and a sprawling extension ecosystem. MkDocs has Material theme (best-in-class design out of the box) and a much shorter learning curve. Use Sphinx when API docs from docstrings matter. Use MkDocs for tutorial-heavy sites where the docs are mostly hand-written prose.
Sphinx vs Docusaurus. Docusaurus is React-based, MDX-powered, and built for project marketing sites that also happen to have docs. Strong for product launches with versioned docs (it tracks versions natively) but generating API docs from Python requires extra tooling (e.g., pydoc-markdown to convert docstrings to MDX). For a Python library, Sphinx wins. For a SaaS product whose docs are part of the marketing surface, Docusaurus wins.
Sphinx vs VitePress. VitePress is the Vue-flavored static site generator behind the Vue docs. Fast builds (Vite under the hood), clean default theme, Markdown-first. Same trade-off as Docusaurus: great for prose docs, poor fit for Python API generation. VitePress also lacks Sphinx’s mature search and cross-reference systems.
autodoc vs autoapi vs sphinx-apidoc. Three tools, similar goal, different mechanisms:
sphinx.ext.autodoc— Imports your modules at build time and extracts docstrings via Python introspection. Requires your package to be importable in the docs build env. Fails if you have heavy native deps.sphinx-autoapi— Parses source files statically without importing them. Doesn’t need the package installed. Better for projects with C extensions, optional deps, or import-time side effects.sphinx-apidoc— Command-line tool that generates.rststub files forautodoc. Often used as a one-time scaffold, then committed and hand-edited. Less common in modern projects.
Default to autodoc for pure Python packages. Switch to autoapi when your package’s import is heavy or unreliable in CI. Treat sphinx-apidoc as a scaffolding tool, not a build step.
myst-parser for Markdown. MyST is Sphinx’s Markdown bridge. It adds CommonMark plus directives via :::{note} colon-fence syntax. Cross-references work with {ref} and {py:class} roles. It does not support arbitrary CommonMark extensions used by other tools — GitHub-flavored task lists need myst_enable_extensions = ["tasklist"], footnotes need ["dollarmath"] or similar opt-ins. The default MyST is restrictive on purpose.
ReadTheDocs hosting. RTD provides free hosting for public projects, automatic builds on push, version switcher, search, and pull-request previews. The build environment is Ubuntu 22.04 with Python 3.12 by default (configurable). RTD does not install your package by default — you must add it explicitly in .readthedocs.yaml:
python:
install:
- method: pip
path: .
extra_requirements: [docs]The path: . line is the one most teams forget; without it, autodoc imports fail in CI but work locally because your local env has the package installed in editable mode.
Alternative hosts. GitHub Pages serves any static HTML, including Sphinx’s output — push the build/html directory to a gh-pages branch via the peaceiris/actions-gh-pages GitHub Action. Cloudflare Pages and Vercel both auto-build Sphinx if you set the build command (make html) and output directory (build/html). Use RTD for the version switcher and search; use Pages/Cloudflare if you want a single-version site with no fee tier limits.
Windows builds. make html requires Make, which Windows doesn’t ship with. Use .\make.bat html instead. Path separators in conf.py should use pathlib.Path or forward slashes — backslashes break on macOS/Linux. The Furo theme’s dark mode toggle uses CSS prefers-color-scheme and works identically across platforms.
Containerized builds. Mirror your RTD environment in CI by building inside the same Python container:
docker run --rm -v "$PWD":/docs -w /docs python:3.12 \
bash -c "pip install -e .[docs] && sphinx-build -W -b html docs/source docs/build"The -W flag turns warnings into errors. Running this in pre-merge CI catches broken cross-references before they hit RTD.
Still Not Working?
Sphinx vs MkDocs
- Sphinx — Mature, RST-first (but supports MyST markdown), complex API doc features, intersphinx, used by every major Python project.
- MkDocs — Markdown-first, simpler, faster builds. Best for prose-heavy docs without complex API references.
Use Sphinx when you need autodoc and cross-referenced API docs. Use MkDocs (with the Material theme) for tutorial-heavy or marketing-flavored docs.
Doctest in Documentation
Sphinx can run code blocks in your docstrings as tests:
def add(a: int, b: int) -> int:
"""Add two integers.
Example:
>>> add(2, 3)
5
>>> add(-1, 1)
0
"""
return a + bmake doctest
# Runs all >>> examples and checks output matchesBrilliant for keeping examples accurate — broken examples fail the build.
Version Switcher
PyData and Furo themes support a version switcher in the navbar. Configure in conf.py:
html_theme_options = {
"switcher": {
"json_url": "https://myproject.io/_static/switcher.json",
"version_match": release,
},
}The JSON lists versions with URLs. ReadTheDocs handles this automatically via its “Activate version” UI.
Integration with pytest
For pytest fixture patterns that exercise documented code examples, see pytest fixture not found. For pre-commit hooks that run make linkcheck or sphinx-build -W in CI, see pre-commit not working.
Type Hints in Docs
The sphinx_autodoc_typehints extension shows type annotations alongside docstrings:
pip install sphinx-autodoc-typehintsextensions = [
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints", # Must come AFTER autodoc
]For mypy-based type checking that pairs with documented type hints, see Python mypy type error.
Search Backend Choice
Sphinx ships with a JavaScript-based search index that ships with the HTML output — works offline, no server. For large doc sets (1000+ pages), the index gets unwieldy. Replace with sphinx-readthedocs-search (uses RTD’s hosted Elasticsearch) or sphinx-tabs-plugin plus Algolia DocSearch. RTD-hosted projects get the Elasticsearch backend automatically; self-hosted sites need Algolia or a separate search service.
Avoid master_doc Drift in Multi-Version Builds
When ReadTheDocs builds multiple versions (stable, latest, v1, v2), each version uses its own conf.py. If you renamed index.rst to README.rst in main but the v1 branch still uses index.rst, RTD builds fail on the v1 branch with master doc not found. Either keep the entry filename consistent across all maintained branches, or set root_doc explicitly in each branch’s conf.py to match its actual file.
Building PDFs Reliably
make latexpdf requires a TeX distribution (TeX Live on Linux, MacTeX on macOS, MiKTeX on Windows). RTD bundles a working LaTeX environment, but local builds fail with ! LaTeX Error: File 'sphinxhowto.cls' not found if you only have a minimal TeX install. For just-in-time PDFs, use ReadTheDocs’s “Download PDF” feature instead of building locally. For self-hosted PDF generation in CI, use the official sphinxdoc/sphinx-latexpdf Docker image — it has the full TeX stack preconfigured.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: MkDocs Not Working — Material Theme, Navigation, and Plugin Errors
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.
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.