Building Custom QGIS Plugins with Python: A Beginner’s Guide

Building Custom QGIS Plugins with Python: A Beginner’s Guide

PyQGIS Development Tutorial · QGIS Desktop · Python · Qt Designer

Introduction

Building a custom QGIS plugin with Python and PyQGIS allows GIS professionals to transform repetitive geospatial workflows into reusable tools. Instead of manually repeating the same sequence of geoprocessing operations, a team can develop a plugin that performs those tasks through a dedicated toolbar button, dialog box, or custom processing interface.

For example, a planning department could create a plugin that validates parcel boundaries, checks for missing attribute values, generates buffers around selected infrastructure, and exports standardized reports. Every team member would use the same workflow, reducing manual errors and improving consistency.

The Build Custom QGIS Plugin Python PyQGIS workflow combines Python for processing logic, PyQGIS for interacting with QGIS data and tools, and Qt for building graphical interfaces.

This guide walks through the complete development process: generating a plugin template, designing its interface, implementing Python functionality, compiling resources, testing the plugin, debugging errors, and sharing the finished project.

1. Developer toolkit needed

Before writing code, prepare the development environment. Using the Python installation bundled with QGIS is generally the easiest approach because it includes the libraries and bindings required by PyQGIS.

Building Custom QGIS Plugins with Python

QGIS Desktop

The primary GIS application used to run, test, and debug plugins. Install a supported QGIS release and use that same release when developing your first plugin.

File:Python logo 01.svg - Wikimedia Commons

Python 3 and PyQGIS

Python provides the programming language, while PyQGIS exposes QGIS functionality such as vector layers, map canvases, feature attributes, and processing algorithms.

Getting to Know Qt Widgets Designer | Qt Widgets Designer Manual

Qt Designer

A visual interface editor for creating dialog windows, buttons, labels, dropdown menus, and file-selection controls without manually writing the entire interface in Python.

4.11.1 Creating a Plugin Template with Plugin Builder 3 | GEOG 489: Advanced Python Programming for GIS

Plugin Builder

A QGIS plugin that generates a structured Python plugin template, including essential files, metadata, and starter code.

1.1 Install and configure the tools

Step 1 — Install QGIS Desktop

Download QGIS from the official website: QGIS download page.

  • Choose a supported release for your operating system.

  • Install QGIS using its recommended installation method.

  • Launch QGIS and verify that it opens correctly.

Step 2 — Install Plugin Builder

  1. Open QGIS Desktop.

  2. Navigate to Plugins → Manage and Install Plugins.

  3. Select the All tab and search for Plugin Builder.

  4. Select the plugin and click Install Plugin.

If Plugin Builder does not appear, check your internet connection, plugin repository settings, and QGIS version compatibility.

Step 3 — Prepare Python and Qt Designer

The Python interpreter bundled with QGIS is usually the safest starting point. Installing an unrelated Python environment can cause missing-module or incompatible-library errors.

Qt Designer may be available through your QGIS installation or its associated Qt tools, depending on the operating system and installation method.

Official references:

2. Step 1 — Generate the plugin template with Plugin Builder

Plugin Builder saves development time by creating the initial directory structure and boilerplate code. Rather than creating every file manually, you start with a working foundation and customize it for your requirements.

2.1 Create a new plugin

In QGIS, open Plugins → Plugin Builder → Plugin Builder. The exact menu placement may vary slightly by version.

Complete the form using a simple example plugin called Layer Inspector.

Example plugin configuration

Class name

LayerInspector

Plugin name

Layer Inspector

Module name

layer_inspector

Description

Inspect the active vector layer and report its feature count.

Minimum QGIS version

Select a version compatible with the QGIS installation you intend to support.

Output directory

A development folder such as Documents/qgis_plugins

Fill in the remaining required fields, including author details and version information. If Plugin Builder offers a template choice, select a standard dialog-based plugin suitable for your first project.

Click OK or the equivalent completion button to generate the project.

2.2 Understand the generated files

Your plugin directory will contain files similar to these. Exact names depend on the template and options selected.

layer_inspector/
├── __init__.py
├── layer_inspector.py
├── layer_inspector_dialog.py
├── layer_inspector_dialog_base.ui
├── metadata.txt
├── resources.qrc
├── resources.py
└── icon.png
File Purpose
__init__.py Provides the plugin entry point through classFactory().
layer_inspector.py Contains the main plugin class and lifecycle methods.
layer_inspector_dialog.py Connects the interface to Python code.
layer_inspector_dialog_base.ui Defines the dialog layout created in Qt Designer.
metadata.txt Stores plugin name, version, description, and compatibility information.
resources.qrc Lists bundled icons and other Qt resources.
resources.py Generated Python resource module, if the template uses compiled Qt resources.
icon.png The plugin’s toolbar or menu icon.

