Qt 5 to Qt 6 Migration Checklist (CMake + Qt5Compat + RHI)

Migrating from Qt 5 to Qt 6 is less a single rewrite and more a sequenced engineering project: stabilize on Qt 5.15, kill deprecated APIs, move the build to CMake (recommended), bridge leftover types with Qt5Compat, then validate graphics under the RHI stack. This checklist is written for C++/Qt developers who are mid-migration or evaluating effort—and it stands alone without buying anything.

Official starting points: the Porting to Qt 6 guide and the per-module Changes to Qt Modules in Qt 6 index. For a curated set of blog posts and tooling hints, see Qt’s references and hints about porting.

Free practice material while you work: download the Qt 6.9 book examples ZIP and skim the free English Chapter 1 — Qt class hierarchy overview (same topic also as class hierarchy overview). German readers can start with the DE tutorial on the first program and Qt modules.

Table of contents

  1. Why migrate in 2026
  2. Phase 0 — Prepare on Qt 5.15
  3. Phase 1 — qmake to CMake (Qt 6)
  4. Phase 2 — Qt5Compat bridge (then remove it)
  5. Phase 3 — QRegExp → QRegularExpression
  6. Phase 4 — RHI, OpenGL, and Qt Quick
  7. Phase 5 — High-DPI, modules, and Clazy
  8. Printable master checklist
  9. FAQ
  10. Next free steps

Why migrate in 2026

Qt 6 is the actively developed line: C++17 as baseline, CMake as the primary application build story, and a unified Rendering Hardware Interface (RHI) behind Qt Quick so the same scene can target OpenGL, Vulkan, Metal, or Direct3D depending on the platform. Qt 5.15 remains a useful staging release for deprecation cleanup, but long-term product work belongs on Qt 6.

KDAB’s migration overview makes the same practical point: treat 5.15 as the intermediate step, turn on deprecation warnings early, then plan CMake and RHI work as first-class workstreams (KDAB — Qt 5 to Qt 6 Migration).

Phase 0 — Prepare on Qt 5.15

The official porting guide is unambiguous: update the application to Qt 5.15 before jumping to Qt 6. APIs already deprecated or obsolete in 5.15 may be gone in Qt 6 (Porting to Qt 6).

Checklist — Phase 0

  • ☐ Build and run the full product on Qt 5.15 (same feature set you intend to port).
  • ☐ Treat compiler deprecation warnings as defects; fix or quarantine them.
  • ☐ Force deprecated Qt 5.15 APIs to fail the build with QT_DISABLE_DEPRECATED_UP_TO=0x050F00 (0x050F00 encodes 5.15.0).

qmake:

DEFINES += QT_DISABLE_DEPRECATED_UP_TO=0x050F00

CMake:

add_compile_definitions(QT_DISABLE_DEPRECATED_UP_TO=0x050F00)

(Source: Porting to Qt 6.)

  • ☐ Inventory modules you use; cross-check module changes and removed/restored module notes linked from the porting guide.
  • ☐ Prefer replacing QRegExp with QRegularExpression while still on Qt 5—both exist there, so you can unit-test the port before the Qt 6 jump (Qt5Compat blog).

Phase 1 — qmake to CMake (Qt 6)

You can keep qmake for many Qt 6 applications—the Qt build itself moved to CMake, but qmake remains supported for apps (Build System Changes in Qt 6). For new tooling and long-term maintenance, Qt and the community strongly recommend CMake (porting references blog).

Custom Qt plugins / libraries that relied on Qt 5 qmake internals must move to CMake for Qt 6 (same build-system page).

Minimal Qt 6 CMake shape

From the official Getting started with CMake pattern (Widgets variant):

cmake_minimum_required(VERSION 3.16)

