Use QGIS Custom Widgets in Qt Designer
Every plugin dialog needs the same handful of controls: pick a layer, pick a field from that layer, choose an output file, choose a colour, choose a coordinate system. Writing them from plain Qt widgets means populating combo boxes from the project, keeping them in sync when layers are added, filtering by geometry type, and validating paths — perhaps two hundred lines that QGIS has already written and that behave exactly like the rest of the application.
This recipe belongs to Qt Designer for GIS Interfaces. It covers the widgets worth knowing, how to place them in a .ui file through promotion, how to use them directly in code, and how to wire the dependent ones together.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- Qt Designer, from your QGIS installation or your package manager.
- A plugin dialog to work on — see Load a .ui File at Runtime in PyQGIS.
The widgets worth knowing
QgsMapLayerComboBox— every layer in the project, filterable by type and geometry, kept in sync automatically.QgsFieldComboBox— the fields of a layer, filterable to numeric, string or date fields.QgsFieldExpressionWidget— a field picker that also accepts an expression, with the expression builder attached.QgsFileWidget— a path field with a browse button, in file, save, directory or multi-file mode.QgsColorButton— a colour swatch opening QGIS's colour dialog, with the project colour scheme.QgsExtentGroupBox— an extent, from a layer, the canvas, or typed coordinates.QgsProjectionSelectionWidget— a coordinate system picker with recent and project options.QgsSpinBoxandQgsDoubleSpinBox— spin boxes that support a clearable "not set" state.QgsCollapsibleGroupBox— a group box that remembers whether the user collapsed it.
Each behaves the way its counterpart in QGIS's own dialogs behaves, which means users already know how to use them.
Promote a widget in Qt Designer
QGIS's widgets are not in Designer's palette unless the custom widget plugin is installed, and the portable way to place them is promotion:
- Drop the closest standard widget onto the form — a
QComboBoxfor a layer picker, aQWidgetfor anything without an obvious base. - Right-click it and choose Promote to….
- Set the promoted class name to
QgsMapLayerComboBoxand the header file toqgis.gui. - Press Add, then Promote.
Breakdown: Promotion records "treat this widget as that class" in the .ui file, and the header value qgis.gui is what tells the Python loader which module to import — it is a Python module path here rather than a C++ header, which is the detail that trips people up. Once promoted, the widget is a plain QComboBox in Designer's preview and the real QGIS widget at run time. The base class matters: promote a QComboBox for the combo widgets and a QWidget for the file, colour, extent and projection widgets, or the loader raises about incompatible types.
Or build them in code
For a dialog assembled in Python, skip Designer entirely:
from qgis.core import QgsMapLayerProxyModel, QgsFieldProxyModel
from qgis.gui import (QgsMapLayerComboBox, QgsFieldComboBox,
QgsFileWidget, QgsProjectionSelectionWidget)
self.layer_box = QgsMapLayerComboBox()
self.layer_box.setFilters(QgsMapLayerProxyModel.PolygonLayer)
self.layer_box.setAllowEmptyLayer(True)
self.field_box = QgsFieldComboBox()
self.field_box.setFilters(QgsFieldProxyModel.Numeric)
self.output = QgsFileWidget()
self.output.setStorageMode(QgsFileWidget.SaveFile)
self.output.setFilter("GeoPackage (*.gpkg)")
self.crs_box = QgsProjectionSelectionWidget()
self.crs_box.setOptionVisible(QgsProjectionSelectionWidget.CurrentCrs, True)
Breakdown: setFilters() on the layer box takes proxy-model flags that can be combined — PolygonLayer | LineLayer for a dialog accepting either — and it filters live, so a raster added later never appears. setAllowEmptyLayer(True) adds a blank entry, which is the correct way to express an optional input rather than a separate checkbox. The field box's filters are a different enumeration with the same shape, which is easy to mix up: QgsFieldProxyModel for fields, QgsMapLayerProxyModel for layers. The projection widget's options control which shortcuts appear — project CRS, layer CRS, recently used — and showing the relevant ones saves the user a trip through the full selector.
Wire the dependent ones together
A field picker is only useful when it follows the layer picker.
self.layer_box.layerChanged.connect(self.field_box.setLayer)
self.field_box.setLayer(self.layer_box.currentLayer())
Breakdown: Two lines: connect the signal so future changes propagate, and call it once so the initial state is correct — omitting the second is why a dialog opens with an empty field list until the user touches the layer box. QgsFieldExpressionWidget uses the same pattern with the same method name, so swapping one for the other is a one-word change. For a dialog with several dependent widgets, connect them all in one place immediately after construction, which keeps the wiring visible rather than scattered through the class.
Read the values back
layer = self.layer_box.currentLayer() # a QgsMapLayer or None
field = self.field_box.currentField() # a field name, or an empty string
path = self.output.filePath() # a path, or an empty string
crs = self.crs_box.crs() # a QgsCoordinateReferenceSystem
expression = self.expression_widget.currentField() # name or expression text
if layer is None:
self.iface.messageBar().pushWarning("Parcel Tools", self.tr("Select a layer"))
return
Breakdown: Each widget returns a real object rather than a string you have to look up — currentLayer() gives the layer itself, which removes the lookup-by-name step and with it the ambiguity when two layers share a name. Empty values are empty strings and None, never exceptions, so validation is a plain check. QgsFieldExpressionWidget returns either a field name or an expression, and its companion isValidExpression() tells you which — worth checking before passing the value somewhere that expects one or the other.
QGIS version compatibility
| QGIS version | Python | Notes |
|---|---|---|
| 3.22 LTR | 3.9 | All widgets listed are present with these methods. |
| 3.28 LTR | 3.9 | Identical. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40 / 3.44 | 3.12 | Adds further widgets, including point cloud pickers; the filter enumerations gained scoped names alongside the legacy ones. |
Promotion in .ui files is stable across all of these, since it records only a class name and a module path.
Troubleshooting
TypeErrorwhen loading the .ui file. The promoted class does not match the base widget — promote aQComboBoxfor combo widgets and aQWidgetfor the rest.- The widget appears as a plain combo box at run time. The header was left as a C++ header instead of
qgis.gui. - The layer list is empty. Filters exclude everything, or the dialog was built before the project loaded. Check
setFilters()first. - The field list stays empty.
setLayer()was connected but never called once at construction. - A colour button shows the wrong colour. It was set before the dialog was shown; set values after construction, not during it.
- The file widget accepts a folder for a file. Storage mode was left at its default. Set it explicitly.
Conclusion
Use the widgets QGIS ships: they know about the project, filter themselves, stay in sync, and look like the rest of the application. Place them in Designer by promoting a compatible base widget with qgis.gui as the header, or construct them directly in Python. Wire the dependent ones with one signal connection plus one priming call, and read values back as real objects rather than names.
Frequently Asked Questions
Why are the QGIS widgets missing from Designer's palette? The custom widgets plugin is not installed or not found. Promotion works without it and is more portable, which is why it is the recommended route.
Can I use these widgets outside a plugin? Yes, in any Qt application that has the QGIS libraries available — they are ordinary widgets. Most need a QGIS application initialised to be useful.
How do I restrict the layer box to one specific layer type and geometry?
Combine proxy-model flags with the bitwise or: PolygonLayer | NoGeometry for polygons and attribute-only tables, for example.
Is there a widget for choosing a symbol or a colour ramp?
Yes — QgsSymbolButton and QgsColorRampButton, both opening QGIS's own editors, which pairs well with Programmatic Layer Styling in PyQGIS.
Do these widgets handle translation?
Their own labels and dialogs follow the QGIS interface language automatically. Your labels around them need tr() as usual.
Should a Processing algorithm use these? No — algorithms declare parameters and the framework builds the interface. These widgets are for a plugin's own dialogs.
What happens to a layer combo box when the user removes the selected layer?
It drops the entry and emits layerChanged with the new selection, which may be None. Handle that case in the connected slot rather than assuming the value is always a layer — this is the most common crash in dialogs that stay open while the user works.
Can I preselect a layer when the dialog opens?
Yes: setLayer(layer) on the combo box, typically with iface.activeLayer() if it passes your filters. Preselecting the layer the user already has highlighted removes one interaction from every run, and falls back gracefully when the active layer is the wrong type.