When you're building Python GUI applications with PyQt5 and Qt Designer, you'll eventually reach a point where the built-in widgets aren't enough. Maybe you've created a custom plotting widget, a stylized button, or a specialized input control in Python, and you want to place it into your Qt Designer layouts alongside all the standard widgets.
The good news is that Qt Designer supports exactly this through a feature called widget promotion. In this tutorial, you'll learn how to take any custom Python widget and integrate it into your Qt Designer .ui files, so you can position and size it visually just like any built-in widget.
There's one important thing to understand up front: Qt Designer itself is a C++ application—it can't run your Python code. That means you won't see your custom widget rendered in the Designer preview. Instead, you'll see a placeholder (the base widget type you promoted from). Once you load the .ui file in your running Python application, your custom widget appears in all its glory.
What is Widget Promotion?
Widget promotion is Qt Designer's way of letting you swap a standard widget for a custom one. You start by placing a regular widget on your form—say, a plain QWidget—and then tell Qt Designer: "When this UI is actually used, replace this placeholder with my custom widget class instead."
Behind the scenes, this adds some extra information to the .ui file. When you load that file in Python using uic.loadUi() or compile it with pyuic5, the loader knows to import your custom class and use it in place of the base widget.
Creating a Custom Widget
Before we get into Qt Designer, let's create a simple custom widget in Python. We'll make a basic colored widget that draws a gradient background—something you'd never get from a standard widget.
Create a new file called custom_widgets.py:
from PyQt5.QtWidgets import QWidget
from PyQt5.QtGui import QPainter, QLinearGradient, QColor
from PyQt5.QtCore import Qt
class GradientWidget(QWidget):
"""A custom widget that displays a gradient background."""
def __init__(self, parent=None):
super().__init__(parent)
def paintEvent(self, event):
painter = QPainter(self)
gradient = QLinearGradient(0, 0, self.width(), self.height())
gradient.setColorAt(0.0, QColor("#2c3e50"))
gradient.setColorAt(1.0, QColor("#3498db"))
painter.fillRect(self.rect(), gradient)
painter.end()
This widget overrides paintEvent to draw a diagonal gradient from dark blue to lighter blue. It's a straightforward example, but the same promotion process works for any custom widget—complex plotting canvases, custom controls, or anything else you build by subclassing a Qt widget.
Setting Up Your Project Structure
For widget promotion to work, the Python file containing your custom widget needs to be importable when your application runs. The simplest way to achieve this is to keep everything in the same directory:
my_project/
├── custom_widgets.py # Your custom widget classes
├── mainwindow.ui # Your Qt Designer file
└── main.py # Your application entry point
The file name and class name matter here—you'll need to tell Qt Designer both of these during the promotion step.
Promoting a Widget in Qt Designer
Now we can open Qt Designer and set up the promotion.
Place a base widget on your form
Open Qt Designer and create a new Main Window (or open your existing .ui file). From the widget box on the left, drag a plain Widget (QWidget) onto your form. Position and resize it however you like—this is where your custom widget will appear when the application runs.
You can use any base widget class as your starting point. If your custom widget subclasses QPushButton, promote a QPushButton. If it subclasses QLabel, promote a QLabel. For our GradientWidget, which subclasses QWidget, a plain QWidget is the right choice.
Open the Promote Widgets dialog
Right-click on the widget you just placed. In the context menu, select Promote to.... This opens the Promoted Widgets dialog.

Fill in the promotion details
In the dialog, you'll see fields for three pieces of information:
-
Base class name — This should already be filled in with the type of widget you right-clicked on (e.g.,
QWidget). Leave this as is. -
Promoted class name — Enter the name of your custom Python class. For our example, type
GradientWidget. -
Header file — This is where Qt Designer's C++ heritage shows through. In C++, this would be a header file path. For Python, you enter the module import path for your widget, without the
.pyextension. Since our class lives incustom_widgets.py, typecustom_widgets.

