5 Tactics for QGIS Plugin Development

Over the last three months, I’ve dedicated my time to building a QGIS plugin for vectorizing maps. Because it was my first time writing PyQGIS code, I learned some tactics along the way to ensure my plugin:

  1. Adheres to the UX a QGIS user expects, and
  2. Safely uses the C++ API QGIS exposes

I wanted to share these five tactics I learned so new QGIS plugin developers could avoid the same traps I found myself in. I tried to order them by likelihood someone would find it useful.

1. Use lldb to debug QGIS segfaults

PyQGIS generates calls into QGIS C++ code via SIP instead of hand-rolled FFI wrapper functions. This means that C++ footguns, like null pointer dereferencing, remain possible despite writing only Python.

In early versions of my plugin, changing the map tool after switching active vector layers could crash QGIS. I was able to debug this by using lldb on Mac, which let me get a full backtrace of the segfault.

lldb -- /Applications/QGIS.app/Contents/MacOS/QGIS

Then, run and ask for a backtrace when it crashes:

(lldb) run
...
(lldb) bt

It turns out I was listening to the QGIS signal onMapToolChanged, which was probably not designed for plugins to attach to. I ended up refactoring my code to avoid that signal to stop my plugin from crashing QGIS.

You can also use valgrind for this, but it’s not supported on Macs with the M1 chip.

2. Design the UX to match QGIS

QGIS users are accustomed to the QGIS way of doing things. Some examples of user expectations:

  1. Keyboard shortcuts. Do escape, backspace, and delete match the user’s expectation?
  2. Global settings. Do you default to users' QGIS global settings when appropriate?
  3. Minimalist menus. Are all windows opened/inputs requested by the plugin mandatory?

Some examples of UX decisions I made in the AI vectorizer to match user expectations:

  1. Read from all visible raster layers in the project (not hidden ones)
  2. Save to the active, editing vector layer (same behavior as Add Line Feature)
  3. Use the user’s zoom level to infer the proper map resolution
  4. Perform raster reprojecting in the QGIS project’s chosen CRS (defaults to EPSG:4326)

3. Make error messages clear

While programmers generally appreciate thoughtfully-written error messages, QGIS plugins present a relatively complex environment in which errors are displayed:

  1. Your plugin will be installed among many other plugins
  2. Your plugin can present behavior that might not obviously root back to the plugin for the user

This makes it hard for a casual user of your plugin to uniquely attribute an error message you show to the plugin they installed a week ago. For this reason, you should definitely prefix your error messages with your plugin name.

A surprising example of this coming to light was when a user posted to the qgis-users mailing list inquiring about a weird new error they had seen:

“When I create a new shapefile or a geopackage layer with Point as the geometry type, I get an error message “Error: Unknown vector layer type”. The layer seems to work so I’m not that worried.”

It was to my dismay that the plugin causing this behavior was mine!

Hi Joe,

it looks like the error message is very likely generated by an external QGIS plugin (“Bunting Labs AI Vectorizer”) installed in the QGIS user profile you are using on the MacBookPro M2 system.

May you please try if the issue also occurs disabling such QGIS plugin or using a new QGIS user profile?

This could have been avoided had I prefixed errors with the plugin name from the beginning.

Additionally, I highly recommend giving all errors a limited duration. I chose duration=15 for most. There’s nothing more annoying than needing to repeatedly dismiss an error, especially if it doesn’t require addressing.

4. Optimize plugin load performance

Because plugins are always loaded whenever QGIS boots, having a slow load __init__() method will annoy your most casual users who experience a slow and steady decline in their app speed.

QGIS plugin load speed

I moved almost everything out of the plugin load path to achieve the 0.009 sec load time. Another important aspect of plugin performance is pushing computation to background tasks, but a whole article could be written about that.

To track plugin load time, you should add Michel Stuyts’s useful “Plugin Load Times” plugin.

5. Use GitHub code search to learn the API

One important aspect of developing in the QGIS ecosystem is the requirement of openness. All contributions to the QGIS plugin repository must be GPL licensed and be source available.

This makes GitHub code search especially useful for learning the mechanics of calling certain APIs.

Of course, my plugin is open source and on GitHub.