This tutorial is part of our QGIS tutorial series:
- QGIS 3 Plugins - Plugin 101
- QGIS 3 Plugins - Qt Designer Explained
- QGIS 3 Plugins - Signals and Slots in PyQt
- QGIS 3 Plugins - Geocoding with Nominatim Part 1 (First Steps)
- QGIS 3 Plugins - Geocoding with Nominatim Part 2 (Interactivity)
- QGIS 3 Plugins - Geocoding with Nominatim Part 3 (Best Practices)
- QGIS 3 Plugins - Geocoding with Nominatim Part 4 (Tests & CI)
- QGIS 3 Plugins - Set up Plugin Repository
- QGIS 3 Plugins - Background Tasks
QGIS 3 Plugins - Plugin 101
This blog is a reference guide to QGIS 3 plugin lingo and explains important concepts. It's mostly based on output of Plugin Builder 3, which is very useful for generating the necessary boiler plate code. However, it's hard to decipher all the hidden meanings of the code it supplies.
If you miss documentation of some methods or concepts, please open an issue on GitHub or create a pull request with your enhancement.
Validity only confirmed for Ubuntu 18.04 and QGIS v3.4 Occassionally, the author might choose to give hints on Windows-specific setups.
Ctrl+Ffor WINDOWS flags. Mac OS users should find the instructions reasonably familiar.
integrated developer environment
graphical user interface
QGIS generally ships with the official Python distribution, which is fairly slim with regards to included packages. Additionally, it ships multiple convenience packages, like
NumPy. Feel free to extend this list in a PR, since there doesn't seem to be extensive documentation.
Under Linux (and presumably Mac OS), QGIS 3 utilizes the system Python3 executable and installs all needed libraries in its
PYTHONPATH. Usually, it's not practical to use virtual environments for QGIS plugins. WINDOWS users are not so lucky: they will have to use the OSGeo4W Python executable or modify their
PYTHONPATH to reflect QGIS native Python libraries.
So, for Linux/Mac users it's straight forward to use an IDE, like Sypder or PyCharm and enjoy the benefit of code completion and inline documentation.
Plugin system paths
QGIS will look for external plugins in these paths :
Deploy plugin to QGIS
Since you usually won't work in the native QGIS plugin path, there's a few extra steps to deploy the plugin so that QGIS recognizes it and you see changes you make along the development.
It's enough to create a symbolic link from your dev environment to the QGIS Python Plugin directory:
pyrcc5 -o resources.py resources.qrc ln -s myplugin/ ~/.local/share/QGIS/QGIS3/profiles/default/python/plugins
Then, use the Plugin Reloader plugin to reload your modified code on-the-fly from within QGIS, without restarting QGIS. Note, a restart is necessary if you modified any methods which are only loaded on QGIS startup, like the root
__init__.py or the
This is the main source for the plugin repository, but also the QGIS Plugin Manager, which both extract information about author, version etc from here. Generally, you can use this file to store all kinds of meta information, like a help URL or collaborators. It's a standard INI structured file with sections and
key=value pairs. Hence, it can be parsed with Python's native
configparser library. It makes sense to keep
metadata.txt as the only entry point for meta information and, when needed in the code base, import the configuration from it (like
Multiline statements (like in changelogs) must be indented after the first line. Paths are set relatively, e.g.
gui/img/icon.png if it lives in
Detailed module and method description
QGIS Python plugins need a few mandatory modules and methods so they work smoothly with QGIS.
Consider the name of your plugin 'MyPlugin', whose boiler plate code was generated with Plugin Builder 3.
The minimum tree for a functioning output of Plugin Builder 3 would look like:
├──myplugin ├──icon.png ├──__init__.py ├──metadata.txt ├──my_plugin.py ├──my_plugin_dialog.py ├──my_plugin_dialog_base.ui └──resources.qrc
icon.png: the default icon for the plugin. Change this file to reflect your own icon.
__init__.py: contains the function which will initialize the plugin on QGIS startup and register it with QGIS, so it knows about this plugin.
metadata.txt: contains information about the plugin, which will be used by the official QGIS plugin repository and the QGIS Plugin Manager to display information about your plugin, e.g. description, version, author, URL etc. See metadata section
my_plugin.py: contains the heart of the plugin: custom functionality will go into this file.
my_plugin_dialog.py: loads the plugin User Interface (UI), when QGIS starts up
my_plugin_dialog_base.ui: this is the Qt Designer file to style and build the UI, i.e. plugin window.
resource.qrc: contains the resources for Qt. You will only edit this file when you rename the plugin icon or you want to add additional icons. Needs to be compiled to resources.py (Note: soon obsolete).
Find a more detailed description below.
That's a Qt resource file. Basically, it contains instructions for the Qt framework where to find which resources, e.g. plugin icons. Find a more detailed discussion here. It needs to be compiled to
resources.py and imported in
Note, this will be deprecated in a future in
PyQt6, load icons and other resources via more pythonic methods.
It's responsible to make QGIS aware of the plugin's existence. Which is what the
classFactory() class does by instantiating your plugin's main class from
my_plugin.py. More on that further down.
Don't alter the classFactory() class! It's implicitly expected by QGIS. Which you'll find is a common thing in QGIS' Python API.
This file only contains a single class:
MyPluginDialog, which sub-classes
FORM_CLASS, hence inherits all methods of both classes.
QWidgetwhich will represent your plugin window (not the UI elements themselves).
FORM_CLASS: loads your UI file into a class.
MyPluginDialog class will be instantiated in the main
my_plugin.py module. The main line to note here is:
setupUi() is a method of
FORM_CLASS, which will set up your UI elements in your
QDialog plugin window. What is highly confusing here: the caller's
self refers to
FORM_CLASS, the argument
self refers to
setupUi() takes a
QWidget as argument). That's entirely valid, since
QuickApiDialg sub-classes both classes, but it sure is confusing to any newcomers.
This is a bigger beast and you'll spend most time here. It looks scary at first, but trust us, there's a lot of unnecessary boiler plate code here (at least for your current purposes). Instead of stripping it down to the most essential parts, we'll explain all methods. But you will only work with the most important ones. [required] methods are implicitly expected and called by the main QGIS application, so don't alter their name or input parameters!
It only contains a single class
MyPlugin. This class will be instantiated by the plugin's
classFactory class, which in turn is called by QGIS on startup to make your plugin known to QGIS. So, really, this is the heart of the plugin.
def __init__(self, iface) [required]
MyPlugin class is passed the
iface parameter, which is a
QgisInterface and lets you interact with the QGIS GUI.
We'll go through the lines in order:
self.iface: saves a reference to the QGIS GUI interface (
locale: all code lines concerning locales, you can (more or less) safely ignore for now. They mostly deal with s and internationalization.
self.actions: a container for
QActions, which we'll explain a little later.
self.menu: the name which will appear in the menu bar for this plugin.
def tr(self, message)
If your plugin makes use of translations, this method will handle it. You can read more about it here.
def add_action(self, ...)
It's creating a
QAction object, which can be used to instruct QGIS to add icons to a menu, toolbar etc. What it only does, is:
- add icon to Plugin Toolbar
- add entry to QGIS 'Vector' menu list
- set help texts if specified (
- add a callback to the action, which is executed when either the icon or the menu item are clicked (
def initGui(self) [required]
This method is called by QGIS when the main GUI starts up or when the plugin is enabled in the Plugin Manager.
Usually, you only want to register the menu items and toolbar buttons here. Which is what the default
self.add_action() does. You can add additional parameters to the call like
status_tip, which give some help to the plugin user.
callback paramter is set to
self.run. Meaning, the
self.run() will be executed once the user clicks on the plugin menu item or on the toolbar icon. So, you can already guess, that that's the most important function in this class.
Also note the icon path:
':/plugins/my_plugin/icon.png'. The colon instructs QGIS to use the compiled
resources.py file to locate the icon. Open the original
resources.qrc file and you'll see the connection. A more detailed discussion of the resource file is given here.
Also, we state here that the plugin is started for the first time during the lifetime of a running QGIS session.
def unload(self) [required]
Will be executed when the plugin is disabled. Either in the Plugin Manager or when QGIS shuts down.
It only removes the previously defined
QAction object from the menu and remove the plugin icon from the toolbar.
This code block executes the custom plugin functionality. This method is called by the previously defined
QAction object, which went into the toolbar icon and the menu entry.
Really, this function could be called anything you'd like. There's no hidden meaning to it, as there is for
initGui(self). You could even have multiple callbacks like
run(self) connected to a
First thing it does here: check if this is the first time the plugin is called. Remember, this function is called every time the user clicks on the plugin toolbar button/menu entry. Only if it's the first start, it'll build the GUI by instantiating the
QuickApiDialog class (which calls its
setupUi() in its
__init__()) and assigning it to
dlg. If the GUI would be freshly built every time the user calls the plugin, all previous GUI settings would be lost.
dlg now holds all plugin GUI objects, which can be accessed by the names you gave them in Qt Designer.
dlg.show() shows the GUI modeless, i.e. the user can interact with QGIS main window while the plugin GUI is open.
dlg.exec_() shows the GUI modal, i.e. the user can't interact with QGIS main window while the plugin is open. When the user presses a button to close the plugin, this function will return a code, which is
1 if the user pressed OK and
0 if the user pressed Cancel. Usually one would either use
exec_() is equivalent to
exec() and was introduced by
exec was a reserved keyword until Python 3.
1, meaning the user clicked OK, we want the plugin to execute its costum code. This is finally where the boiler plate ends and the action starts.
PyQt5 (and its C++ parent Qt) is the GUI framework QGIS relies on. Most UI related objects, methods and properties can be found in this library. Mostly, you'll deal with
Unfortunately, the direct code documentation of PyQt5 provided by Riverside is non-existing. However, there are a few ways to help yourself here:
you're using an IDE? Great! If you're lucky,
Ctrl+Qin PyCharm (
Ctrl+Iin Spyder) will show input and output parameters of the selected function.
the main documentation is here. However, it usually only refers you to the C++ documentation of the Qt library, which PyQt5 wraps for Python. That documentation can be slightly overwhelming. You'll get through it though, just consider these few guidelines (taking
- in Functions description, the first column tells you which object type is returned.
voiddoes not return anything.
QStringis implemented as a simple Python
str. The first few rows let you know how to construct an instance of the widget.
- the Properties are implemented as methods, not attributes, i.e. in
textis implemented in
<some QLineEdit widget>.text(), which will give you the current text of the widget
- we will deal with Signals and Slots later
- in Functions description, the first column tells you which object type is returned.
to lookup properties of a specific widget, use Qt Creator's Properties panel
PyQGIS is the standard synonym for the
qgis Python library (which will help you a lot when googling solutions). For Linux users (with IDE's), documentation is fairly straight forward, as they benefit from auto-completion and inline documentation. WINDOWS users who did not manually change their Python executable to QGIS' one (or include the appropriate directories in their PYTHONPATH), won't be as lucky.
main documentation is here: https://www.qgis.org/pyqgis/master/. From QGIS v3.x on, the documentation improved A LOT! If you ever wonder how to access certain QGIS related properties or methods, either use the search box. Or drill down manually. Basically, the (commonly) 2 most important modules in PyQGIS are
core. The classes are ordered by broader GIS topics (Attributes, Fields and so on) and class names are very descriptive, so you should find your way easily. Note, that some class names are prefixed by
Qgs, some are prefixed by
Qgis(no, that's not confusing at all...). Occassionally, you'll find that the C++ documentation is more descriptive than the Python one.
the main online platform for questions is Stack Exchange. All the great QGIS goddesses and gods frequently visit and can help you out of your misery. Apply common sense before asking questions though, i.e. research for at least 20 mins. It really boosts your understanding if you solve problems on your own. Also, your question might have been asked before.
For more general questions which could be interesting for the whole community, subscribe to the QGIS developer mailing list