Leave the Global include checkbox unchecked.
Add and promote
Click Add to add your class to the list of known promoted widgets. Then, with your class selected in the list, click Promote. The dialog closes, and you'll notice the widget's class name in the Object Inspector (top-right panel) now shows GradientWidget instead of QWidget.
That's it for the Designer side. Save your .ui file.
Promoting additional widgets
Once you've added a promoted class through this dialog, it becomes available for reuse. The next time you want to promote a widget to GradientWidget, just right-click the widget and you'll see it listed directly in the Promote to submenu—no need to open the full dialog again.
Loading the UI in Python
Now let's write the Python code to load the .ui file and see our custom widget in action. Create main.py:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("mainwindow.ui", self)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
When you run this, uic.loadUi() reads the .ui file and sees that one of the widgets has been promoted to GradientWidget from the custom_widgets module. It automatically does the equivalent of:
from custom_widgets import GradientWidget
...and creates an instance of GradientWidget wherever you placed that promoted widget in your layout. Instead of a blank QWidget, you'll see your gradient background.
Using Compiled UI Files
If you prefer to compile your .ui files to Python using pyuic5 rather than loading them at runtime, promotion works the same way. Run:
pyuic5 mainwindow.ui -o ui_mainwindow.py
If you open the generated ui_mainwindow.py, you'll find an import line near the bottom:
from custom_widgets import GradientWidget
The compiled code creates your GradientWidget instance in the right place automatically. You can then use the generated file in your application:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Both approaches—runtime loading and compiled files—handle promoted widgets in the same way.
A More Practical Example: Embedding PyQtGraph
One of the most common reasons to promote widgets is to embed third-party plotting libraries like PyQtGraph into your Designer layouts. PyQtGraph's PlotWidget is a subclass of QGraphicsView, so you'd promote a QGraphicsView in Designer.
Here's how you'd fill in the promotion dialog for PyQtGraph:
- Base class name:
QGraphicsView - Promoted class name:
PlotWidget - Header file:
pyqtgraph
That's all it takes. When your application runs, the placeholder QGraphicsView becomes a fully functional PlotWidget that you can plot data on.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("mainwindow.ui", self)
# self.graphWidget is the promoted PlotWidget
# (use the objectName you set in Designer)
self.graphWidget.plot([1, 2, 3, 4, 5], [10, 20, 15, 30, 25])
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Promoting Widgets from Submodules
If your custom widget lives in a submodule or package, you can use dotted import paths in the Header file field. For example, if your project structure looks like this:
my_project/
├── widgets/
│ ├── __init__.py
│ └── gradient.py # contains GradientWidget
├── mainwindow.ui
└── main.py
You would enter widgets.gradient as the header file in the promotion dialog. The loader will then do:
from widgets.gradient import GradientWidget
This keeps things organized as your project grows.
Troubleshooting Common Issues
"No module named 'custom_widgets'" — This means Python can't find the file containing your custom widget class. Make sure the module file is in the same directory as your script (or somewhere on your Python path), and that the name in the promotion dialog matches the file name exactly (without .py).
The widget appears blank or as a plain QWidget — Double-check that the promoted class name matches your Python class name exactly, including capitalization. GradientWidget and gradientwidget are different classes as far as Python is concerned.
The widget doesn't resize properly — Make sure you've added the promoted widget to a layout in Qt Designer. Widgets outside of layouts won't resize with the window, regardless of whether they're promoted or not.
Changes to your custom widget don't appear in Designer — Remember, Qt Designer can't render Python widgets. You'll always see the base widget type in the Designer preview. Run your application to see your custom widget.
Summary
Widget promotion is a straightforward way to bridge the gap between Qt Designer's visual layout tools and your custom Python widgets. The process is always the same:
- Place a base widget of the appropriate type in Qt Designer.
- Right-click and promote it, specifying your custom class name and module path.
- Save the
.uifile and load it in your Python application.
Your custom widget won't be visible in the Designer preview—that's expected. But when your application runs, the promoted widget is swapped in seamlessly, giving you the best of both worlds: visual layout design with the full power of custom Python widgets.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick
(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!