Fixing PyQGIS Module Import Errors

HomePyQGIS Fundamentals & Environment SetupDebugging PyQGIS Scripts → Fixing Module Import Errors

ModuleNotFoundError: No module named 'qgis' and ImportError: DLL load failed while importing _core are the two errors you hit the moment you try to import qgis from anything other than the QGIS application itself. Both share one root cause: your Python interpreter is running outside QGIS's managed environment, so it never learns where the PyQGIS bindings and their compiled C++ dependencies live. This page is a focused, reproducible fix for that failure — the specific case of getting qgis.core to import from an external interpreter such as VS Code, PyCharm, or a plain terminal. Resolving it means either injecting QGIS's library paths at runtime or running your script through the QGIS-bundled Python executable.

Import failures are the first wall most people hit when they move from the QGIS Python Console to real scripts, so it is worth understanding why the import breaks rather than copying a fix blindly. Inside QGIS the environment is fully wired up for you; outside it, nothing is, and the linker searches the wrong directories.

Why import qgis fails outside QGIS, and the fixA before-and-after comparison. The same external interpreter — VS Code, PyCharm, or a terminal — feeds two paths. On the left, QGIS_PREFIX_PATH is unset, PATH has no QGIS bin, and sys.path is missing the python directory, so import qgis.core raises ModuleNotFoundError or a DLL load failure. On the right, sys.path.insert, an updated PATH, and QGIS_PREFIX_PATH are set first, so import qgis.core succeeds and QgsApplication.initQgis leaves PyQGIS ready.Same external interpreterVS Code · PyCharm · terminalBefore — environment not wiredQGIS_PREFIX_PATH — unsetPATH — no QGIS \binsys.path — missing \pythonimport qgis.coreImport failsModuleNotFoundError:No module named 'qgis'ImportError: DLL loadfailed importing _coreAfter — paths injected firstsys.path.insert(0, …/python)os.environ PATH += …/binQGIS_PREFIX_PATH = prefiximport qgis.coreImport succeedsQgsApplication.initQgis()PyQGIS ready

Prerequisites

Before applying any fix, confirm the following so you are debugging the right layer of the problem:

  • A working QGIS install. Note its exact version (Help → About) and installation type — Standalone, OSGeo4W, macOS bundle, or a Linux package. The paths below differ for each.
  • The bundled Python version. PyQGIS bindings are compiled against one specific Python ABI. QGIS 3.34 and later — including the 3.44 LTR — ship Python 3.12, while the older 3.28 LTR shipped Python 3.9. You need this number to match your external interpreter.
  • A 64-bit interpreter. Modern PyQGIS is strictly 64-bit; a 32-bit Python will never load _core.
  • The ability to inspect your environment. You should be able to print sys.executable, sys.version, and os.environ from the interpreter that is failing. Most misdiagnoses come from fixing paths in one interpreter while the IDE silently runs another. If your IDE's interpreter selection is opaque, the PyQGIS Fundamentals & Environment Setup reference explains how interpreter inheritance and site-packages resolution work.

The Fix: Manual Path Injection

When running scripts externally, inject QGIS paths before any qgis import. Import order matters — the compiled libraries are resolved against PATH and sys.path at import time, so setting these variables afterwards has no effect. Place this block at the very top of your script:

import sys
import os

# Update to match your exact QGIS installation
QGIS_PREFIX = r"C:\Program Files\QGIS 3.44\apps\qgis"  # Windows Standalone
# QGIS_PREFIX = "/Applications/QGIS.app/Contents/MacOS"  # macOS
# QGIS_PREFIX = "/usr"                                    # Linux (Debian/Ubuntu)

# Inject QGIS Python paths (order matters)
sys.path.insert(0, os.path.join(QGIS_PREFIX, "python"))
sys.path.insert(0, os.path.join(QGIS_PREFIX, "python", "plugins"))

# Set mandatory environment variables
os.environ["QGIS_PREFIX_PATH"] = QGIS_PREFIX
os.environ["PATH"] = os.path.join(QGIS_PREFIX, "bin") + os.pathsep + os.environ.get("PATH", "")

