Add a Plugin Icon and Resources File
Icons are the part of a plugin everybody sees and nobody plans. A toolbar button with the generic Python icon looks unfinished, an icon that is a screenshot scaled to 24 pixels looks worse, and a plugin whose icon is missing entirely after packaging looks broken — which, from the user's point of view, it is.
This recipe belongs to Plugin Boilerplate and Structure. It covers the three places an icon appears, loading icons from a path or from a compiled resource file, choosing between the two, sizing and theme considerations, and keeping icons working after packaging.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- A plugin with
initGui()and ametadata.txt. pyrcc5if you choose to use a compiled resource file; it ships with PyQt5 and with most QGIS installations.
The plugin manager icon
[general]
name=Parcel Tools
icon=icon.png
Breakdown: The path is relative to the plugin folder, and this is the only icon the plugin manager reads — a plugin with beautiful toolbar icons and no icon= line still appears as a blank square in the list where users choose what to install. A square PNG of 64 or 128 pixels is right; the manager scales it down, and starting larger keeps it sharp on high-resolution displays. This icon also appears on the plugin's page in the repository, which makes it the first thing a prospective user sees.
Load icons from a path
The simplest approach needs no build step at all:
import os
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
class ParcelTools:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
def initGui(self):
icon = QIcon(os.path.join(self.plugin_dir, "icons", "summarise.svg"))
action = QAction(icon, self.tr("Summarise parcels"), self.iface.mainWindow())
action.triggered.connect(self.run)
self.iface.addToolBarIcon(action)
self.iface.addPluginToVectorMenu(self.tr("Parcel Tools"), action)
self.actions.append(action)
Breakdown: Building the path from os.path.dirname(__file__) is what makes it work regardless of where the plugin is installed — a relative path such as "icons/summarise.svg" resolves against the current working directory, which is wherever QGIS was started and almost never your plugin folder. That single mistake accounts for most missing-icon reports. An SVG scales cleanly to whatever size Qt asks for, which is why it is preferable to a PNG for anything drawn at more than one size. Keeping the actions in a list makes unload() symmetrical, which matters as much for icons as for anything else.
Or compile a resources file
The alternative bundles images into a Python module and refers to them by a virtual path:
pyrcc5 -o resources.py resources.qrc
<!-- resources.qrc -->
<RCC>
<qresource prefix="/plugins/parcel_tools">
<file>icons/summarise.svg</file>
<file>icons/split.svg</file>
<file>icon.png</file>
</qresource>
</RCC>
from . import resources # noqa: F401 — registers the resources
icon = QIcon(":/plugins/parcel_tools/icons/summarise.svg")
Breakdown: pyrcc5 turns the listed files into byte arrays inside resources.py, and importing that module registers them with Qt under the declared prefix. The leading colon in the path is what tells QIcon to look in the resource system rather than on disk. The import looks unused and is not — it must happen before any resource path is used, which is why it carries a linter suppression in most plugins. The generated file goes into the archive; the .qrc need not.
Which to choose? Paths are simpler, need no build step, and let you change an icon without recompiling — for most plugins that is the better trade. Resources are worth it when a plugin has many images, when it is packaged in ways that make the folder layout unreliable, or when a .ui file references icons, since Designer writes resource paths rather than file paths.
Draw icons that read at 24 pixels
A toolbar icon is roughly 24 pixels square, and a menu icon 16. That is a severe constraint, and most homemade plugin icons fail it in the same ways.
One idea, few shapes. At 24 pixels a symbol has room for two or three strokes. A map, a magnifier and a gear stacked together become a smudge.
Strong contrast, no fine detail. Hairlines disappear and thin text becomes noise. Nothing smaller than about two pixels of stroke survives.
Match the surrounding style. QGIS's own icons are flat, outlined, and mostly monochrome with a single accent. An icon in a different style stands out in the wrong way — it looks like it came from somewhere else, because it did.
Test it at size, in both themes. QGIS ships light and dark interface themes, and an icon drawn as dark strokes on transparent vanishes against a dark toolbar. Either use a mid-tone that works on both, or ship two variants and select on the active theme.
from qgis.core import QgsSettings
theme = QgsSettings().value("UI/UITheme", "default", type=str)
suffix = "_dark" if theme.lower().startswith("night") else ""
icon = QIcon(os.path.join(self.plugin_dir, "icons", f"summarise{suffix}.svg"))
Breakdown: Reading QGIS's theme setting lets a plugin pick a variant that stays visible. The value is a theme name rather than a boolean, so a prefix check is more robust than an equality test against one spelling. Note that the theme can change while QGIS runs and the icons will not update until the plugin reloads — acceptable for most plugins, and a reason to prefer a single mid-tone icon where the design allows it.
Make sure the icons survive packaging
Two failures show up only after installing the built archive, which is why the clean-profile test in Package a QGIS Plugin as a Zip matters.
A missing icons folder. A build script excluding by suffix can easily drop .svg or .png files. Include them explicitly, and check the archive contents rather than assuming.
A stale or missing resources.py. If the plugin uses compiled resources, the compile step must run before packaging, and the generated module must be included. A plugin that works in development because resources.py is sitting in the working folder will ship without it and lose every icon.
Both are caught by the same check: install the archive into a fresh profile and look at the toolbar. It takes thirty seconds and is the only reliable test, since your development folder always has the files.
QGIS version compatibility
| QGIS version | Python | Notes |
|---|---|---|
| 3.22 LTR | 3.9 | QIcon from paths or resources; pyrcc5 from PyQt5. |
| 3.28 LTR | 3.9 | Identical. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40 / 3.44 | 3.12 | Identical; builds against Qt 6 use pyside6-rcc or an equivalent, another reason to prefer plain file paths. |
Qt 6 removes pyrcc5, so a plugin that wants to work across both bindings without a conditional build step is better off loading icons from paths.
Troubleshooting
- The icon is blank in the plugin manager. No
icon=line inmetadata.txt, or the path does not resolve inside the plugin folder. - Toolbar icons are missing after install. A relative path resolved against the working directory, or the icons folder was excluded from the archive.
ModuleNotFoundError: resources. The compiled module was not generated or not packaged.- The icon is invisible on a dark theme. Dark strokes on transparent. Use a mid-tone or ship a variant.
- An SVG icon renders blurry. It is not really an SVG — a bitmap embedded in an SVG wrapper scales like a bitmap.
- The icon changed but QGIS shows the old one. Qt caches icons per path; reload the plugin, and rebuild resources if you use them.
Conclusion
Point metadata.txt at a square PNG for the plugin manager, and build QIcon paths from os.path.dirname(__file__) so they resolve wherever the plugin is installed. Prefer file paths unless a .ui file or a large image set argues for compiled resources, design for 24 pixels with a couple of strong shapes, check both interface themes, and verify the icons after installing the built archive rather than trusting your development folder.
Frequently Asked Questions
What size should the plugin manager icon be? Square, 64 or 128 pixels. It is scaled down, so larger is safer than smaller.
Can I use an SVG for the manager icon? PNG is the safe choice there; SVG is ideal for toolbar and menu icons where Qt renders at several sizes.
Do I have to use a resources file? No. It is a convention from the Plugin Builder template rather than a requirement, and file paths are simpler for most plugins.
Where should icons live in the plugin folder?
An icons/ subfolder, with the manager icon at the top level where metadata.txt points. Consistency matters more than the exact layout.
Can I reuse QGIS's own icons?
Yes, through QgsApplication.getThemeIcon("/mActionZoomIn.svg"), which follows the user's theme automatically. It is the best option when your action mirrors a built-in one.
How do I add an icon to a Processing algorithm?
Return one from the algorithm's icon() method, or from the provider's for the whole group — see Register a Processing Provider in a Plugin.