[{"data":1,"prerenderedAt":1336},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-plugin-icon-and-resources-pyqgis":3},{"id":4,"title":5,"body":6,"description":1325,"extension":1326,"meta":1327,"navigation":288,"path":1332,"seo":1333,"stem":1334,"__hash__":1335},"docs\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-plugin-icon-and-resources-pyqgis\u002Findex.md","Add a Plugin Icon and Resources File",{"type":7,"value":8,"toc":1312},"minimark",[9,13,17,26,159,164,192,196,224,234,238,241,491,513,517,520,540,587,623,643,650,776,780,783,789,795,801,807,902,1018,1023,1027,1035,1049,1060,1063,1067,1141,1147,1151,1197,1201,1216,1220,1226,1232,1238,1251,1261,1275,1279,1308],[10,11,5],"h1",{"id":12},"add-a-plugin-icon-and-resources-file",[14,15,16],"p",{},"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.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","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.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,91,97,102,106,109,117,123,127,129,132,135,138,142,145,152,156],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 272","img","Diagram of the three places a plugin icon appears, the plugin manager entry, the toolbar button and the menu item, and which declaration drives each","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three icons, two mechanisms",[40,41,42],"desc",{},"The 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.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","272","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"iconArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#0f766e",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","The same image, declared in two different places",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":66,"style":84},"40","52","300","70","10","#eef7f4","stroke-width:2.5",[68,86,90],{"x":87,"y":88,"style":89,"fill":66,"textAnchor":74},"190","78","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","metadata.txt",[68,92,96],{"x":87,"y":93,"style":94,"fill":95,"textAnchor":74},"100","text-anchor:middle;font-size:11px;font-family:sans-serif","#2f3b35","icon=icon.png",[44,98],{"x":99,"y":79,"width":80,"height":81,"rx":82,"fill":100,"stroke":101,"style":84},"420","#eff3ff","#2563eb",[68,103,105],{"x":104,"y":88,"style":89,"fill":101,"textAnchor":74},"570","QIcon in initGui",[68,107,108],{"x":104,"y":93,"style":94,"fill":95,"textAnchor":74},"path or compiled resource",[44,110],{"x":111,"y":112,"width":113,"height":114,"rx":58,"fill":115,"stroke":66,"style":116},"24","168","216","72","#fffdf7","stroke-width:2",[68,118,122],{"x":119,"y":120,"style":121,"fill":66,"textAnchor":74},"132","196","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","plugin manager",[68,124,126],{"x":119,"y":125,"style":94,"fill":95,"textAnchor":74},"218","the list entry",[44,128],{"x":48,"y":112,"width":113,"height":114,"rx":58,"fill":115,"stroke":101,"style":116},[68,130,131],{"x":70,"y":120,"style":121,"fill":101,"textAnchor":74},"toolbar button",[68,133,134],{"x":70,"y":125,"style":94,"fill":95,"textAnchor":74},"24 pixels, seen constantly",[44,136],{"x":137,"y":112,"width":113,"height":114,"rx":58,"fill":115,"stroke":101,"style":116},"520",[68,139,141],{"x":140,"y":120,"style":121,"fill":101,"textAnchor":74},"628","menu item",[68,143,144],{"x":140,"y":125,"style":94,"fill":95,"textAnchor":74},"16 pixels, beside the label",[146,147],"line",{"x1":148,"y1":149,"x2":119,"y2":150,"stroke":66,"style":151},"150","122","162","stroke-width:2;marker-end:url(#iconArrow)",[146,153],{"x1":154,"y1":149,"x2":155,"y2":150,"stroke":66,"style":151},"530","400",[146,157],{"x1":158,"y1":149,"x2":140,"y2":150,"stroke":66,"style":151},"600",[160,161,163],"h2",{"id":162},"prerequisites","Prerequisites",[165,166,167,175,186],"ul",{},[168,169,170,174],"li",{},[171,172,173],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[168,176,177,178,182,183,185],{},"A plugin with ",[179,180,181],"code",{},"initGui()"," and a ",[179,184,90],{},".",[168,187,188,191],{},[179,189,190],{},"pyrcc5"," if you choose to use a compiled resource file; it ships with PyQt5 and with most QGIS installations.",[160,193,195],{"id":194},"the-plugin-manager-icon","The plugin manager icon",[197,198,203],"pre",{"className":199,"code":200,"language":201,"meta":202,"style":202},"language-ini shiki shiki-themes github-dark","[general]\nname=Parcel Tools\nicon=icon.png\n","ini","",[179,204,205,212,218],{"__ignoreMap":202},[206,207,209],"span",{"class":146,"line":208},1,[206,210,211],{},"[general]\n",[206,213,215],{"class":146,"line":214},2,[206,216,217],{},"name=Parcel Tools\n",[206,219,221],{"class":146,"line":220},3,[206,222,223],{},"icon=icon.png\n",[14,225,226,229,230,233],{},[171,227,228],{},"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 ",[179,231,232],{},"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.",[160,235,237],{"id":236},"load-icons-from-a-path","Load icons from a path",[14,239,240],{},"The simplest approach needs no build step at all:",[197,242,246],{"className":243,"code":244,"language":245,"meta":202,"style":202},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.PyQt.QtGui import QIcon\nfrom qgis.PyQt.QtWidgets import QAction\n\n\nclass ParcelTools:\n    def __init__(self, iface):\n        self.iface = iface\n        self.plugin_dir = os.path.dirname(__file__)\n        self.actions = []\n\n    def initGui(self):\n        icon = QIcon(os.path.join(self.plugin_dir, \"icons\", \"summarise.svg\"))\n        action = QAction(icon, self.tr(\"Summarise parcels\"), self.iface.mainWindow())\n        action.triggered.connect(self.run)\n\n        self.iface.addToolBarIcon(action)\n        self.iface.addPluginToVectorMenu(self.tr(\"Parcel Tools\"), action)\n        self.actions.append(action)\n","python",[179,247,248,258,271,283,290,295,308,321,336,355,368,373,384,414,441,452,457,465,483],{"__ignoreMap":202},[206,249,250,254],{"class":146,"line":208},[206,251,253],{"class":252},"snl16","import",[206,255,257],{"class":256},"s95oV"," os\n",[206,259,260,263,266,268],{"class":146,"line":214},[206,261,262],{"class":252},"from",[206,264,265],{"class":256}," qgis.PyQt.QtGui ",[206,267,253],{"class":252},[206,269,270],{"class":256}," QIcon\n",[206,272,273,275,278,280],{"class":146,"line":220},[206,274,262],{"class":252},[206,276,277],{"class":256}," qgis.PyQt.QtWidgets ",[206,279,253],{"class":252},[206,281,282],{"class":256}," QAction\n",[206,284,286],{"class":146,"line":285},4,[206,287,289],{"emptyLinePlaceholder":288},true,"\n",[206,291,293],{"class":146,"line":292},5,[206,294,289],{"emptyLinePlaceholder":288},[206,296,298,301,305],{"class":146,"line":297},6,[206,299,300],{"class":252},"class",[206,302,304],{"class":303},"svObZ"," ParcelTools",[206,306,307],{"class":256},":\n",[206,309,311,314,318],{"class":146,"line":310},7,[206,312,313],{"class":252},"    def",[206,315,317],{"class":316},"sDLfK"," __init__",[206,319,320],{"class":256},"(self, iface):\n",[206,322,324,327,330,333],{"class":146,"line":323},8,[206,325,326],{"class":316},"        self",[206,328,329],{"class":256},".iface ",[206,331,332],{"class":252},"=",[206,334,335],{"class":256}," iface\n",[206,337,339,341,344,346,349,352],{"class":146,"line":338},9,[206,340,326],{"class":316},[206,342,343],{"class":256},".plugin_dir ",[206,345,332],{"class":252},[206,347,348],{"class":256}," os.path.dirname(",[206,350,351],{"class":316},"__file__",[206,353,354],{"class":256},")\n",[206,356,358,360,363,365],{"class":146,"line":357},10,[206,359,326],{"class":316},[206,361,362],{"class":256},".actions ",[206,364,332],{"class":252},[206,366,367],{"class":256}," []\n",[206,369,371],{"class":146,"line":370},11,[206,372,289],{"emptyLinePlaceholder":288},[206,374,376,378,381],{"class":146,"line":375},12,[206,377,313],{"class":252},[206,379,380],{"class":303}," initGui",[206,382,383],{"class":256},"(self):\n",[206,385,387,390,392,395,398,401,405,408,411],{"class":146,"line":386},13,[206,388,389],{"class":256},"        icon ",[206,391,332],{"class":252},[206,393,394],{"class":256}," QIcon(os.path.join(",[206,396,397],{"class":316},"self",[206,399,400],{"class":256},".plugin_dir, ",[206,402,404],{"class":403},"sU2Wk","\"icons\"",[206,406,407],{"class":256},", ",[206,409,410],{"class":403},"\"summarise.svg\"",[206,412,413],{"class":256},"))\n",[206,415,417,420,422,425,427,430,433,436,438],{"class":146,"line":416},14,[206,418,419],{"class":256},"        action ",[206,421,332],{"class":252},[206,423,424],{"class":256}," QAction(icon, ",[206,426,397],{"class":316},[206,428,429],{"class":256},".tr(",[206,431,432],{"class":403},"\"Summarise parcels\"",[206,434,435],{"class":256},"), ",[206,437,397],{"class":316},[206,439,440],{"class":256},".iface.mainWindow())\n",[206,442,444,447,449],{"class":146,"line":443},15,[206,445,446],{"class":256},"        action.triggered.connect(",[206,448,397],{"class":316},[206,450,451],{"class":256},".run)\n",[206,453,455],{"class":146,"line":454},16,[206,456,289],{"emptyLinePlaceholder":288},[206,458,460,462],{"class":146,"line":459},17,[206,461,326],{"class":316},[206,463,464],{"class":256},".iface.addToolBarIcon(action)\n",[206,466,468,470,473,475,477,480],{"class":146,"line":467},18,[206,469,326],{"class":316},[206,471,472],{"class":256},".iface.addPluginToVectorMenu(",[206,474,397],{"class":316},[206,476,429],{"class":256},[206,478,479],{"class":403},"\"Parcel Tools\"",[206,481,482],{"class":256},"), action)\n",[206,484,486,488],{"class":146,"line":485},19,[206,487,326],{"class":316},[206,489,490],{"class":256},".actions.append(action)\n",[14,492,493,495,496,499,500,503,504,508,509,512],{},[171,494,228],{}," Building the path from ",[179,497,498],{},"os.path.dirname(__file__)"," is what makes it work regardless of where the plugin is installed — a relative path such as ",[179,501,502],{},"\"icons\u002Fsummarise.svg\""," resolves against the ",[505,506,507],"em",{},"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 ",[179,510,511],{},"unload()"," symmetrical, which matters as much for icons as for anything else.",[160,514,516],{"id":515},"or-compile-a-resources-file","Or compile a resources file",[14,518,519],{},"The alternative bundles images into a Python module and refers to them by a virtual path:",[197,521,525],{"className":522,"code":523,"language":524,"meta":202,"style":202},"language-bash shiki shiki-themes github-dark","pyrcc5 -o resources.py resources.qrc\n","bash",[179,526,527],{"__ignoreMap":202},[206,528,529,531,534,537],{"class":146,"line":208},[206,530,190],{"class":303},[206,532,533],{"class":316}," -o",[206,535,536],{"class":403}," resources.py",[206,538,539],{"class":403}," resources.qrc\n",[197,541,545],{"className":542,"code":543,"language":544,"meta":202,"style":202},"language-xml shiki shiki-themes github-dark","\u003C!-- resources.qrc -->\n\u003CRCC>\n  \u003Cqresource prefix=\"\u002Fplugins\u002Fparcel_tools\">\n    \u003Cfile>icons\u002Fsummarise.svg\u003C\u002Ffile>\n    \u003Cfile>icons\u002Fsplit.svg\u003C\u002Ffile>\n    \u003Cfile>icon.png\u003C\u002Ffile>\n  \u003C\u002Fqresource>\n\u003C\u002FRCC>\n","xml",[179,546,547,552,557,562,567,572,577,582],{"__ignoreMap":202},[206,548,549],{"class":146,"line":208},[206,550,551],{},"\u003C!-- resources.qrc -->\n",[206,553,554],{"class":146,"line":214},[206,555,556],{},"\u003CRCC>\n",[206,558,559],{"class":146,"line":220},[206,560,561],{},"  \u003Cqresource prefix=\"\u002Fplugins\u002Fparcel_tools\">\n",[206,563,564],{"class":146,"line":285},[206,565,566],{},"    \u003Cfile>icons\u002Fsummarise.svg\u003C\u002Ffile>\n",[206,568,569],{"class":146,"line":292},[206,570,571],{},"    \u003Cfile>icons\u002Fsplit.svg\u003C\u002Ffile>\n",[206,573,574],{"class":146,"line":297},[206,575,576],{},"    \u003Cfile>icon.png\u003C\u002Ffile>\n",[206,578,579],{"class":146,"line":310},[206,580,581],{},"  \u003C\u002Fqresource>\n",[206,583,584],{"class":146,"line":323},[206,585,586],{},"\u003C\u002FRCC>\n",[197,588,590],{"className":243,"code":589,"language":245,"meta":202,"style":202},"from . import resources          # noqa: F401 — registers the resources\nicon = QIcon(\":\u002Fplugins\u002Fparcel_tools\u002Ficons\u002Fsummarise.svg\")\n",[179,591,592,608],{"__ignoreMap":202},[206,593,594,596,599,601,604],{"class":146,"line":208},[206,595,262],{"class":252},[206,597,598],{"class":256}," . ",[206,600,253],{"class":252},[206,602,603],{"class":256}," resources          ",[206,605,607],{"class":606},"sjoCn","# noqa: F401 — registers the resources\n",[206,609,610,613,615,618,621],{"class":146,"line":214},[206,611,612],{"class":256},"icon ",[206,614,332],{"class":252},[206,616,617],{"class":256}," QIcon(",[206,619,620],{"class":403},"\":\u002Fplugins\u002Fparcel_tools\u002Ficons\u002Fsummarise.svg\"",[206,622,354],{"class":256},[14,624,625,627,628,630,631,634,635,638,639,642],{},[171,626,228],{}," ",[179,629,190],{}," turns the listed files into byte arrays inside ",[179,632,633],{},"resources.py",", and importing that module registers them with Qt under the declared prefix. The leading colon in the path is what tells ",[179,636,637],{},"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 ",[179,640,641],{},".qrc"," need not.",[14,644,645,646,649],{},"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 ",[179,647,648],{},".ui"," file references icons, since Designer writes resource paths rather than file paths.",[14,651,652],{},[29,653,656,659,662,665,668,678,683,686,690,694,698,703,708,710,714,716,720,723,727,729,732,734,737,740,744,748,751,755,758,762,766,768,771,773],{"viewBox":654,"role":32,"ariaLabel":655,"xmlns":34},"0 0 760 250","Comparison of file path icons against compiled resource icons across setup cost, editing, packaging and use in user interface files",[36,657,658],{},"Paths or resources",[40,660,661],{},"File 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.",[44,663],{"x":46,"y":46,"width":47,"height":664,"fill":49},"250",[68,666,667],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Neither is wrong; pick for how the plugin is built",[44,669],{"x":670,"y":671,"width":672,"height":673,"rx":674,"fill":675,"stroke":676,"style":677},"16","46","248","30","6","#efeadd","#59645f","stroke-width:1.5",[68,679,682],{"x":680,"y":681,"style":121,"fill":73,"textAnchor":74},"140","66","question",[44,684],{"x":48,"y":671,"width":685,"height":673,"rx":674,"fill":83,"stroke":66,"style":677},"228",[68,687,689],{"x":688,"y":681,"style":121,"fill":66,"textAnchor":74},"386","file paths",[44,691],{"x":692,"y":671,"width":693,"height":673,"rx":674,"fill":100,"stroke":101,"style":677},"508","236",[68,695,697],{"x":696,"y":681,"style":121,"fill":101,"textAnchor":74},"626","compiled resources",[44,699],{"x":670,"y":700,"width":672,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},"82","36","stroke-width:1.2",[68,704,707],{"x":673,"y":705,"style":706,"fill":95},"105","font-size:11px;font-family:sans-serif","build step needed?",[44,709],{"x":48,"y":700,"width":685,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},[68,711,713],{"x":712,"y":705,"style":706,"fill":95},"286","none",[44,715],{"x":692,"y":700,"width":693,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},[68,717,719],{"x":718,"y":705,"style":706,"fill":95},"522","pyrcc5 before packaging",[44,721],{"x":670,"y":722,"width":672,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},"124",[68,724,726],{"x":673,"y":725,"style":706,"fill":95},"147","changing an icon",[44,728],{"x":48,"y":722,"width":685,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},[68,730,731],{"x":712,"y":725,"style":706,"fill":95},"swap the file",[44,733],{"x":692,"y":722,"width":693,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},[68,735,736],{"x":718,"y":725,"style":706,"fill":95},"swap and recompile",[44,738],{"x":670,"y":739,"width":672,"height":701,"rx":674,"fill":115,"stroke":676,"style":702},"166",[68,741,743],{"x":673,"y":742,"style":706,"fill":95},"189","used by .ui files?",[44,745],{"x":48,"y":739,"width":685,"height":701,"rx":674,"fill":746,"stroke":747,"style":677},"#fdf2e2","#b45309",[68,749,750],{"x":712,"y":742,"style":706,"fill":95},"awkward",[44,752],{"x":692,"y":739,"width":693,"height":701,"rx":674,"fill":753,"stroke":754,"style":677},"#edf8e9","#15803d",[68,756,757],{"x":718,"y":742,"style":706,"fill":95},"what Designer writes",[44,759],{"x":670,"y":760,"width":672,"height":761,"rx":674,"fill":115,"stroke":676,"style":702},"208","34",[68,763,765],{"x":673,"y":764,"style":706,"fill":95},"230","packaging risk",[44,767],{"x":48,"y":760,"width":685,"height":761,"rx":674,"fill":115,"stroke":676,"style":702},[68,769,770],{"x":712,"y":764,"style":706,"fill":95},"forget the icons folder",[44,772],{"x":692,"y":760,"width":693,"height":761,"rx":674,"fill":115,"stroke":676,"style":702},[68,774,775],{"x":718,"y":764,"style":706,"fill":95},"forget to recompile",[160,777,779],{"id":778},"draw-icons-that-read-at-24-pixels","Draw icons that read at 24 pixels",[14,781,782],{},"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.",[14,784,785,788],{},[171,786,787],{},"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.",[14,790,791,794],{},[171,792,793],{},"Strong contrast, no fine detail."," Hairlines disappear and thin text becomes noise. Nothing smaller than about two pixels of stroke survives.",[14,796,797,800],{},[171,798,799],{},"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.",[14,802,803,806],{},[171,804,805],{},"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.",[14,808,809],{},[29,810,813,816,819,821,824,829,834,840,844,849,852,855,858,861,864,867,869,873,877,882,887,890,893,898],{"viewBox":811,"role":32,"ariaLabel":812,"xmlns":34},"0 0 760 248","Diagram showing the same icon at three sizes and against two toolbar backgrounds, illustrating which design choices survive",[36,814,815],{},"The same icon, three sizes and two themes",[40,817,818],{},"At 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.",[44,820],{"x":46,"y":46,"width":47,"height":672,"fill":49},[68,822,823],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Design for 24 pixels, then check both themes",[44,825],{"x":78,"y":826,"width":827,"height":828,"rx":82,"fill":115,"stroke":676,"style":116},"56","320","164",[68,830,833],{"x":831,"y":832,"style":121,"fill":73,"textAnchor":74},"200","80","the same icon, three sizes",[835,836],"circle",{"cx":93,"cy":837,"r":673,"fill":66,"fillOpacity":838,"stroke":66,"style":839},"130",0.3,"stroke-width:3",[63,841],{"d":842,"fill":713,"stroke":66,"style":843},"M84 130 L96 142 L118 118","stroke-width:4",[68,845,848],{"x":93,"y":846,"style":847,"fill":676,"textAnchor":74},"180","text-anchor:middle;font-size:10px;font-family:sans-serif","64 px",[835,850],{"cx":831,"cy":837,"r":851,"fill":66,"fillOpacity":838,"stroke":66,"style":116},"14",[63,853],{"d":854,"fill":713,"stroke":66,"style":84},"M193 130 L198 135 L208 124",[68,856,857],{"x":831,"y":846,"style":847,"fill":676,"textAnchor":74},"24 px — the toolbar",[835,859],{"cx":80,"cy":837,"r":860,"fill":66,"fillOpacity":838,"stroke":66,"style":677},"9",[63,862],{"d":863,"fill":713,"stroke":66,"style":116},"M295 130 L299 134 L306 126",[68,865,866],{"x":80,"y":846,"style":847,"fill":676,"textAnchor":74},"16 px",[44,868],{"x":155,"y":826,"width":827,"height":828,"rx":82,"fill":115,"stroke":676,"style":116},[68,870,872],{"x":871,"y":832,"style":121,"fill":73,"textAnchor":74},"560","two toolbar backgrounds",[44,874],{"x":875,"y":876,"width":48,"height":78,"rx":59,"fill":675,"stroke":676,"style":677},"424","96",[835,878],{"cx":879,"cy":880,"r":881,"fill":66,"fillOpacity":838,"stroke":66,"style":116},"452","116","11",[68,883,886],{"x":884,"y":885,"style":706,"fill":95},"484","120","light theme — visible",[44,888],{"x":875,"y":889,"width":885,"height":78,"rx":59,"fill":73,"stroke":676,"style":677},"148",[835,891],{"cx":884,"cy":112,"r":881,"fill":66,"fillOpacity":892,"stroke":66,"style":116},0.45,[68,894,897],{"x":895,"y":896,"style":706,"fill":95},"556","172","dark theme — still reads",[68,899,901],{"x":871,"y":900,"style":847,"fill":676,"textAnchor":74},"210","a near-black icon would vanish in the lower row",[197,903,905],{"className":243,"code":904,"language":245,"meta":202,"style":202},"from qgis.core import QgsSettings\n\ntheme = QgsSettings().value(\"UI\u002FUITheme\", \"default\", type=str)\nsuffix = \"_dark\" if theme.lower().startswith(\"night\") else \"\"\nicon = QIcon(os.path.join(self.plugin_dir, \"icons\", f\"summarise{suffix}.svg\"))\n",[179,906,907,919,923,954,982],{"__ignoreMap":202},[206,908,909,911,914,916],{"class":146,"line":208},[206,910,262],{"class":252},[206,912,913],{"class":256}," qgis.core ",[206,915,253],{"class":252},[206,917,918],{"class":256}," QgsSettings\n",[206,920,921],{"class":146,"line":214},[206,922,289],{"emptyLinePlaceholder":288},[206,924,925,928,930,933,936,938,941,943,947,949,952],{"class":146,"line":220},[206,926,927],{"class":256},"theme ",[206,929,332],{"class":252},[206,931,932],{"class":256}," QgsSettings().value(",[206,934,935],{"class":403},"\"UI\u002FUITheme\"",[206,937,407],{"class":256},[206,939,940],{"class":403},"\"default\"",[206,942,407],{"class":256},[206,944,946],{"class":945},"s9osk","type",[206,948,332],{"class":252},[206,950,951],{"class":316},"str",[206,953,354],{"class":256},[206,955,956,959,961,964,967,970,973,976,979],{"class":146,"line":285},[206,957,958],{"class":256},"suffix ",[206,960,332],{"class":252},[206,962,963],{"class":403}," \"_dark\"",[206,965,966],{"class":252}," if",[206,968,969],{"class":256}," theme.lower().startswith(",[206,971,972],{"class":403},"\"night\"",[206,974,975],{"class":256},") ",[206,977,978],{"class":252},"else",[206,980,981],{"class":403}," \"\"\n",[206,983,984,986,988,990,992,994,996,998,1001,1004,1007,1010,1013,1016],{"class":146,"line":292},[206,985,612],{"class":256},[206,987,332],{"class":252},[206,989,394],{"class":256},[206,991,397],{"class":316},[206,993,400],{"class":256},[206,995,404],{"class":403},[206,997,407],{"class":256},[206,999,1000],{"class":252},"f",[206,1002,1003],{"class":403},"\"summarise",[206,1005,1006],{"class":316},"{",[206,1008,1009],{"class":256},"suffix",[206,1011,1012],{"class":316},"}",[206,1014,1015],{"class":403},".svg\"",[206,1017,413],{"class":256},[14,1019,1020,1022],{},[171,1021,228],{}," 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.",[160,1024,1026],{"id":1025},"make-sure-the-icons-survive-packaging","Make sure the icons survive packaging",[14,1028,1029,1030,1034],{},"Two failures show up only after installing the built archive, which is why the clean-profile test in ",[21,1031,1033],{"href":1032},"\u002Fqgis-plugin-development\u002Fpublishing-to-the-qgis-plugin-repository\u002Fpackage-qgis-plugin-as-zip\u002F","Package a QGIS Plugin as a Zip"," matters.",[14,1036,1037,1040,1041,1044,1045,1048],{},[171,1038,1039],{},"A missing icons folder."," A build script excluding by suffix can easily drop ",[179,1042,1043],{},".svg"," or ",[179,1046,1047],{},".png"," files. Include them explicitly, and check the archive contents rather than assuming.",[14,1050,1051,1056,1057,1059],{},[171,1052,1053,1054,185],{},"A stale or missing ",[179,1055,633],{}," 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 ",[179,1058,633],{}," is sitting in the working folder will ship without it and lose every icon.",[14,1061,1062],{},"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.",[160,1064,1066],{"id":1065},"qgis-version-compatibility","QGIS version compatibility",[1068,1069,1070,1086],"table",{},[1071,1072,1073],"thead",{},[1074,1075,1076,1080,1083],"tr",{},[1077,1078,1079],"th",{},"QGIS version",[1077,1081,1082],{},"Python",[1077,1084,1085],{},"Notes",[1087,1088,1089,1106,1116,1127],"tbody",{},[1074,1090,1091,1095,1098],{},[1092,1093,1094],"td",{},"3.22 LTR",[1092,1096,1097],{},"3.9",[1092,1099,1100,1102,1103,1105],{},[179,1101,637],{}," from paths or resources; ",[179,1104,190],{}," from PyQt5.",[1074,1107,1108,1111,1113],{},[1092,1109,1110],{},"3.28 LTR",[1092,1112,1097],{},[1092,1114,1115],{},"Identical.",[1074,1117,1118,1121,1124],{},[1092,1119,1120],{},"3.34 LTR",[1092,1122,1123],{},"3.12",[1092,1125,1126],{},"Baseline for this page.",[1074,1128,1129,1132,1134],{},[1092,1130,1131],{},"3.40 \u002F 3.44",[1092,1133,1123],{},[1092,1135,1136,1137,1140],{},"Identical; builds against Qt 6 use ",[179,1138,1139],{},"pyside6-rcc"," or an equivalent, another reason to prefer plain file paths.",[14,1142,1143,1144,1146],{},"Qt 6 removes ",[179,1145,190],{},", so a plugin that wants to work across both bindings without a conditional build step is better off loading icons from paths.",[160,1148,1150],{"id":1149},"troubleshooting","Troubleshooting",[165,1152,1153,1165,1171,1179,1185,1191],{},[168,1154,1155,1158,1159,1161,1162,1164],{},[171,1156,1157],{},"The icon is blank in the plugin manager."," No ",[179,1160,232],{}," line in ",[179,1163,90],{},", or the path does not resolve inside the plugin folder.",[168,1166,1167,1170],{},[171,1168,1169],{},"Toolbar icons are missing after install."," A relative path resolved against the working directory, or the icons folder was excluded from the archive.",[168,1172,1173,1178],{},[171,1174,1175,185],{},[179,1176,1177],{},"ModuleNotFoundError: resources"," The compiled module was not generated or not packaged.",[168,1180,1181,1184],{},[171,1182,1183],{},"The icon is invisible on a dark theme."," Dark strokes on transparent. Use a mid-tone or ship a variant.",[168,1186,1187,1190],{},[171,1188,1189],{},"An SVG icon renders blurry."," It is not really an SVG — a bitmap embedded in an SVG wrapper scales like a bitmap.",[168,1192,1193,1196],{},[171,1194,1195],{},"The icon changed but QGIS shows the old one."," Qt caches icons per path; reload the plugin, and rebuild resources if you use them.",[160,1198,1200],{"id":1199},"conclusion","Conclusion",[14,1202,1203,1204,1206,1207,1209,1210,1212,1213,1215],{},"Point ",[179,1205,90],{}," at a square PNG for the plugin manager, and build ",[179,1208,637],{}," paths from ",[179,1211,498],{}," so they resolve wherever the plugin is installed. Prefer file paths unless a ",[179,1214,648],{}," 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.",[160,1217,1219],{"id":1218},"frequently-asked-questions","Frequently Asked Questions",[14,1221,1222,1225],{},[171,1223,1224],{},"What size should the plugin manager icon be?","\nSquare, 64 or 128 pixels. It is scaled down, so larger is safer than smaller.",[14,1227,1228,1231],{},[171,1229,1230],{},"Can I use an SVG for the manager icon?","\nPNG is the safe choice there; SVG is ideal for toolbar and menu icons where Qt renders at several sizes.",[14,1233,1234,1237],{},[171,1235,1236],{},"Do I have to use a resources file?","\nNo. It is a convention from the Plugin Builder template rather than a requirement, and file paths are simpler for most plugins.",[14,1239,1240,1243,1244,1247,1248,1250],{},[171,1241,1242],{},"Where should icons live in the plugin folder?","\nAn ",[179,1245,1246],{},"icons\u002F"," subfolder, with the manager icon at the top level where ",[179,1249,90],{}," points. Consistency matters more than the exact layout.",[14,1252,1253,1256,1257,1260],{},[171,1254,1255],{},"Can I reuse QGIS's own icons?","\nYes, through ",[179,1258,1259],{},"QgsApplication.getThemeIcon(\"\u002FmActionZoomIn.svg\")",", which follows the user's theme automatically. It is the best option when your action mirrors a built-in one.",[14,1262,1263,1266,1267,1270,1271,185],{},[171,1264,1265],{},"How do I add an icon to a Processing algorithm?","\nReturn one from the algorithm's ",[179,1268,1269],{},"icon()"," method, or from the provider's for the whole group — see ",[21,1272,1274],{"href":1273},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fregister-processing-provider-in-plugin\u002F","Register a Processing Provider in a Plugin",[160,1276,1278],{"id":1277},"related","Related",[165,1280,1281,1286,1292,1296,1302],{},[168,1282,1283,1285],{},[21,1284,24],{"href":23}," — the guide this recipe belongs to",[168,1287,1288],{},[21,1289,1291],{"href":1290},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002F","Add a Toolbar Button to a QGIS Plugin",[168,1293,1294],{},[21,1295,1033],{"href":1032},[168,1297,1298],{},[21,1299,1301],{"href":1300},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer\u002F","Use QGIS Custom Widgets in Qt Designer",[168,1303,1304],{},[21,1305,1307],{"href":1306},"\u002Fqgis-plugin-development\u002Fpublishing-to-the-qgis-plugin-repository\u002Fwrite-metadata-txt-qgis-plugin\u002F","Write metadata.txt for a QGIS Plugin",[1309,1310,1311],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":202,"searchDepth":214,"depth":214,"links":1313},[1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324],{"id":162,"depth":214,"text":163},{"id":194,"depth":214,"text":195},{"id":236,"depth":214,"text":237},{"id":515,"depth":214,"text":516},{"id":778,"depth":214,"text":779},{"id":1025,"depth":214,"text":1026},{"id":1065,"depth":214,"text":1066},{"id":1149,"depth":214,"text":1150},{"id":1199,"depth":214,"text":1200},{"id":1218,"depth":214,"text":1219},{"id":1277,"depth":214,"text":1278},"Give a QGIS plugin proper icons — the plugin manager icon, toolbar and menu icons, the Qt resource file and when to skip it, sizing and theme-aware icons, and keeping the build reproducible.","md",{"slug":1328,"type":1329,"breadcrumb":1330,"datePublished":1331,"dateModified":1331},"add-plugin-icon-and-resources-pyqgis","article","Icons & Resources","2026-08-15","\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-plugin-icon-and-resources-pyqgis",{"title":5,"description":1325},"qgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-plugin-icon-and-resources-pyqgis\u002Findex","C7xOBBG_UMRqgv3IIRDJSe-9nJ1WKRMuCG6ELRrbmKc",1786789584640]