Important: Do not assume that every template includes exactly these files. Preserve the structure generated by Plugin Builder and adapt the code to your actual filenames.

3. Step 2 — Design the user interface in Qt Designer

A useful plugin should be easy for another GIS analyst to operate without opening a Python editor. Qt Designer lets you build this interface visually.

For the Layer Inspector example, create a dialog that lets a user select a vector layer and inspect its feature count.

3.1 Open the .ui file

  1. Locate layer_inspector_dialog_base.ui in your plugin directory.

  2. Open it using Qt Designer.

  3. Inspect the existing dialog and decide whether to modify it or create a new layout.

  4. Save the file after making changes.

If your QGIS installation does not provide a convenient Qt Designer launcher, use the compatible Qt tools installed for your QGIS environment.

3.2 Add interface widgets

Drag the following widgets from the Qt Designer widget palette onto your dialog.

Example dialog layout

Layer Inspector

Select a layer…

Feature count and layer information will appear here.

Inspect Layer

Close

Illustrative layout for the example plugin; build the actual widgets in Qt Designer.

Use these widget types and object names:

Widget Object name Purpose
QComboBox layerComboBox Lists available vector layers.
QLabel resultLabel Displays inspection results.
QPushButton inspectButton Runs the inspection.
QPushButton closeButton Closes the dialog.

To assign a name, select a widget and edit its objectName property in the Property Editor. The name is important because Python code uses it to access the widget.

3.3 Connect the buttons

You can connect signals and slots through Qt Designer, but a beginner-friendly approach is to connect the buttons in Python.

For example:

self.inspectButton.clicked.connect(    self.inspect_layer)self.closeButton.clicked.connect(    self.close)

These connections tell Qt which Python method to execute when a user clicks a button. The methods must exist on the object that owns these widgets.

Save your .ui file before moving to the implementation stage.

4. Step 3 — Write the core PyQGIS execution logic

This stage turns the interface into a functional GIS tool.

PyQGIS provides access to the current project through QgsProject, loaded map layers through mapLayers(), and vector features through a layer’s feature iterator.

For this first plugin, the task is intentionally simple: list vector layers in the interface, allow a user to select one, and display its name, geometry type, and feature count.

4.1 Load vector layers into the dropdown

In your dialog’s Python class, populate the dropdown using the current QGIS project.

from qgis.core import QgsProject, QgsVectorLayerdef populate_layers(self):    self.layerComboBox.clear()    layers = QgsProject.instance().mapLayers().values()    for layer in layers:        if isinstance(layer, QgsVectorLayer):            self.layerComboBox.addItem(                layer.name(),                layer.id()            )

What this code does:

  • QgsProject.instance() retrieves the current QGIS project.

  • mapLayers() returns the project’s loaded layers.

  • isinstance() filters out raster layers and other non-vector layers.

  • addItem() adds a readable layer name and stores its unique layer ID as item data.

Storing the layer ID is useful because two different layers can have the same display name.

4.2 Inspect the selected layer

Add the following method to the same dialog class:

def inspect_layer(self):    layer_id = self.layerComboBox.currentData()    if not layer_id:        self.resultLabel.setText(            "Please select a vector layer."        )        return    layer = QgsProject.instance().mapLayer(layer_id)    if not isinstance(layer, QgsVectorLayer):        self.resultLabel.setText(            "The selected layer is unavailable."        )        return    feature_count = layer.featureCount()    geometry_type = layer.geometryType()    geometry_names = {        0: "Point",        1: "Line",        2: "Polygon"    }    geometry_name = geometry_names.get(        geometry_type, "Other"    )    self.resultLabel.setText(        f"Layer: {layer.name()}\n"        f"Geometry: {geometry_name}\n"        f"Features: {feature_count}"    )

The method validates the selected layer before accessing its properties. It then reports three useful pieces of information.

Note that featureCount() may depend on the data provider and can require provider-specific work for some large or remote datasets. For an introductory plugin, it is a practical starting point.

4.3 Wire the dialog controls together

In the dialog initialization code, ensure that the interface is loaded before you access its widgets.

A typical dialog class generated from a Plugin Builder template may already load the .ui file. If so, do not load it a second time. Add the following connections to the existing initialization method after the widgets are available:

