QGIS Python Version Compatibility Guide
QGIS ships with a strictly version-locked Python interpreter. The bundled Python minor version changes with each major release, and you cannot safely swap it for a system Python, a conda environment, or a standalone installer without breaking the compiled qgis.core and qgis.gui bindings. If you have ever seen ImportError: DLL load failed or ModuleNotFoundError: qgis right after installing a package, you have hit this wall. This guide explains why the lock exists, how to read the compatibility matrix, and the exact steps to install dependencies and recover from an ABI mismatch — always matching external packages to the precise minor version QGIS provides.
Rule of thumb: Never assume a Python wheel built for your operating system's system Python will work inside QGIS. Verify that the ABI tag (cp39, cp310, cp312) matches the QGIS runtime before installing any C-extension package.
Prerequisites
Before working through the recipes below, make sure you have:
- A working QGIS install — this guide covers the 3.16 through 4.0 range. If PyQGIS is not yet importable on your machine, start with How to Install QGIS Python Bindings on Windows.
- Access to the QGIS Python Console (
Plugins > Python Console), or the bundled shell launcher (python-qgis-ltr.baton Windows). - A basic grasp of the binding layer — the QGIS API Architecture that generates
qgis._corefrom C++ via SIP. That article explains the compiled layer this whole compatibility story rests on. - Knowing where your QGIS user profile lives, because ABI recovery involves clearing manually copied packages from it.
Why Version Locking Matters
The QGIS API relies on SIP-generated Python bindings compiled against a specific Python ABI and a specific Qt/PyQt build. The Application Binary Interface (ABI) is the low-level contract — struct layouts, symbol names, reference-counting behaviour — between compiled qgis._core objects and the CPython interpreter that loads them. When Python's minor version changes (say 3.9 to 3.12), that contract changes too, and the pre-compiled C-extensions inside qgis._core become binary-incompatible with the interpreter trying to import them.
That is why mixing an external Python installation with QGIS produces errors like ImportError: DLL load failed (Windows) or ImportError: cannot import name 'QgsApplication' (any OS). The Python source looks identical; the binary does not line up. Treat the bundled interpreter as an immovable part of the runtime rather than a component you can upgrade independently.
Verify Your Active Environment
The first concrete step is always to confirm which interpreter is actually running. Paste this into the QGIS Python Console to print the alignment between the interpreter, the QGIS release, and the on-disk binding module:
import sys
import qgis.core
print(f"QGIS Python: {sys.version_info.major}.{sys.version_info.minor}")
print(f"QGIS version: {qgis.core.Qgis.QGIS_VERSION}")
print(f"QGIS Core Path: {qgis.core.__file__}")
if sys.version_info < (3, 9):
raise RuntimeError("Python < 3.9 is incompatible with QGIS 3.16+")
The minor version reported here — 3.9, 3.10, or 3.12 — is the number every C-extension you install must target. Note it down before touching pip.
Safe Dependency Installation
Once you know the runtime's Python version, add third-party libraries without breaking bindings by treating QGIS as a self-contained runtime, following the broader PyQGIS Fundamentals & Environment Setup workflow.
- Use the bundled
pip. QGIS 3.22+ includespipin the console. Always targetsys.executableso the package lands in the QGIS interpreter'ssite-packages, not a system one:import subprocess, sys subprocess.check_call([sys.executable, "-m", "pip", "install", "requests"]) - Windows: Run external scripts via
python-qgis.batorpython-qgis-ltr.bat(in the QGISbinfolder). This launcher setsPYTHONHOME,PYTHONPATH, andQT_PLUGIN_PATHautomatically, so the correct interpreter and Qt plugins are resolved for you. - Linux/macOS: Invoke scripts using the QGIS-bundled Python executable directly. Never manually export
PYTHONPATHto system Python directories when targeting the QGIS runtime.
If you need a fully external process — for heavy data crunching that never imports qgis — drive it through a separate interpreter as described in Running Python Scripts Outside QGIS Desktop.
The release train and what it locks
Each QGIS release ships with one Python version and one Qt version, and those choices propagate to everything you can install alongside them.
Which Python you are actually running
Three interpreters commonly exist on one machine, and a script that works in the Console and fails in a terminal is nearly always running under a different one.
QGIS-Version Compatibility Notes
Use this matrix to map any QGIS release to its bundled Python and the ABI tag your wheels must carry. Because the interpreter is shared across a release range, the table groups releases by the Python they ship.
| QGIS Release Range | Bundled Python | ABI Tag | Compatibility Notes |
|---|---|---|---|
| 3.16–3.22 | 3.9.x | cp39 | Legacy LTR. Requires pip install --ignore-installed for reinstalls. |
| 3.24–3.28 | 3.9.x | cp39 | Stable series. C-extensions must target the 3.9 ABI. |
| 3.30–3.32 | 3.10.x | cp310 | Transition builds. macOS Homebrew may ship mismatched wheels. |
| 3.34–3.36 | 3.12.x | cp312 | Requires updated C-extensions. PyQt5 remains the default. |
| 3.38–3.44 | 3.12.x | cp312 | Includes the 3.40 and 3.44 LTRs — the final 3.x (Qt5/PyQt5) series. |
| 4.0+ | 3.12.x+ | cp312+ | Qt6/PyQt6 generation. Confirm with sys.version inside the QGIS Console; release notes may revise this. |
Two rules follow directly from the table:
- Pin your code to an LTR. The 3.34 and 3.44 LTRs both bundle Python 3.12, so examples pinned to
cp312remain valid across that whole span. Pure-Python packages with no compiled code are generally safe across minor versions; only C-extensions care about the ABI tag. - A minor-version bump is a breaking change. Moving from a 3.9 release to a 3.12 release invalidates every previously installed C-extension. Plan the recompile before you upgrade, not after.
Troubleshooting: Recover from ABI Mismatches
If you encounter ImportError: cannot import name 'QgsApplication', DLL load failed, or silent crashes after installing packages, work through these steps in order:
- Clear corrupted packages. Delete manually copied folders in the QGIS user profile:
- Windows:
%APPDATA%\QGIS\QGIS3\profiles\default\python\ - Linux/macOS:
~/.local/share/QGIS/QGIS3/profiles/default/python/
- Windows:
- Force a reinstall via the bundled pip. From the Python Console:
import subprocess, sys subprocess.check_call([sys.executable, "-m", "pip", "install", "--force-reinstall", "package_name"]) - Use OSGeo4W for geospatial wheels (Windows). For GDAL, Fiona, or Shapely, install via the OSGeo4W installer. It compiles binaries against the exact Python version and MSVC runtime QGIS expects, preventing C-extension segfaults that generic PyPI wheels can cause.
- Avoid
venvfor QGIS plugins. CopyingqgisandPyQt5into a virtual environment only works temporarily and breaks on the next update. Reserve external environments for pure data processing (pandas, rasterio) that runs outside the QGIS process.
Deployment checklist
- Match your plugin's
metadata.txtqgisMinimumVersionto the QGIS version you tested against. - Never hardcode
sys.path. Useos.environ["PYTHONPATH"]only when spawning external processes. - Audit C-extensions before upgrading QGIS. A Python minor-version bump (e.g. 3.9 → 3.12) requires recompiling or sourcing new ABI-compatible wheels for every C-extension dependency.
- Keep
qgis.core,qgis.gui, andqgis.analysisimports strictly inside the bundled runtime.
Conclusion
QGIS compatibility comes down to a single chain: the QGIS release fixes the bundled Python minor version, that minor version fixes the ABI tag, and the ABI tag decides which wheels load. Confirm the interpreter with sys.version_info in the console, install every C-extension against the matching cp39/cp310/cp312 tag using the bundled pip, and never try to graft a system or conda Python onto the runtime. When you do upgrade across a minor-version boundary, treat it as a breaking change and recompile your compiled dependencies. Follow those rules and the version-lock stops being a source of cryptic import errors and becomes a predictable constraint you design around.
Frequently Asked Questions
Which Python version does QGIS 3.34 LTR ship with?
QGIS 3.34 ships with Python 3.12.x, and that interpreter is shared across the whole 3.34–3.36 range. Any C-extension you install for use inside QGIS must therefore target the cp312 ABI. Run sys.version_info in the QGIS Python Console to confirm the exact minor version on your install.
Can I upgrade the Python interpreter that QGIS uses?
No. The bundled Python is version-locked because the SIP-generated qgis._core and qgis._gui bindings are compiled against one specific Python ABI and Qt build. Swapping in a system Python, conda environment, or newer installer breaks those bindings and produces ImportError: DLL load failed or ModuleNotFoundError: qgis. Treat QGIS as a self-contained runtime instead.
How do I check whether a wheel is compatible with my QGIS install?
Match the wheel's ABI tag to the QGIS Python minor version: cp39 for QGIS 3.16–3.28, cp310 for 3.30–3.32, and cp312 for 3.34 and later. A wheel built for your operating system's system Python will not necessarily match, so verify the tag before installing any C-extension package. Pure-Python packages with no compiled code are generally safe across minor versions.
Why do I get an ABI mismatch after upgrading QGIS?
A QGIS upgrade that bumps the Python minor version (for example 3.9 to 3.12) invalidates every previously installed C-extension, because those binaries were compiled against the old ABI. Reinstall the affected packages with pip install --force-reinstall from the bundled pip, or source new ABI-compatible wheels. Pure data-processing libraries running outside the QGIS process in their own environment are unaffected.
Should I use a virtual environment for QGIS plugin dependencies?
Not for code that imports qgis or PyQt5. Copying those bindings into a venv only works temporarily and breaks on the next QGIS update. Reserve virtual environments for pure data-processing dependencies such as pandas or rasterio that run outside the QGIS process, and let QGIS manage its own bundled packages.
Related
- Up: QGIS API Architecture Explained — the parent topic covering how PyQGIS bindings are generated.
- How to Install QGIS Python Bindings on Windows
- Running Python Scripts Outside QGIS Desktop
- PyQGIS Fundamentals & Environment Setup — the top-level overview for the whole setup workflow.