project(MyApp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

qt_standard_project_setup()

qt_add_executable(MyApp
    main.cpp
    mainwindow.cpp
)

target_link_libraries(MyApp PRIVATE Qt6::Widgets)

Notes that matter in migration reviews:

  • Qt 6 expects C++17+.
  • qt_standard_project_setup() turns on CMAKE_AUTOMOC / CMAKE_AUTOUIC—you typically do not list headers solely for moc the way qmake did (CMake get-started).
  • Prefer qt_add_executable / qt_add_library over raw add_executable when you want Qt’s plugin linking and platform defaults.

Checklist — Phase 1

  • ☐ Create a parallel CMake build; keep the .pro until CI is green on both (or until you cut over).
  • ☐ Map QT += … → find_package(Qt6 REQUIRED COMPONENTS …) + target_link_libraries(… Qt6::…).
  • ☐ Map RESOURCES → qt_add_resources(...).
  • ☐ Map DEFINES / INCLUDEPATH → target_compile_definitions / target_include_directories.
  • ☐ Optionally bootstrap with qmake2cmake (pip install qmake2cmake), then hand-fix custom rules—the converter is a starting point, not a finished port (references blog).
  • ☐ Deep dive on site: Qt 6 build system — CMake & qmake.

Phase 2 — Qt5Compat bridge (then remove it)

When types moved out of Qt Core, the Qt 5 Core Compatibility APIs module (Qt6::Core5Compat / core5compat) keeps common Qt 5 classes available so you can compile on Qt 6 while finishing replacements (Qt5Compat blog).

Qt 5 class (compat) Prefer in new Qt 6 code
QRegExp QRegularExpression
QStringRef QStringView
QTextCodec / QTextEncoder / QTextDecoder QStringConverter / QStringEncoder / QStringDecoder
QXmlSimpleReader QXmlStreamReader
QLinkedList std::list (or QList if you do not need reference stability)

qmake:

QT += core5compat

CMake:

find_package(Qt6 REQUIRED COMPONENTS Core5Compat)
target_link_libraries(MyApp PRIVATE Qt6::Core5Compat)

Includes can stay as #include <QRegExp> or use #include <QtCore5Compat/QRegExp> (same blog; also QRegExp class docs).

Checklist — Phase 2

  • ☐ Add Core5Compat only where the linker complains—not as a permanent dumping ground.
  • ☐ Track every remaining compat type in a ticket; remove the module when the list is empty.
  • ☐ Remember: Qt5Compat is a portability bridge, not the destination architecture (also listed under add-on modules in the free class hierarchy overview).

Phase 3 — QRegExp → QRegularExpression

QRegExp is deprecated in Qt 6 and lives in Core5Compat; new code should use QRegularExpression (QRegExp documentation). The match model differs: QRegularExpression is immutable during matching and returns QRegularExpressionMatch / iterators.

High-signal porting rules (from official docs)

  • Exact match: wrap with QRegularExpression::anchoredPattern(...) instead of QRegExp::exactMatch().
  • Global match: replace indexIn loops with globalMatch() + QRegularExpressionMatchIterator.
  • Silent semantic traps: \xHHHH often needs \x{HHHH}; {,n} should become {0,n}; . does not match newline by default—use DotMatchesEverythingOption if you need Qt 5 QRegExp behavior; enable UseUnicodePropertiesOption if you relied on Unicode-aware \d / \w.
  • Wildcards: use QRegularExpression::wildcardToRegularExpression(...); pass UnanchoredWildcardConversion when you need unanchored globs.

Example — exact match:

QRegularExpression re(QRegularExpression::anchoredPattern(QStringLiteral("[a-z]+\\d+")));
if (re.match(text).hasMatch()) {
    // full-string match
}

Example — global match:

QRegularExpression re(QStringLiteral("(\\w+)"));
auto it = re.globalMatch(subject);
while (it.hasNext()) {
    const QRegularExpressionMatch m = it.next();
    // use m.captured(1) …
}

(Source patterns: Porting to QRegularExpression section on the QRegExp page.)

Checklist — Phase 3

  • ☐ Grep for QRegExp, QRegExpValidator, and related APIs.
  • ☐ Port hot paths first; keep Core5Compat only for cold leftovers.
  • ☐ Add tests for newline / Unicode / wildcard cases—these are where silent behavior changes hide.

Phase 4 — RHI, OpenGL, and Qt Quick

In Qt 6, Qt Quick’s default scene graph path is QRhi-based. There is no supported “direct OpenGL only” scene-graph path like the Qt 5 default; OpenGL remains one RHI backend among others (Graphics in Qt 6.0: QRhi, Qt Quick, Qt Quick 3D). The porting guide also warns: OpenGL is no longer guaranteed as the default on every target—test visual output (Porting to Qt 6).

Widget painting that never touched the Quick scene graph is largely unchanged; OpenGL helpers moved (for example QOpenGLWidget lives in the OpenGL Widgets module). Apps that inject OpenGL under/over Quick should pin the API explicitly, e.g. QQuickWindow::setGraphicsApi(...), so Qt Quick and your code agree (RHI blog).

Shader / material migration highlights:

  • ShaderEffect no longer takes inline GLSL strings; point vertexShader / fragmentShader at .qsb URLs produced by the qsb toolchain / qt6_add_shaders() (RHI blog).
  • Custom QSGMaterialShader follows the RHI-era API (the Qt 5.14/5.15 QSGMaterialRhiShader preview direction became the default).
  • OpenGL-specific helpers such as raw GLuint textureId() / FBO setters on Quick types were removed in favor of native-texture wrappers queryable per API.

Environment variables such as QSG_RHI_BACKEND (e.g. opengl, vulkan, metal, d3d11) remain the usual way to force a backend during testing; KDAB’s graphics guidance emphasizes identifying OpenGL-only code, converting shaders via the modern pipeline, and multi-backend QA (KDAB migration services).

Checklist — Phase 4

  • ☐ Classify graphics: Widgets-only / Quick / custom OpenGL / Qt 3D / Quick 3D.
  • ☐ Run visual regression on each shipping OS with the default RHI backend and forced OpenGL where you still depend on it.
  • ☐ Port ShaderEffect and custom materials to .qsb assets.
  • ☐ Replace removed OpenGL-only Quick APIs; use QSGRendererInterface / native texture helpers as documented.
  • ☐ For Qt 3D, know that RHI is default and a legacy OpenGL renderer may still be selectable via QT3D_RENDERER=opengl when you are not ready to port techniques (Qt 3D Render Porting to RHI).

Phase 5 — High-DPI, modules, and Clazy

High-DPI

Qt 6 enables high-DPI handling broadly and defaults the scale-factor rounding policy to PassThrough (Qt 5 behaved like Round). Widget apps can show glitches at fractional scales (e.g. 175% on Windows); restore Qt 5-like rounding if needed (Porting to Qt 6).

Modules and small API landmines

Walk the module changes index. One concrete Core example called out on the porting page: QTextStream::setCodec() → QTextStream::setEncoding().

Use the free Qt class hierarchy overview to re-orient which modules you actually need after streamlining.

Clazy Qt 6 checks

Qt documents Clazy checks dedicated to porting—run them against a Qt 5 build; the fixed code targets Qt 6 (Porting with Clazy):

  • qt6-deprecated-api-fixes
  • qt6-header-fixes
  • qt6-qhash-signature
  • qt6-fwd-fixes
  • missing-qobject-macro

Checklist — Phase 5

  • ☐ Test fractional DPI (Windows) and Retina (macOS).
  • ☐ Diff your module list against Qt 6 availability for your exact minor version (6.2 restored many gaps relative to early 6.0).
  • ☐ Run Clazy Qt 6 checks once before the final cutover.
  • ☐ Smoke-test encoding / stream / container edge cases that often slip past compile.

Printable master checklist

Copy into your ticket tracker:

  1. 5.15 baseline green; QT_DISABLE_DEPRECATED_UP_TO=0x050F00 clean.
  2. Module inventory reconciled with modulechanges.
  3. CMake builds the product; CI artifact matches qmake (if still dual-building).
  4. Core5Compat linked only for listed leftovers.
  5. QRegExp (and related) ported or explicitly deferred with tests.
  6. Quick/RHI visual QA on default + forced backends; shaders are .qsb.
  7. High-DPI policy decided and documented.
  8. Clazy Qt 6 checks applied; release candidate signed off.

FAQ

Is qmake dead in Qt 6?

No. qmake is still supported for building many Qt applications, but Qt’s own build and ongoing tooling investment center on CMake. Prefer CMake for new work and for plugins that depended on Qt 5 qmake internals (Build System Changes in Qt 6).

Must I use Qt5Compat forever?

No. Link Qt6::Core5Compat to keep compiling while you replace types such as QRegExp and QTextCodec. Plan to delete the dependency once replacements and tests are done (Qt5Compat blog).

Will my Qt Widgets app break on RHI?

Widget applications’ graphical backend is called out as largely unchanged versus Qt 5; the big RHI default shift is in Qt Quick (and related 3D stacks). Still verify High-DPI and any custom OpenGL widgets (Porting to Qt 6; RHI blog).

How do I force OpenGL for Qt Quick during migration?

Request the OpenGL RHI backend via QQuickWindow::setGraphicsApi(...) and/or QSG_RHI_BACKEND=opengl while you port custom rendering. That pins Qt Quick to the same API your OpenGL code expects (RHI blog).

Where should I start if I only have one week?

Spend the week on Phase 0 (5.15 + deprecations) and a CMake compile of the main binary with Core5Compat allowed. Defer deep Quick shader work to week two unless graphics is your release blocker—compile green first, then visual parity.

Next free steps

Work through the checklist against a small vertical slice of your app, then compare patterns with the free materials on this site:

When you want a structured, book-length path through modern Qt 6.9 (widgets, Quick, build system, and more), Max Schlee’s Qt 6.9 book is available from the same site—the free chapter and examples above are intentionally enough to start the migration without a purchase.

Leave a Reply

Your email address will not be published. Required fields are marked *