# Initialize QGIS application context
from qgis.core import QgsApplication
QgsApplication.setPrefixPath(QGIS_PREFIX, True)
qgs = QgsApplication([], False)
qgs.initQgis()
print("PyQGIS environment loaded successfully.")

Note that PYTHONHOME is intentionally omitted. Setting it in an external environment forces the interpreter to look for its own standard library inside the QGIS prefix, which frequently breaks virtual environments and triggers silent crashes. Setting QGIS_PREFIX_PATH, PATH, and sys.path is enough to load the bindings without disturbing the interpreter's own runtime.

This bootstrap is the same one used when running Python scripts outside QGIS desktop — the difference here is that we are treating it as a fix for a broken import rather than a general launch pattern, so the emphasis is on getting the three path variables exactly right for your platform.

OS-specific path differences

The one line you almost always get wrong is QGIS_PREFIX. The correct value depends on how QGIS was installed:

Install typeTypical QGIS_PREFIXNotes
Windows StandaloneC:\Program Files\QGIS 3.44\apps\qgisUse apps\qgis-ltr for an LTR build
Windows OSGeo4WC:\OSGeo4W\apps\qgis-ltrPrefer the OSGeo4W shell for wiring PATH
macOS bundle/Applications/QGIS.app/Contents/MacOSPoint at Contents/MacOS, not the .app root
Linux (package)/usrSymlinks are usually already on the path; injection is only needed inside an isolated venv

On Linux, distribution packages typically register the bindings system-wide, so import qgis works without any injection unless you have created an isolated virtual environment. In that case, create the environment with --system-site-packages so it can see the packaged bindings.

Where Python looks, and why it misses

An import error is almost always a path problem, and seeing the search order makes it obvious which of the three fixes applies.

The search path, and three ways to add QGIS to itPython searches the script directory, then PYTHONPATH entries, then the standard library, then site-packages. The QGIS python directory is in none of those by default. It can be added by setting PYTHONPATH before launch, by inserting into sys.path at runtime, or by dropping a pth file into site-packages.import qgis fails because none of these contain itsys.path, in order1 the script's own directory2 $PYTHONPATH entries3 the standard library4 site-packagesset PYTHONPATH before launchworks for every script · the portable fixsys.path.insert(0, …)works in this script only · hard-codes a patha .pth file in site-packagesworks for one environment · invisible later

Interpreter mismatch is a different failure

If the path is right and the import still fails — or fails with a binary incompatibility rather than "no module named" — the interpreter is not the one QGIS was compiled against.

Matching versus mismatched interpreterQGIS ships compiled bindings built against one specific Python version. The bundled interpreter of that version imports them successfully. A separately installed interpreter of a different version finds the files but cannot load them, producing an ImportError about a binary module rather than a missing one.The bindings are compiled — the version has to match exactlyqgis._core.sobuilt for Python 3.12the bundled python3.12imports cleanlya separate python3.10finds it, cannot load itfrom qgis.core import QgsProjectImportError: undefined symbolnot "no module named"

The wording of the error is the diagnostic: "No module named qgis" is a path problem, while an undefined symbol or an ABI complaint is a version problem — and no amount of path fixing will solve the second.

QGIS-version compatibility notes

Import errors are frequently version-mismatch errors in disguise. Keep these rules in mind:

  • Match the minor Python version. Because the bindings are compiled against a specific ABI, a 3.11 interpreter will raise ImportError against a QGIS that ships 3.12. Patch releases within the same minor version (3.12.1 vs 3.12.4) are compatible; minor versions (3.11 vs 3.12) are not.
  • QGIS 3.34+ / 3.44 LTR → Python 3.12. These are the current targets and what the code above assumes.
  • QGIS 3.28 LTR → Python 3.9. If you are pinned to this older LTR, your external interpreter must also be 3.9.
  • Architecture must match. A 64-bit QGIS needs a 64-bit interpreter; the mismatch surfaces as a failure during QgsApplication initialization rather than at import.
  • API surface drifts between releases. Even once the import succeeds, classes added in later releases will raise AttributeError on an older QGIS. Pin your example code to the LTR you actually run.

Troubleshooting

