Running Python Scripts Outside QGIS Desktop

To run Python scripts outside the QGIS desktop, you must manually bootstrap the PyQGIS environment before importing any qgis modules. This requires setting QGIS_PREFIX_PATH, PATH, and PYTHONPATH to your QGIS installation directory, then calling QgsApplication.initQgis() in headless mode (False). This bypasses the GUI while retaining full access to vector/raster APIs, GDAL/OGR drivers, and the Processing framework.

For reproducible deployments, isolate these dependencies to prevent system-level conflicts. A properly configured Virtual Environments for GIS ensures your standalone interpreter resolves QGIS libraries consistently across development and production machines. The foundational mapping between Python interpreters and QGIS binaries is detailed in the PyQGIS Fundamentals & Environment Setup reference, which covers version alignment and dependency resolution.

Working Code Snippet

import os
import sys

# 1. Set QGIS installation root (adjust per OS)
QGIS_PREFIX = r"C:\OSGeo4W\apps\qgis" # Windows OSGeo4W
# macOS: "/Applications/QGIS.app/Contents/MacOS"
# Linux: "/usr"

# 2. Inject environment variables BEFORE importing qgis modules
os.environ["QGIS_PREFIX_PATH"] = QGIS_PREFIX
os.environ["PATH"] = os.pathsep.join([os.path.join(QGIS_PREFIX, "bin"), os.environ.get("PATH", "")])
os.environ["PYTHONPATH"] = os.pathsep.join([os.path.join(QGIS_PREFIX, "python"), os.environ.get("PYTHONPATH", "")])

# Prepend to sys.path (required for some installations)
sys.path.insert(0, os.path.join(QGIS_PREFIX, "python"))

from qgis.core import QgsApplication, QgsVectorLayer

# 3. Initialize headless QGIS application
QgsApplication.setPrefixPath(QGIS_PREFIX, True)
qgs = QgsApplication([], False) # False disables GUI
qgs.initQgis()

# 4. Execute GIS logic
layer = QgsVectorLayer("data/roads.shp", "roads", "ogr")
if layer.isValid():
 print(f"Loaded {layer.featureCount()} features")
else:
 print("Layer failed to load. Check path and OGR drivers.")

# 5. Clean shutdown
qgs.exitQgis()

Compatibility Requirements

ComponentRequirementFailure Symptom
Python VersionMust exactly match QGIS bundled version (e.g., 3.10 for QGIS 3.34+)ImportError: DLL load failed or ModuleNotFoundError: qgis
QGIS Major VersionScripts must target the exact installed major.minor releaseAttributeError: module 'qgis.core' has no attribute 'QgsApplication'
Path SeparatorsUse os.pathsep and os.path.join to avoid OS-specific crashesFileNotFoundError during setPrefixPath()
macOS BundlePoint QGIS_PREFIX_PATH to Contents/MacOS, not the .app rootQgsApplication initializes but fails to load providers
Linux HeadlessExport QT_QPA_PLATFORM=offscreen before executionqt.qpa.xcb: could not connect to display crash

Execution & Integration

  1. Direct CLI: python standalone_script.py
  2. Scheduled Tasks/Cron: Wrap execution in a batch/shell script that exports environment variables first, then invokes Python.
  3. Processing Framework: To use processing.run(), register native algorithms after initialization:
from qgis.analysis import QgsNativeAlgorithms
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())

Troubleshooting & Fallbacks

If initialization fails or crashes silently, apply these diagnostic steps:

  1. Verify Path Resolution: Print sys.executable and os.environ["QGIS_PREFIX_PATH"] immediately after assignment. A mismatched interpreter is the most common cause of missing qgis modules.
  2. Use qgis_process (Recommended): QGIS 3.14+ ships with a standalone CLI that auto-configures environments. Run qgis_process run --script=your_script.py to bypass manual path injection entirely. This is the most reliable fallback for CI/CD pipelines.
  3. Conda Environment Fallback: If system paths are unstable or permissions restrict PATH modification, use conda-forge: conda create -n qgis-standalone qgis python=3.10. Activate the environment and run your script. Conda automatically resolves GDAL, PROJ, Qt, and SIP bindings.
  4. Enable Debug Logging: Add QgsApplication.messageLog().logMessage("Init check", "Standalone") and run with QGIS_LOG_FILE=debug.log to capture silent provider load failures.
  5. GDAL Driver Registration: If formats fail to open, explicitly call from osgeo import gdal; gdal.UseExceptions() and verify QGIS_PREFIX_PATH includes the gdalplugins directory.

Standalone execution removes GUI overhead while preserving the full spatial engine. Always validate environment variables at runtime, match Python versions exactly, and prefer qgis_process or Conda when manual path mapping proves unstable.