Skip to content

Plugin migration to be compatible with Qt5 and Qt6

Richard Duivenvoorde edited this page Sep 11, 2025 · 19 revisions

Qt6 migration

It's possible to make a plugin for both Qt5 and Qt6.

Script

To run this script, you need QGIS with Qt6.

  1. Install Python dependencies:

    pip install astpretty tokenize-rt
  2. Install additional system dependencies. Typically on Debian based-images:

    sudo apt install python3-pyqt6 python3-pyqt6.qtsvg python3-pyqt6.qsci
  3. Download the pyqt5_to_pyqt6 script

  4. You should check that PyQt5 is not available in the Python environment, the script will work better

  5. pyqt5_to_pyqt6.py /path/to/plugin

  6. Edit the metadata.txt by adding supportsQt6=True

This will get you in the right direction but might not do all necessary changes. Make sure to test your plugin thoroughly. Consider using an IDE with inspection that can notify you about broken imports, bad usage or non-existent references.

Use OSGeo4W shell (for Windows users)

  1. Install a QGIS Qt6(!) build first, see for example "Latest Version for Windows (3.44) with Qt6 (experimental)" on https://qgis.org/download
  2. Download the script from: https://github.com/qgis/QGIS/blob/master/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py
  3. Then given you installed your QGISQT6 in C:\Program Files\QGISQT6 x.xx.x
  4. First make sure you are in the 'osgeo4w' environment by running the C:\Program Files\QGISQT6 x.xx.x\OSGeo4W.bat. From THAT command window, run the following:
# all from within the OSGeo4W command terminal!

cd "C:\Program Files\QGISQT6 x.xx.x"
".\bin\qt6_env.bat"
".\bin\qgis-qt6-bin.env"
set PYTHONPATH=C:\Program Files\QGISQT6 x.xx.x\apps\qgis-qt6\python;%PYTHONPATH%
pip install astpretty tokenize-rt
python "path/to/pyqt5_to_pyqt6.py" "path/to/plugin"

If you see: WARNING:root:QGIS classes not available for introspection, only a partial upgrade will be performed you misspelled the step with the PYTHONPATH...

If all went well the script silently ends and you will have QgsMapLayer.VectorLayer -> QgsMapLayer.LayerType.VectorLayer and QMessageBox.Ok -> QMessageBox.StandardButton.Ok which will work both in Qt5/QGIS3 and Qt6/QGIS4

Use Docker image

A bundled image with QGIS based on Qt6, including Oracle client and PDAL and the migration script is available: https://gitlab.com/Oslandia/qgis/pyqgis-4-checker. It can be used like this:

docker run --rm -v "$(pwd):/home/pyqgisdev/" registry.gitlab.com/oslandia/qgis/pyqgis-4-checker/pyqgis-qt-checker:latest pyqt5_to_pyqt6.py --logfile /home/pyqgisdev/pyqt6_checker.log .

CI

The Docker image can also be used in a CI context to ensure compatibility along the development life-cycle.

GitLab CI

Edit "{plugin_folder_path_within_git_repository}" below to point to your plugin folder relative to your Git repository root.

stages:
  - 🐍 lint

variables:
  PROJECT_FOLDER: "{plugin_folder_path_within_git_repository}"

lint:qt6:
  stage: 🐍 lint
  image: registry.gitlab.com/oslandia/qgis/pyqgis-4-checker/pyqgis-qt-checker:latest
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - "$PROJECT_FOLDER/**/*.py"
        - ".gitlab-ci.yml"
      when: on_success
    - when: never
  script:
    # just print the script help
    # - pyqt5_to_pyqt6.py --help
    # first, a dry run to get the log file
    - pyqt5_to_pyqt6.py --dry_run --logfile pyqt6_checker.log $PROJECT_FOLDER/
    # then running with edit enabled to
    - pyqt5_to_pyqt6.py $PROJECT_FOLDER/
  artifacts:
    paths:
      - pyqt6_checker.log
    when: always
    access: all
    expire_in: 3 days

pyqt6_checker.log files are available as job artifacts after the pipeline has run.

Official C++ documentation

Differences Between PyQt6 and PyQt5 (Riverbank Computing)

https://www.riverbankcomputing.com/static/Docs/PyQt6/pyqt5_differences.html "gives an overview of the differences between PyQt6 and PyQt5. This is not an exhaustive list and does not go into the detail of the differences between the Qt v6 and Qt v5 APIs."

Manual conversion

To make a code working for both versions, it's mostly about how enums are called. For instance, according to Qt Namespace :

Working only in PyQt5 Working for both PyQt5 and PyQt6
Qt.UserRole Qt.ItemDataRole.UserRole
Qt.WaitCursor Qt.CursorShape.WaitCursor
Qt.blue Qt.GlobalColor.blue
etc. etc.
Clone this wiki locally