If path injection still throws ImportError: cannot import name 'QgsApplication', crashes silently, or reports a missing DLL, work through these fallbacks in order.

  1. Execute via the QGIS Python wrapper. Bypass the external interpreter entirely by calling the bundled executable, which has every path pre-configured:
    • Windows: "C:\Program Files\QGIS 3.44\bin\python-qgis.bat" your_script.py
    • macOS: /Applications/QGIS.app/Contents/MacOS/bin/python3 your_script.py
    • Linux: python3 your_script.py after sourcing the QGIS environment shell script
  2. Run inside the QGIS Python Console. Open QGIS → Plugins → Python Console and run exec(open('your_script.py').read()). The console auto-initializes QgsApplication and loads all qgis.* namespaces, which is the fastest way to confirm the problem is environmental rather than a bug in your code.
  3. Remove a conflicting PyPI package. Run pip list | grep qgis (macOS/Linux) or pip list | findstr qgis (Windows). Official PyQGIS bindings are never distributed through PyPI — the PyPI qgis package is a documentation stub that shadows the real libraries. If you see it, uninstall it immediately with pip uninstall qgis.
  4. Verify the native dependency chain. PyQGIS depends on compiled C++ libraries (GDAL, PROJ, Qt). DLL load failed while importing _core means Python found the qgis package but could not load those dependencies — usually because the QGIS bin directory is missing from PATH, or the Visual C++ Redistributables are absent on Windows. Repair the install, or run the OSGeo4W installer in repair mode, to restore native binaries.
  5. Confirm you are debugging the right interpreter. Print sys.executable at the top of the failing script. When an IDE activates an isolated virtual environment, it can strip the inherited system paths that made your terminal work, so the same code passes in one place and fails in another.

For persistent runtime crashes and silent failures that survive the import — bad arguments to the C++ layer, threading issues inside the Qt event loop — structured logging and step-through inspection are the next tools to reach for. The parent Debugging PyQGIS Scripts guide covers isolating stack traces and validating environment state before you deploy to a production pipeline.

Conclusion

PyQGIS import errors almost always reduce to one of three mismatches: the interpreter cannot find the bindings (sys.path), it cannot find their compiled dependencies (PATH / QGIS_PREFIX_PATH), or the Python and QGIS versions are incompatible. Inject the three path variables before any qgis import, match your external Python minor version to the QGIS build, and keep the PyPI qgis stub out of your environment. When manual mapping stays fragile — CI runners, locked-down machines, tangled IDE virtual environments — fall back to the bundled python-qgis wrapper or qgis_process rather than fighting PATH.

Frequently Asked Questions

Why does import qgis fail outside QGIS but work fine in the Python Console? The QGIS Python Console runs inside the application, which has already set QGIS_PREFIX_PATH, configured PATH, and added the bundled Python libraries to sys.path. An external interpreter inherits none of that, so you must inject those paths manually before importing any qgis module.

What does ImportError: DLL load failed while importing _core actually mean? It means Python found the qgis package but could not load its compiled C++ dependencies (GDAL, PROJ, Qt). On Windows this is usually a missing QGIS bin directory on PATH or absent Visual C++ Redistributables; repairing the QGIS install via the OSGeo4W installer often resolves it.

Do I need to match my external Python version to QGIS exactly? You must match the minor version, because the bindings are compiled against a specific Python ABI. QGIS 3.34 and later (including 3.44 LTR) ship Python 3.12, while QGIS 3.28 shipped Python 3.9, so a 3.11 interpreter will raise ImportError against a 3.12 QGIS. Patch versions within the same minor release are fine.

Should I install the qgis package from PyPI to fix the import? No. The PyPI qgis package is a documentation stub that shadows the real bindings and makes the problem worse. If pip list shows a qgis package in your environment, run pip uninstall qgis and rely on QGIS's bundled libraries via path injection or a --system-site-packages virtual environment.

Why is PYTHONHOME deliberately left unset in the fix? Setting PYTHONHOME forces the interpreter to look for its standard library in the QGIS prefix, which breaks virtual environments and can trigger silent crashes. Setting only QGIS_PREFIX_PATH, PATH, and sys.path is enough to load the bindings without disturbing the interpreter's own runtime.