Use File and CRS Selection Widgets in PyQGIS
Almost every plugin dialog asks for the same few things: an input file, an output folder, a coordinate reference system, an extent, a field. Plain Qt widgets can do each — a line edit plus a browse button plus a QFileDialog — but QGIS ships specialised widgets that already behave the way QGIS users expect: CRS selection with search and recently used systems, file pickers that remember folders and filter by GIS formats, extent boxes that take the canvas or a layer's extent. Using them makes dialogs smaller, more consistent and less buggy.
This recipe belongs to Qt Designer for GIS Interfaces. It sets up file and folder pickers, CRS selection, extent input and field selection, wires them together, uses them in Qt Designer, and reads their values safely.
Prerequisites
- QGIS 3.34 LTR or newer, or the QGIS 4 series.
- A plugin dialog, created in code or Qt Designer, as in loading a .ui file at runtime.
Pick files and folders with QgsFileWidget
QgsFileWidget combines a line edit, a browse button and drag-and-drop. Its storage mode decides whether it picks an existing file, a file to save, or a folder.
from qgis.gui import QgsFileWidget
from qgis.PyQt.QtWidgets import QDialog, QFormLayout
dlg = QDialog()
form = QFormLayout(dlg)
input_file = QgsFileWidget()
input_file.setStorageMode(QgsFileWidget.GetFile)
input_file.setFilter("GeoPackage (*.gpkg);;Shapefile (*.shp);;All files (*)")
input_file.setDialogTitle("Choose the input layer")
input_file.setDefaultRoot("/data/inbox")
output_dir = QgsFileWidget()
output_dir.setStorageMode(QgsFileWidget.GetDirectory)
report = QgsFileWidget()
report.setStorageMode(QgsFileWidget.SaveFile)
report.setFilter("PDF (*.pdf)")
report.setConfirmOverwrite(True)
form.addRow("Input", input_file)
form.addRow("Output folder", output_dir)
form.addRow("Report", report)
input_file.fileChanged.connect(lambda path: print("input:", path))
Breakdown: GetFile, GetMultipleFiles, GetDirectory and SaveFile cover the cases; for saving, setConfirmOverwrite asks before replacing an existing file. Filters use the familiar Qt syntax and appear in the browse dialog; QGIS can also generate a filter of every supported vector format with QgsProviderRegistry.instance().fileVectorFilters(). setDefaultRoot sets the starting folder; the widget also remembers the last folder used. fileChanged fires whether the user browses, types or drops a file, so one handler covers all input methods.
Pick a CRS with QgsProjectionSelectionWidget
CRS choice is where hand-built dialogs are weakest. The projection selection widget offers the project CRS, layer CRS, recently used systems and a full searchable dialog.
from qgis.gui import QgsProjectionSelectionWidget
from qgis.core import QgsCoordinateReferenceSystem, QgsProject
crs_widget = QgsProjectionSelectionWidget()
crs_widget.setCrs(QgsProject.instance().crs())
crs_widget.setOptionVisible(QgsProjectionSelectionWidget.ProjectCrs, True)
crs_widget.setOptionVisible(QgsProjectionSelectionWidget.CrsNotSet, False)
crs_widget.setLayerCrs(QgsCoordinateReferenceSystem("EPSG:25832"))
crs_widget.crsChanged.connect(lambda crs: print("target CRS:", crs.authid()))
form.addRow("Target CRS", crs_widget)
Breakdown: Starting with the project CRS matches what users usually want. Hiding "not set" prevents an invalid choice when a CRS is mandatory; keep it visible when "no change" is a valid answer. setLayerCrs adds the input layer's CRS as a quick option — update it when the input changes. crs() returns a QgsCoordinateReferenceSystem, so no parsing of strings is needed. For geographic versus projected checks before analysis, see choosing a projected CRS for analysis.
Pick an extent with QgsExtentGroupBox
Clip, export and download dialogs need an area. The extent group box lets users take the current canvas extent, a layer's extent, draw on the map, or type coordinates — and reports the extent with its CRS.
from qgis.gui import QgsExtentGroupBox
from qgis.utils import iface
extent_box = QgsExtentGroupBox()
extent_box.setMapCanvas(iface.mapCanvas())
extent_box.setOriginalExtent(iface.mapCanvas().extent(), iface.mapCanvas().mapSettings().destinationCrs())
extent_box.setCurrentExtent(iface.mapCanvas().extent(), iface.mapCanvas().mapSettings().destinationCrs())
extent_box.setOutputCrs(crs_widget.crs())
extent_box.setCheckable(True)
extent_box.setChecked(False)
form.addRow(extent_box)
crs_widget.crsChanged.connect(extent_box.setOutputCrs)
Breakdown: Giving the box the map canvas enables "draw on canvas" and "map canvas extent" buttons. The output CRS is the CRS in which outputExtent() returns coordinates — linking it to the CRS widget keeps the two consistent when the user changes the target CRS. A checkable group box makes the extent optional: unchecked means "whole layer". Read extent_box.isChecked() before using the extent.
Pick a field and link widgets together
Dialogs are easier to use when widgets react to each other: choosing a layer fills the field list, choosing an input file sets a sensible output name.
from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox
from qgis.core import QgsMapLayerProxyModel, QgsFieldProxyModel
from pathlib import Path
layer_combo = QgsMapLayerComboBox()
layer_combo.setFilters(QgsMapLayerProxyModel.PolygonLayer)
field_combo = QgsFieldComboBox()
field_combo.setFilters(QgsFieldProxyModel.Numeric)
field_combo.setAllowEmptyFieldName(False)
def on_layer(layer):
field_combo.setLayer(layer)
if layer is not None:
crs_widget.setLayerCrs(layer.crs())
layer_combo.layerChanged.connect(on_layer)
on_layer(layer_combo.currentLayer())
def suggest_output(path):
if path and not report.filePath():
report.setFilePath(str(Path(path).with_name(Path(path).stem + "_report.pdf")))
input_file.fileChanged.connect(suggest_output)
form.insertRow(0, "Layer", layer_combo)
form.insertRow(1, "Value field", field_combo)
Breakdown: Filtering the layer combo to polygon layers and the field combo to numeric fields means users can only choose valid inputs, which removes a whole class of validation code. Calling on_layer once at setup initialises the field list for the preselected layer. Suggesting an output name from the input saves typing but never overwrites a name the user already entered. Using a QgsMapLayerComboBox in a plugin dialog covers layer combo options in more depth.
Other QGIS widgets worth knowing
The same family includes widgets for most other values a GIS dialog asks for. Each replaces a plain Qt widget with one that understands QGIS conventions.
from qgis.gui import (QgsDoubleSpinBox, QgsColorButton, QgsScaleWidget,
QgsFieldExpressionWidget, QgsDateTimeEdit)
from qgis.PyQt.QtGui import QColor
distance = QgsDoubleSpinBox()
distance.setRange(0, 10000)
distance.setSuffix(" m")
distance.setClearValue(100) # reset button returns to 100
colour = QgsColorButton()
colour.setColor(QColor("#2563eb"))
colour.setAllowOpacity(True)
scale = QgsScaleWidget()
scale.setMapCanvas(iface.mapCanvas()) # "current scale" button
expr = QgsFieldExpressionWidget()
expr.setLayer(layer_combo.currentLayer())
layer_combo.layerChanged.connect(expr.setLayer)
for label, w in (("Buffer distance", distance), ("Highlight colour", colour),
("Export scale", scale), ("Filter", expr)):
form.addRow(label, w)
Breakdown: QgsDoubleSpinBox adds a clear button that resets to a defined value — handy for "back to default". QgsColorButton opens QGIS's colour picker with palettes, recent colours and opacity. QgsScaleWidget accepts scales in the familiar 1:x form and can take the current canvas scale. QgsFieldExpressionWidget lets users pick a field or write an expression with the full expression builder, linked to the chosen layer — far more flexible than a field combo where users might want "area / 10000" instead of a column. All of them can be placed in Qt Designer like the widgets above.
Use the widgets in Qt Designer
All these widgets are available in Qt Designer's QGIS widget palette when Designer is started with QGIS's plugin path; in the .ui file they appear with their QGIS class names and are created automatically when the form loads.
from qgis.PyQt import uic
import os
FORM, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), "export_dialog.ui"))
class ExportDialog(QDialog, FORM):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.mOutputFolder.setStorageMode(QgsFileWidget.GetDirectory)
self.mLayer.layerChanged.connect(self.mField.setLayer)
self.mField.setLayer(self.mLayer.currentLayer())
def values(self):
return {"layer": self.mLayer.currentLayer(), "field": self.mField.currentField(),
"crs": self.mCrs.crs(), "folder": self.mOutputFolder.filePath()}
Breakdown: Widgets placed in Designer are configured in code after setupUi for anything Designer does not expose, such as storage modes and signal links. A values() method gathering everything into a dictionary keeps the rest of the plugin independent of widget names. Validate the dictionary before running — empty paths, invalid CRSs — as in validating plugin dialog input. Setting up Designer with QGIS widgets is covered in using QGIS custom widgets in Qt Designer.
QGIS version compatibility
QgsFileWidget, QgsProjectionSelectionWidget, QgsExtentGroupBox, QgsMapLayerComboBox and QgsFieldComboBox work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. On QGIS 4, filter and mode enums are scoped — QgsFileWidget.StorageMode.GetFile, Qgis.LayerFilter.PolygonLayer, QgsFieldProxyModel.Filter.Numeric — and QgsMapLayerProxyModel filters moved to Qgis.LayerFilter in 3.34.
Troubleshooting
- The field list stays empty. The field combo was never given a layer; connect
layerChangedand initialise once. - The extent is in the wrong CRS. The extent box's output CRS was not set or not updated with the CRS widget.
- The file widget returns an empty string. The user typed nothing; validate before running.
- QGIS widgets are missing in Designer. Designer was not started with QGIS's widget plugin path.
Conclusion
Use QgsFileWidget with the right storage mode and filters, QgsProjectionSelectionWidget with sensible quick options, QgsExtentGroupBox linked to the canvas and output CRS, and filtered layer and field combo boxes linked by signals; configure Designer-placed widgets after setupUi, gather values in one method and validate them before running.
Frequently Asked Questions
Can QgsFileWidget pick multiple files?
Yes — GetMultipleFiles mode, read with splitFilePaths(filePath()).
Can a file widget accept URLs?
Yes — users can paste a URL into the line edit, and filePath() returns it; check whether your code accepts remote sources before allowing it.
How do I remember the last values between sessions?
Store them with QgsSettings on accept and restore them on open, as in remembering the last used folder.
Do these widgets respect QGIS's theme and language? Yes. They use QGIS's own icons, styling and translations, which is one reason dialogs built from them feel native in every language QGIS supports.
Can I disable typing so users must browse?
Set the file widget's line edit read-only with lineEdit().setReadOnly(True); browsing and drag-and-drop still work.
Is there a widget for picking a Processing output? Processing parameter widgets exist, but for plugins the widgets here are simpler.
Can the CRS widget show only projected systems?
Validate the choice instead; the widget lists all CRSs, and a check on isGeographic() with a message is clearer.