self.inspectButton.clicked.connect(    self.inspect_layer)self.closeButton.clicked.connect(    self.close)self.populate_layers()

Keep populate_layers() and inspect_layer() as methods of the same dialog class.

If your generated dialog does not use the object names shown in this guide, update either the names in Qt Designer or the Python references so they match exactly.

4.4 Integrate the dialog with the main plugin class

Plugin Builder normally generates a main plugin class that registers an action in QGIS and opens a dialog when that action is triggered.

The general pattern is:

def run(self):    if self.first_start:        self.first_start = False        self.dlg = LayerInspectorDialog()    self.populate_dialog_if_needed()    self.dlg.show()    self.dlg.exec_()

This is a structural example, not a drop-in replacement for every generated template. Keep your template’s existing initialization, imports, action registration, and dialog constructor. Replace populate_dialog_if_needed() with the appropriate refresh method if you want the layer list updated whenever the dialog opens.

On QGIS versions using newer Qt bindings, dialog execution may use exec() rather than exec_(). Follow the API supported by your target QGIS release.

The plugin’s toolbar action should call run(), allowing users to open the dialog directly from QGIS.

5. Step 4 — Compile resources and load the plugin into QGIS

Before testing the interface, make sure the plugin’s Python files, UI definition, and resource references are correctly configured.

5.1 Compile Qt resources when required

A .qrc file lists resources such as icons that Qt can embed in an application. If your plugin uses compiled Qt resources, changes to the .qrc file must be compiled into the corresponding Python resource module.

The standard compilation command is:

pyrcc5 -o resources.py resources.qrc

Depending on your QGIS and Qt installation, the available command may differ. For example, a Qt 6-based environment may use a Qt 6 resource compiler rather than pyrcc5.

Run the compiler in the appropriate QGIS Python environment, or use the executable supplied by your installation. If the template does not use compiled resources, you may not need this step.

Important distinction: Qt Designer creates the .ui file, while the Qt resource compiler processes .qrc files. They are separate tasks. Some plugin templates load .ui files dynamically; others compile them into Python code.

5.2 Install the plugin in the local development directory

QGIS searches designated plugin directories for Python plugins. A common user-level location on Windows is:

%APPDATA%\QGIS\QGIS3\profiles\default\python\plugins\

The actual location depends on your operating system, active QGIS profile, and version. You can inspect the Python paths available to your installation from the QGIS Python Console.

Copy your entire plugin directory into the appropriate plugin directory, preserving its internal structure.

For example:

plugins/
└── layer_inspector/
    ├── __init__.py
    ├── layer_inspector.py
    ├── metadata.txt
    └── ...

Do not copy only the main .py file. The plugin entry point, metadata, dialog files, and any required resources must remain accessible.

5.3 Enable and test the plugin

1. Restart or refresh QGIS

Restart QGIS if necessary so the plugin loader can discover the new files.

2. Enable the plugin

Navigate to Plugins → Manage and Install Plugins → Installed, find Layer Inspector, and enable it.

3. Load test data

Add a point, line, or polygon vector layer to the project. GeoPackage is a convenient format for testing.

4. Run the plugin

Open Layer Inspector from its registered menu or toolbar action. Select a layer and click Inspect Layer.

A successful test should display the selected layer’s name, geometry type, and feature count. Repeat the test with different layer types and with a project that contains no vector layers.

6. Debugging and troubleshooting

Plugin development involves more than writing valid Python. Your code must also work with the correct QGIS libraries, Qt bindings, plugin structure, and data providers.

Common errors and solutions

Error or symptom Likely cause Recommended fix
Plugin does not appear in the Installed tab Incorrect folder structure or missing metadata Check the plugin directory, __init__.py, and metadata.txt.
ModuleNotFoundError Missing module or incompatible Python environment Run the plugin inside the QGIS Python environment and verify imports.
AttributeError for a widget Object name mismatch or UI not loaded Compare Python widget names with the Qt Designer object names.
Resource import error Missing or outdated compiled resource module Recompile resources and verify the generated filename.
Plugin opens but the dropdown is empty No vector layers or layer population was not called Load a vector layer and invoke populate_layers() after UI initialization.
Feature count is unexpected Provider limitations, invalid data, or filtering Check the layer source, provider status, and active feature filters.

6.1 Use the QGIS Python Console

Open Plugins → Python Console in QGIS to inspect the active environment and investigate errors.

For example:

from qgis.core import QgsProjectproject = QgsProject.instance()for layer in project.mapLayers().values():    print(        layer.name(),        layer.isValid(),        layer.featureCount()        if hasattr(layer, "featureCount")        else "Not a vector layer"    )

