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.

Three icons, two mechanismsThe plugin manager reads an icon path from metadata.txt. The toolbar button and the menu item both take a QIcon constructed in initGui, either from a file path or from a compiled resource. All three usually point at the same image, and forgetting the metadata entry is what leaves a blank square in the plugin manager list.The same image, declared in two different placesmetadata.txticon=icon.pngQIcon in initGuipath or compiled resourceplugin managerthe list entrytoolbar button24 pixels, seen constantlymenu item16 pixels, beside the label

Prerequisites

  • QGIS 3.34 LTR (bundled Python 3.12) or newer.
  • A plugin with initGui() and a metadata.txt.
  • pyrcc5 if 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.

Paths or resourcesFile paths need no build step, let an icon be replaced by swapping the file, and require the icons folder to be included when packaging. Compiled resources need a build step and a rebuild after any change, but bundle everything into one module and are what Qt Designer writes into user interface files.Neither is wrong; pick for how the plugin is builtquestionfile pathscompiled resourcesbuild step needed?nonepyrcc5 before packagingchanging an iconswap the fileswap and recompileused by .ui files?awkwardwhat Designer writespackaging riskforget the icons folderforget to recompile

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.

The same icon, three sizes and two themesAt sixty-four pixels an icon can carry detail. At twenty-four, the toolbar size, only two or three strong shapes remain legible. At sixteen, the menu size, it is a silhouette. Against a dark toolbar a dark icon disappears, so a mid-tone that reads on both backgrounds is the safest single design.Design for 24 pixels, then check both themesthe same icon, three sizes64 px24 px — the toolbar16 pxtwo toolbar backgroundslight theme — visibledark theme — still readsa near-black icon would vanish in the lower row

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 versionPythonNotes
3.22 LTR3.9QIcon from paths or resources; pyrcc5 from PyQt5.
3.28 LTR3.9Identical.
3.34 LTR3.12Baseline for this page.
3.40 / 3.443.12Identical; 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 in metadata.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.