This helps establish whether the project contains valid layers before you investigate the plugin’s own code.

For more detailed debugging, use Python’s logging module or Python’s traceback module to capture exception details. Check QGIS messages and the Python Console for the full traceback rather than relying only on a generic error dialog.

Development tip: Make one change at a time, restart or reload the plugin as needed, and test the same operation again. This makes it easier to identify the cause of a failure.

7. Package and distribute the plugin

Once the plugin works locally, package it so colleagues can install and use it without manually reconstructing the project.

7.1 Prepare the repository

Use Git to track the source files and document the installation process. A suitable repository structure might look like this:

layer-inspector/
├── __init__.py
├── layer_inspector.py
├── layer_inspector_dialog.py
├── layer_inspector_dialog_base.ui
├── metadata.txt
├── resources.qrc
├── resources.py
├── icon.png
├── README.md
├── LICENSE
└── .gitignore

Only include files your plugin actually needs. If your build process generates resource or UI modules, document that process so another developer can reproduce the build.

Your README should explain:

  • What the plugin does.

  • Which QGIS versions it supports.

  • How to install and enable it.

  • How to use its main functions.

  • Any external dependencies.

  • Known limitations and troubleshooting steps.

7.2 Create a ZIP archive

For direct sharing, package the plugin directory into a ZIP file. The archive should contain the plugin folder at its root, not an additional unrelated parent directory.

Example using Python:

from pathlib import Pathimport shutilplugin_folder = Path("layer_inspector")shutil.make_archive(    "layer_inspector",    "zip",    root_dir=plugin_folder.parent,    base_dir=plugin_folder.name)

Run this script from a location where layer_inspector exists, or replace the path with the actual directory. The resulting archive can be shared with colleagues.

To install it, colleagues can use QGIS’s Install from ZIP option under the plugin manager. They should verify compatibility before enabling it.

7.3 Share through GitHub or the QGIS plugin repository

For internal teams, a private Git repository can support version control, code reviews, issue tracking, and coordinated development.

For wider distribution, you can publish a compatible plugin through the official QGIS plugin repository: QGIS Python Plugins Repository.

Before release:

  • Update the plugin version in metadata.txt.

  • Test on every QGIS version you claim to support.

  • Remove temporary files and development-only data.

  • Check that bundled dependencies are documented and appropriately licensed.

  • Verify that the installation archive has the correct structure.

  • Provide a clear README and a suitable open-source license when applicable.

For teams that rely on the plugin for operational workflows, tag each stable release in Git so users can identify which version they are running.

8. Further development: turn the example into a real GIS tool

The Layer Inspector demonstrates the fundamental plugin architecture. Once the pattern is working, you can extend it into tools that automate more substantial geospatial workflows.

25.2.5. Topology Checker Plugin — QGIS Documentation documentație

Automated data validation

Check for missing attributes, invalid geometries, duplicate identifiers, or inconsistent coordinate reference systems.

6.2. Lección: Análisis vectorial — documentación de QGIS Documentation -

Custom toolbar tools

Add one-click buffers, clipping, reprojection, or standardized exports using PyQGIS and the Processing framework.

ボロノイ分割とは? | MarketPlanner製品サイト

Reporting and quality assurance

Generate summary tables, validation reports, and reusable outputs that follow your organization’s standards.

For more advanced processing, explore the official QGIS user and PyQGIS developer documentation, especially the PyQGIS Developer Cookbook and Processing framework documentation.

Summary

Mastering the art of Building Custom QGIS Plugins with Python gives you a practical way to standardize workflows. Building a custom QGIS plugin with Python and PyQGIS is a practical way to standardize geospatial workflows and reduce repetitive manual work.

The development process follows a repeatable sequence:

  1. Install QGIS and prepare the compatible Python and Qt tools.

  2. Generate a plugin skeleton using Plugin Builder.

  3. Design the interface in Qt Designer.

  4. Implement the GIS logic using PyQGIS.

  5. Compile resources where required and load the plugin into QGIS.

  6. Debug, test, document, and package the finished tool.

Start with a small, testable task such as inspecting a vector layer. Once you understand how the plugin entry point, dialog, widget signals, and PyQGIS objects work together, you can build more sophisticated tools for data validation, spatial analysis, automated reporting, and internal GIS operations.

The real value of a custom plugin is not simply adding a new toolbar button. It is turning a reliable GIS workflow into a reusable tool that other people can operate consistently.