[{"data":1,"prerenderedAt":2281},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin":3},{"id":4,"title":5,"body":6,"description":2269,"extension":2270,"meta":2271,"navigation":456,"path":2277,"seo":2278,"stem":2279,"__hash__":2280},"docs\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002Findex.md","Add a Toolbar Button to a QGIS Plugin",{"type":7,"value":8,"toc":2255},"minimark",[9,13,42,56,61,94,98,131,377,381,390,894,940,944,956,1402,1435,1439,1446,1701,1731,1735,1741,1863,1867,1870,1958,1962,1970,2051,2054,2058,2073,2090,2103,2116,2136,2140,2163,2167,2188,2194,2202,2216,2233,2237,2251],[10,11,5],"h1",{"id":12},"add-a-toolbar-button-to-a-qgis-plugin",[14,15,16,17,21,22,25,26,29,30,33,34,37,38,41],"p",{},"A toolbar button is the most direct way for users to reach your plugin. In PyQGIS it is a ",[18,19,20],"code",{},"QAction"," carrying a ",[18,23,24],{},"QIcon",", registered with the QGIS interface during ",[18,27,28],{},"initGui()",", connected to a callback through its ",[18,31,32],{},"triggered"," signal, and — critically — removed again during ",[18,35,36],{},"unload()",". Getting the lifecycle symmetric is what separates a plugin that disables cleanly from one that leaves ghost buttons behind. This page shows the full pattern: a single icon on the main plugin toolbar, the same action mirrored into a plugin menu, and an optional dedicated ",[18,39,40],{},"QToolBar"," for plugins that expose several tools.",[14,43,44,45,50,51,55],{},"This recipe is part of ",[46,47,49],"a",{"href":48},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate & Structure",". If you have not yet scaffolded a plugin, generate one first with ",[46,52,54],{"href":53},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder\u002F","Create a QGIS Plugin with Plugin Builder 3"," — the code below drops straight into the main class it produces.",[57,58,60],"h2",{"id":59},"prerequisites","Prerequisites",[62,63,64,72,82,88],"ul",{},[65,66,67,71],"li",{},[68,69,70],"strong",{},"QGIS 3.34 LTR"," (Python 3.12) with the Python console enabled",[65,73,74,75,78,79],{},"An existing plugin skeleton exposing a ",[18,76,77],{},"classFactory"," and a main class that receives ",[18,80,81],{},"iface",[65,83,84,85],{},"An icon file (PNG or SVG) reachable from your plugin directory, or compiled into a ",[18,86,87],{},".qrc",[65,89,90,91,93],{},"Familiarity with Qt's signal\u002Fslot mechanism — buttons fire ",[18,92,32],{},", you connect a slot",[57,95,97],{"id":96},"how-the-interface-registers-buttons","How the Interface Registers Buttons",[14,99,100,101,103,104,107,108,111,112,115,116,119,120,123,124,126,127,130],{},"The ",[18,102,81],{}," object (a ",[18,105,106],{},"QgisInterface",") is your single entry point. It offers two relevant placements: a slot on the shared ",[68,109,110],{},"Plugins toolbar"," via ",[18,113,114],{},"addToolBarIcon",", and a ",[68,117,118],{},"plugin menu"," entry via ",[18,121,122],{},"addPluginToMenu",". For multiple related actions you can instead create your own ",[18,125,40],{}," with ",[18,128,129],{},"addToolBar",". The diagram below shows what each call adds and what must be torn down.",[132,133,138,139,138,143,138,147,138,176,138,183,138,192,138,197,138,201,138,208,138,214,138,219,138,225,138,229,138,233,138,237,138,240,138,244,138,252,138,257,138,265,138,271,138,274,138,277,138,284,138,287,138,290,138,293,138,296,138,301,138,308,138,312,138,318,138,323,138,330,138,335,138,340,138,344,138,347,138,350,138,352,138,355,138,360,138,364,138,368,138,372],"svg",{"viewBox":134,"role":135,"ariaLabel":136,"xmlns":137},"0 0 740 372","img","Three-column diagram: initGui registers a QAction, which appears as an icon on the shared Plugins toolbar and an entry in the Plugins menu of the QGIS main window, and unload removes each placement with a matching call","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[140,141,142],"title",{},"Registering a toolbar button in initGui and removing it in unload",[144,145,146],"desc",{},"In initGui a QAction is built and registered with addToolBarIcon and addPluginToMenu, which make the button appear on the shared Plugins toolbar and in the Plugins menu of the QGIS main window. In unload each placement is reversed by a matching removeToolBarIcon and removePluginMenu call, so no orphaned button is left behind.",[148,149,150,151,150,164,150,170,138],"defs",{},"\n    ",[152,153,159],"marker",{"id":154,"markerWidth":155,"markerHeight":155,"refX":156,"refY":157,"orient":158},"tbSeq","9","7","4.5","auto",[160,161],"path",{"d":162,"fill":163},"M0,0 L9,4.5 L0,9 z","#2f3b35",[152,165,167],{"id":166,"markerWidth":155,"markerHeight":155,"refX":156,"refY":157,"orient":158},"tbAdd",[160,168],{"d":162,"fill":169},"#15803d",[152,171,173],{"id":172,"markerWidth":155,"markerHeight":155,"refX":156,"refY":157,"orient":158},"tbRem",[160,174],{"d":162,"fill":175},"#b45309",[177,178],"rect",{"x":179,"y":179,"width":180,"height":181,"fill":182},"0","740","372","#f6f3ea",[184,185,191],"text",{"x":186,"y":187,"fill":188,"style":189,"textAnchor":190},"126","32","#0f766e","text-anchor:middle;font-family:sans-serif;font-size:14px;font-weight:bold","middle","initGui()  ·  register",[184,193,196],{"x":194,"y":187,"fill":195,"style":189,"textAnchor":190},"370","#17211d","QGIS main window",[184,198,200],{"x":199,"y":187,"fill":175,"style":189,"textAnchor":190},"614","unload()  ·  remove",[177,202],{"x":203,"y":204,"width":205,"height":204,"rx":206,"fill":207},"24","52","204","6","#26322d",[184,209,213],{"x":186,"y":210,"fill":211,"style":212,"textAnchor":190},"76","#d9f99d","text-anchor:middle;font-family:monospace;font-size:11.5px","action = QAction(icon, …)",[184,215,218],{"x":186,"y":216,"fill":211,"style":217,"textAnchor":190},"94","text-anchor:middle;font-family:monospace;font-size:11px","action.triggered.connect(run)",[220,221],"line",{"x1":186,"y1":222,"x2":186,"y2":223,"stroke":163,"style":224},"104","132","stroke-width:2;marker-end:url(#tbSeq)",[177,226],{"x":203,"y":227,"width":205,"height":228,"rx":206,"fill":207},"134","42",[184,230,232],{"x":186,"y":231,"fill":211,"style":212,"textAnchor":190},"160","addToolBarIcon(action)",[220,234],{"x1":186,"y1":235,"x2":186,"y2":236,"stroke":163,"style":224},"176","212",[177,238],{"x":203,"y":239,"width":205,"height":228,"rx":206,"fill":207},"214",[184,241,243],{"x":186,"y":242,"fill":211,"style":217,"textAnchor":190},"240","addPluginToMenu(menu, action)",[177,245],{"x":246,"y":204,"width":205,"height":247,"rx":248,"fill":249,"stroke":250,"style":251},"268","280","10","#fffdf7","#2563eb","stroke-width:2.5",[184,253,256],{"x":194,"y":254,"fill":163,"style":255,"textAnchor":190},"92","text-anchor:middle;font-family:sans-serif;font-size:11px","shared Plugins toolbar",[177,258],{"x":259,"y":260,"width":261,"height":262,"rx":263,"fill":182,"stroke":163,"style":264},"286","100","168","30","4","stroke-width:1",[177,266],{"x":267,"y":268,"width":269,"height":269,"rx":270,"fill":249,"stroke":163,"style":264},"292","105","20","3",[177,272],{"x":273,"y":268,"width":269,"height":269,"rx":270,"fill":249,"stroke":163,"style":264},"322",[177,275],{"x":276,"y":268,"width":269,"height":269,"rx":270,"fill":250,"stroke":250,"style":264},"352",[278,279],"circle",{"cx":280,"cy":281,"r":206,"fill":282,"stroke":249,"style":283},"362","115","none","stroke-width:1.6",[278,285],{"cx":280,"cy":281,"r":286,"fill":249},"2.4",[177,288],{"x":289,"y":268,"width":269,"height":269,"rx":270,"fill":249,"stroke":163,"style":264},"382",[177,291],{"x":292,"y":268,"width":269,"height":269,"rx":270,"fill":249,"stroke":163,"style":264},"412",[184,294,295],{"x":194,"y":235,"fill":163,"style":255,"textAnchor":190},"Plugins ▸ menu",[177,297],{"x":298,"y":299,"width":231,"height":300,"rx":263,"fill":249,"stroke":163,"style":264},"290","186","120",[184,302,307],{"x":303,"y":304,"fill":195,"style":305,"textAnchor":306},"302","210","text-anchor:start;font-family:monospace;font-size:11px","start","Plugins ▾",[184,309,311],{"x":303,"y":310,"fill":195,"style":305,"textAnchor":306},"234","  Buffer Tool ▸",[177,313],{"x":314,"y":315,"width":316,"height":317,"rx":270,"fill":250},"296","246","148","22",[184,319,322],{"x":320,"y":321,"fill":249,"style":305,"textAnchor":306},"306","261","Buffer active layer",[220,324],{"x1":325,"y1":326,"x2":327,"y2":328,"stroke":169,"style":329},"228","155","284","118","stroke-width:2.2;marker-end:url(#tbAdd)",[220,331],{"x1":325,"y1":332,"x2":333,"y2":334,"stroke":169,"style":329},"235","288","232",[184,336,339],{"x":337,"y":223,"fill":169,"style":338,"textAnchor":190},"252","text-anchor:middle;font-family:sans-serif;font-size:11px;font-weight:bold","+ add",[184,341,339],{"x":342,"y":343,"fill":169,"style":338,"textAnchor":190},"256","218",[177,345],{"x":346,"y":227,"width":205,"height":228,"rx":206,"fill":207},"512",[184,348,349],{"x":199,"y":231,"fill":211,"style":217,"textAnchor":190},"removeToolBarIcon(action)",[177,351],{"x":346,"y":239,"width":205,"height":228,"rx":206,"fill":207},[184,353,354],{"x":199,"y":242,"fill":211,"style":217,"textAnchor":190},"removePluginMenu(menu, action)",[220,356],{"x1":357,"y1":328,"x2":346,"y2":358,"stroke":175,"style":359},"456","152","stroke-width:2.2;marker-end:url(#tbRem)",[220,361],{"x1":362,"y1":363,"x2":346,"y2":332,"stroke":175,"style":359},"452","255",[184,365,367],{"x":366,"y":223,"fill":175,"style":338,"textAnchor":190},"486","− remove",[184,369,367],{"x":370,"y":371,"fill":175,"style":338,"textAnchor":190},"484","278",[184,373,376],{"x":194,"y":374,"fill":163,"style":375,"textAnchor":190},"358","text-anchor:middle;font-family:sans-serif;font-size:11.5px","Every add in initGui() has a matching remove in unload() — no orphaned button after teardown.",[57,378,380],{"id":379},"recipe-1-a-single-button-on-the-plugins-toolbar","Recipe 1: A Single Button on the Plugins Toolbar",[14,382,383,384,386,387,389],{},"The simplest case adds one icon to the shared QGIS Plugins toolbar and the same command to a plugin menu. Store the ",[18,385,20],{}," so ",[18,388,36],{}," can find it later.",[391,392,397],"pre",{"className":393,"code":394,"language":395,"meta":396,"style":396},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.PyQt.QtGui import QIcon\nfrom qgis.PyQt.QtWidgets import QAction\nfrom qgis.core import Qgis\n\n\nclass BufferTool:\n    def __init__(self, iface):\n        self.iface = iface\n        self.plugin_dir = os.path.dirname(__file__)\n        self.menu = \"&Buffer Tool\"\n        self.actions = []\n\n    def initGui(self):\n        icon = QIcon(os.path.join(self.plugin_dir, \"icons\", \"buffer.svg\"))\n        action = QAction(icon, \"Buffer active layer\", self.iface.mainWindow())\n        action.setStatusTip(\"Create a buffer around the active vector layer\")\n        action.triggered.connect(self.run)\n\n        self.iface.addToolBarIcon(action)\n        self.iface.addPluginToMenu(self.menu, action)\n        self.actions.append(action)\n\n    def unload(self):\n        for action in self.actions:\n            self.iface.removeToolBarIcon(action)\n            self.iface.removePluginMenu(self.menu, action)\n        self.actions.clear()\n\n    def run(self):\n        layer = self.iface.activeLayer()\n        if layer is None:\n            self.iface.messageBar().pushMessage(\n                \"Buffer Tool\", \"Select a layer first.\",\n                level=Qgis.Warning, duration=4,\n            )\n            return\n        self.iface.messageBar().pushMessage(\n            \"Buffer Tool\", f\"Buffering {layer.name()}...\",\n            level=Qgis.Info, duration=3,\n        )\n","python","",[18,398,399,411,425,438,451,458,463,476,489,504,523,537,550,555,566,595,616,627,638,643,651,664,672,677,687,705,714,726,734,739,749,762,779,787,801,822,828,834,841,869,888],{"__ignoreMap":396},[400,401,403,407],"span",{"class":220,"line":402},1,[400,404,406],{"class":405},"snl16","import",[400,408,410],{"class":409},"s95oV"," os\n",[400,412,414,417,420,422],{"class":220,"line":413},2,[400,415,416],{"class":405},"from",[400,418,419],{"class":409}," qgis.PyQt.QtGui ",[400,421,406],{"class":405},[400,423,424],{"class":409}," QIcon\n",[400,426,428,430,433,435],{"class":220,"line":427},3,[400,429,416],{"class":405},[400,431,432],{"class":409}," qgis.PyQt.QtWidgets ",[400,434,406],{"class":405},[400,436,437],{"class":409}," QAction\n",[400,439,441,443,446,448],{"class":220,"line":440},4,[400,442,416],{"class":405},[400,444,445],{"class":409}," qgis.core ",[400,447,406],{"class":405},[400,449,450],{"class":409}," Qgis\n",[400,452,454],{"class":220,"line":453},5,[400,455,457],{"emptyLinePlaceholder":456},true,"\n",[400,459,461],{"class":220,"line":460},6,[400,462,457],{"emptyLinePlaceholder":456},[400,464,466,469,473],{"class":220,"line":465},7,[400,467,468],{"class":405},"class",[400,470,472],{"class":471},"svObZ"," BufferTool",[400,474,475],{"class":409},":\n",[400,477,479,482,486],{"class":220,"line":478},8,[400,480,481],{"class":405},"    def",[400,483,485],{"class":484},"sDLfK"," __init__",[400,487,488],{"class":409},"(self, iface):\n",[400,490,492,495,498,501],{"class":220,"line":491},9,[400,493,494],{"class":484},"        self",[400,496,497],{"class":409},".iface ",[400,499,500],{"class":405},"=",[400,502,503],{"class":409}," iface\n",[400,505,507,509,512,514,517,520],{"class":220,"line":506},10,[400,508,494],{"class":484},[400,510,511],{"class":409},".plugin_dir ",[400,513,500],{"class":405},[400,515,516],{"class":409}," os.path.dirname(",[400,518,519],{"class":484},"__file__",[400,521,522],{"class":409},")\n",[400,524,526,528,531,533],{"class":220,"line":525},11,[400,527,494],{"class":484},[400,529,530],{"class":409},".menu ",[400,532,500],{"class":405},[400,534,536],{"class":535},"sU2Wk"," \"&Buffer Tool\"\n",[400,538,540,542,545,547],{"class":220,"line":539},12,[400,541,494],{"class":484},[400,543,544],{"class":409},".actions ",[400,546,500],{"class":405},[400,548,549],{"class":409}," []\n",[400,551,553],{"class":220,"line":552},13,[400,554,457],{"emptyLinePlaceholder":456},[400,556,558,560,563],{"class":220,"line":557},14,[400,559,481],{"class":405},[400,561,562],{"class":471}," initGui",[400,564,565],{"class":409},"(self):\n",[400,567,569,572,574,577,580,583,586,589,592],{"class":220,"line":568},15,[400,570,571],{"class":409},"        icon ",[400,573,500],{"class":405},[400,575,576],{"class":409}," QIcon(os.path.join(",[400,578,579],{"class":484},"self",[400,581,582],{"class":409},".plugin_dir, ",[400,584,585],{"class":535},"\"icons\"",[400,587,588],{"class":409},", ",[400,590,591],{"class":535},"\"buffer.svg\"",[400,593,594],{"class":409},"))\n",[400,596,598,601,603,606,609,611,613],{"class":220,"line":597},16,[400,599,600],{"class":409},"        action ",[400,602,500],{"class":405},[400,604,605],{"class":409}," QAction(icon, ",[400,607,608],{"class":535},"\"Buffer active layer\"",[400,610,588],{"class":409},[400,612,579],{"class":484},[400,614,615],{"class":409},".iface.mainWindow())\n",[400,617,619,622,625],{"class":220,"line":618},17,[400,620,621],{"class":409},"        action.setStatusTip(",[400,623,624],{"class":535},"\"Create a buffer around the active vector layer\"",[400,626,522],{"class":409},[400,628,630,633,635],{"class":220,"line":629},18,[400,631,632],{"class":409},"        action.triggered.connect(",[400,634,579],{"class":484},[400,636,637],{"class":409},".run)\n",[400,639,641],{"class":220,"line":640},19,[400,642,457],{"emptyLinePlaceholder":456},[400,644,646,648],{"class":220,"line":645},20,[400,647,494],{"class":484},[400,649,650],{"class":409},".iface.addToolBarIcon(action)\n",[400,652,654,656,659,661],{"class":220,"line":653},21,[400,655,494],{"class":484},[400,657,658],{"class":409},".iface.addPluginToMenu(",[400,660,579],{"class":484},[400,662,663],{"class":409},".menu, action)\n",[400,665,667,669],{"class":220,"line":666},22,[400,668,494],{"class":484},[400,670,671],{"class":409},".actions.append(action)\n",[400,673,675],{"class":220,"line":674},23,[400,676,457],{"emptyLinePlaceholder":456},[400,678,680,682,685],{"class":220,"line":679},24,[400,681,481],{"class":405},[400,683,684],{"class":471}," unload",[400,686,565],{"class":409},[400,688,690,693,696,699,702],{"class":220,"line":689},25,[400,691,692],{"class":405},"        for",[400,694,695],{"class":409}," action ",[400,697,698],{"class":405},"in",[400,700,701],{"class":484}," self",[400,703,704],{"class":409},".actions:\n",[400,706,708,711],{"class":220,"line":707},26,[400,709,710],{"class":484},"            self",[400,712,713],{"class":409},".iface.removeToolBarIcon(action)\n",[400,715,717,719,722,724],{"class":220,"line":716},27,[400,718,710],{"class":484},[400,720,721],{"class":409},".iface.removePluginMenu(",[400,723,579],{"class":484},[400,725,663],{"class":409},[400,727,729,731],{"class":220,"line":728},28,[400,730,494],{"class":484},[400,732,733],{"class":409},".actions.clear()\n",[400,735,737],{"class":220,"line":736},29,[400,738,457],{"emptyLinePlaceholder":456},[400,740,742,744,747],{"class":220,"line":741},30,[400,743,481],{"class":405},[400,745,746],{"class":471}," run",[400,748,565],{"class":409},[400,750,752,755,757,759],{"class":220,"line":751},31,[400,753,754],{"class":409},"        layer ",[400,756,500],{"class":405},[400,758,701],{"class":484},[400,760,761],{"class":409},".iface.activeLayer()\n",[400,763,765,768,771,774,777],{"class":220,"line":764},32,[400,766,767],{"class":405},"        if",[400,769,770],{"class":409}," layer ",[400,772,773],{"class":405},"is",[400,775,776],{"class":484}," None",[400,778,475],{"class":409},[400,780,782,784],{"class":220,"line":781},33,[400,783,710],{"class":484},[400,785,786],{"class":409},".iface.messageBar().pushMessage(\n",[400,788,790,793,795,798],{"class":220,"line":789},34,[400,791,792],{"class":535},"                \"Buffer Tool\"",[400,794,588],{"class":409},[400,796,797],{"class":535},"\"Select a layer first.\"",[400,799,800],{"class":409},",\n",[400,802,804,808,810,813,816,818,820],{"class":220,"line":803},35,[400,805,807],{"class":806},"s9osk","                level",[400,809,500],{"class":405},[400,811,812],{"class":409},"Qgis.Warning, ",[400,814,815],{"class":806},"duration",[400,817,500],{"class":405},[400,819,263],{"class":484},[400,821,800],{"class":409},[400,823,825],{"class":220,"line":824},36,[400,826,827],{"class":409},"            )\n",[400,829,831],{"class":220,"line":830},37,[400,832,833],{"class":405},"            return\n",[400,835,837,839],{"class":220,"line":836},38,[400,838,494],{"class":484},[400,840,786],{"class":409},[400,842,844,847,849,852,855,858,861,864,867],{"class":220,"line":843},39,[400,845,846],{"class":535},"            \"Buffer Tool\"",[400,848,588],{"class":409},[400,850,851],{"class":405},"f",[400,853,854],{"class":535},"\"Buffering ",[400,856,857],{"class":484},"{",[400,859,860],{"class":409},"layer.name()",[400,862,863],{"class":484},"}",[400,865,866],{"class":535},"...\"",[400,868,800],{"class":409},[400,870,872,875,877,880,882,884,886],{"class":220,"line":871},40,[400,873,874],{"class":806},"            level",[400,876,500],{"class":405},[400,878,879],{"class":409},"Qgis.Info, ",[400,881,815],{"class":806},[400,883,500],{"class":405},[400,885,270],{"class":484},[400,887,800],{"class":409},[400,889,891],{"class":220,"line":890},41,[400,892,893],{"class":409},"        )\n",[14,895,896,899,900,903,904,907,908,911,912,915,916,918,919,921,922,925,926,588,929,931,932,935,936,939],{},[68,897,898],{},"Breakdown:"," ",[18,901,902],{},"QAction(icon, text, parent)"," builds the clickable command; passing ",[18,905,906],{},"self.iface.mainWindow()"," as the parent ties its Qt lifetime to the main window. ",[18,909,910],{},"triggered.connect(self.run)"," fires ",[18,913,914],{},"run()"," on every click. ",[18,917,114],{}," places it on the shared toolbar; ",[18,920,122],{}," mirrors it into a named submenu under ",[18,923,924],{},"Plugins",". Because the action is kept in ",[18,927,928],{},"self.actions",[18,930,36],{}," can reverse both registrations — ",[18,933,934],{},"removeToolBarIcon"," and ",[18,937,938],{},"removePluginMenu"," must each be called, or the orphaned widget lingers after the plugin is disabled.",[57,941,943],{"id":942},"recipe-2-a-dedicated-custom-toolbar","Recipe 2: A Dedicated Custom Toolbar",[14,945,946,947,949,950,952,953,955],{},"When a plugin ships several related tools, a dedicated ",[18,948,40],{}," groups them and lets users dock or hide the set as a unit. Create it once in ",[18,951,28],{}," and destroy it in ",[18,954,36],{},".",[391,957,959],{"className":393,"code":958,"language":395,"meta":396,"style":396},"import os\nfrom qgis.PyQt.QtGui import QIcon\nfrom qgis.PyQt.QtWidgets import QAction\n\n\nclass GeometryToolkit:\n    def __init__(self, iface):\n        self.iface = iface\n        self.plugin_dir = os.path.dirname(__file__)\n        self.menu = \"&Geometry Toolkit\"\n        self.actions = []\n        self.toolbar = None\n\n    def _make_action(self, icon_name, text, callback):\n        icon = QIcon(os.path.join(self.plugin_dir, \"icons\", icon_name))\n        action = QAction(icon, text, self.iface.mainWindow())\n        action.triggered.connect(callback)\n        self.toolbar.addAction(action)\n        self.iface.addPluginToMenu(self.menu, action)\n        self.actions.append(action)\n        return action\n\n    def initGui(self):\n        self.toolbar = self.iface.addToolBar(\"Geometry Toolkit\")\n        self.toolbar.setObjectName(\"GeometryToolkit\")  # required for state persistence\n        self._make_action(\"centroid.svg\", \"Centroids\", self.run_centroids)\n        self._make_action(\"simplify.svg\", \"Simplify\", self.run_simplify)\n\n    def unload(self):\n        for action in self.actions:\n            self.iface.removePluginMenu(self.menu, action)\n            self.toolbar.removeAction(action)\n        self.actions.clear()\n        if self.toolbar is not None:\n            del self.toolbar  # removes the toolbar widget from the main window\n            self.toolbar = None\n\n    def run_centroids(self):\n        self.iface.messageBar().pushMessage(\"Toolkit\", \"Centroids tool\", duration=2)\n\n    def run_simplify(self):\n        self.iface.messageBar().pushMessage(\"Toolkit\", \"Simplify tool\", duration=2)\n",[18,960,961,967,977,987,991,995,1004,1012,1022,1036,1047,1057,1069,1073,1083,1100,1113,1118,1125,1135,1141,1149,1153,1161,1179,1196,1218,1239,1243,1251,1263,1273,1280,1286,1303,1316,1326,1330,1339,1365,1369,1378],{"__ignoreMap":396},[400,962,963,965],{"class":220,"line":402},[400,964,406],{"class":405},[400,966,410],{"class":409},[400,968,969,971,973,975],{"class":220,"line":413},[400,970,416],{"class":405},[400,972,419],{"class":409},[400,974,406],{"class":405},[400,976,424],{"class":409},[400,978,979,981,983,985],{"class":220,"line":427},[400,980,416],{"class":405},[400,982,432],{"class":409},[400,984,406],{"class":405},[400,986,437],{"class":409},[400,988,989],{"class":220,"line":440},[400,990,457],{"emptyLinePlaceholder":456},[400,992,993],{"class":220,"line":453},[400,994,457],{"emptyLinePlaceholder":456},[400,996,997,999,1002],{"class":220,"line":460},[400,998,468],{"class":405},[400,1000,1001],{"class":471}," GeometryToolkit",[400,1003,475],{"class":409},[400,1005,1006,1008,1010],{"class":220,"line":465},[400,1007,481],{"class":405},[400,1009,485],{"class":484},[400,1011,488],{"class":409},[400,1013,1014,1016,1018,1020],{"class":220,"line":478},[400,1015,494],{"class":484},[400,1017,497],{"class":409},[400,1019,500],{"class":405},[400,1021,503],{"class":409},[400,1023,1024,1026,1028,1030,1032,1034],{"class":220,"line":491},[400,1025,494],{"class":484},[400,1027,511],{"class":409},[400,1029,500],{"class":405},[400,1031,516],{"class":409},[400,1033,519],{"class":484},[400,1035,522],{"class":409},[400,1037,1038,1040,1042,1044],{"class":220,"line":506},[400,1039,494],{"class":484},[400,1041,530],{"class":409},[400,1043,500],{"class":405},[400,1045,1046],{"class":535}," \"&Geometry Toolkit\"\n",[400,1048,1049,1051,1053,1055],{"class":220,"line":525},[400,1050,494],{"class":484},[400,1052,544],{"class":409},[400,1054,500],{"class":405},[400,1056,549],{"class":409},[400,1058,1059,1061,1064,1066],{"class":220,"line":539},[400,1060,494],{"class":484},[400,1062,1063],{"class":409},".toolbar ",[400,1065,500],{"class":405},[400,1067,1068],{"class":484}," None\n",[400,1070,1071],{"class":220,"line":552},[400,1072,457],{"emptyLinePlaceholder":456},[400,1074,1075,1077,1080],{"class":220,"line":557},[400,1076,481],{"class":405},[400,1078,1079],{"class":471}," _make_action",[400,1081,1082],{"class":409},"(self, icon_name, text, callback):\n",[400,1084,1085,1087,1089,1091,1093,1095,1097],{"class":220,"line":568},[400,1086,571],{"class":409},[400,1088,500],{"class":405},[400,1090,576],{"class":409},[400,1092,579],{"class":484},[400,1094,582],{"class":409},[400,1096,585],{"class":535},[400,1098,1099],{"class":409},", icon_name))\n",[400,1101,1102,1104,1106,1109,1111],{"class":220,"line":597},[400,1103,600],{"class":409},[400,1105,500],{"class":405},[400,1107,1108],{"class":409}," QAction(icon, text, ",[400,1110,579],{"class":484},[400,1112,615],{"class":409},[400,1114,1115],{"class":220,"line":618},[400,1116,1117],{"class":409},"        action.triggered.connect(callback)\n",[400,1119,1120,1122],{"class":220,"line":629},[400,1121,494],{"class":484},[400,1123,1124],{"class":409},".toolbar.addAction(action)\n",[400,1126,1127,1129,1131,1133],{"class":220,"line":640},[400,1128,494],{"class":484},[400,1130,658],{"class":409},[400,1132,579],{"class":484},[400,1134,663],{"class":409},[400,1136,1137,1139],{"class":220,"line":645},[400,1138,494],{"class":484},[400,1140,671],{"class":409},[400,1142,1143,1146],{"class":220,"line":653},[400,1144,1145],{"class":405},"        return",[400,1147,1148],{"class":409}," action\n",[400,1150,1151],{"class":220,"line":666},[400,1152,457],{"emptyLinePlaceholder":456},[400,1154,1155,1157,1159],{"class":220,"line":674},[400,1156,481],{"class":405},[400,1158,562],{"class":471},[400,1160,565],{"class":409},[400,1162,1163,1165,1167,1169,1171,1174,1177],{"class":220,"line":679},[400,1164,494],{"class":484},[400,1166,1063],{"class":409},[400,1168,500],{"class":405},[400,1170,701],{"class":484},[400,1172,1173],{"class":409},".iface.addToolBar(",[400,1175,1176],{"class":535},"\"Geometry Toolkit\"",[400,1178,522],{"class":409},[400,1180,1181,1183,1186,1189,1192],{"class":220,"line":689},[400,1182,494],{"class":484},[400,1184,1185],{"class":409},".toolbar.setObjectName(",[400,1187,1188],{"class":535},"\"GeometryToolkit\"",[400,1190,1191],{"class":409},")  ",[400,1193,1195],{"class":1194},"sjoCn","# required for state persistence\n",[400,1197,1198,1200,1203,1206,1208,1211,1213,1215],{"class":220,"line":707},[400,1199,494],{"class":484},[400,1201,1202],{"class":409},"._make_action(",[400,1204,1205],{"class":535},"\"centroid.svg\"",[400,1207,588],{"class":409},[400,1209,1210],{"class":535},"\"Centroids\"",[400,1212,588],{"class":409},[400,1214,579],{"class":484},[400,1216,1217],{"class":409},".run_centroids)\n",[400,1219,1220,1222,1224,1227,1229,1232,1234,1236],{"class":220,"line":716},[400,1221,494],{"class":484},[400,1223,1202],{"class":409},[400,1225,1226],{"class":535},"\"simplify.svg\"",[400,1228,588],{"class":409},[400,1230,1231],{"class":535},"\"Simplify\"",[400,1233,588],{"class":409},[400,1235,579],{"class":484},[400,1237,1238],{"class":409},".run_simplify)\n",[400,1240,1241],{"class":220,"line":728},[400,1242,457],{"emptyLinePlaceholder":456},[400,1244,1245,1247,1249],{"class":220,"line":736},[400,1246,481],{"class":405},[400,1248,684],{"class":471},[400,1250,565],{"class":409},[400,1252,1253,1255,1257,1259,1261],{"class":220,"line":741},[400,1254,692],{"class":405},[400,1256,695],{"class":409},[400,1258,698],{"class":405},[400,1260,701],{"class":484},[400,1262,704],{"class":409},[400,1264,1265,1267,1269,1271],{"class":220,"line":751},[400,1266,710],{"class":484},[400,1268,721],{"class":409},[400,1270,579],{"class":484},[400,1272,663],{"class":409},[400,1274,1275,1277],{"class":220,"line":764},[400,1276,710],{"class":484},[400,1278,1279],{"class":409},".toolbar.removeAction(action)\n",[400,1281,1282,1284],{"class":220,"line":781},[400,1283,494],{"class":484},[400,1285,733],{"class":409},[400,1287,1288,1290,1292,1294,1296,1299,1301],{"class":220,"line":789},[400,1289,767],{"class":405},[400,1291,701],{"class":484},[400,1293,1063],{"class":409},[400,1295,773],{"class":405},[400,1297,1298],{"class":405}," not",[400,1300,776],{"class":484},[400,1302,475],{"class":409},[400,1304,1305,1308,1310,1313],{"class":220,"line":803},[400,1306,1307],{"class":405},"            del",[400,1309,701],{"class":484},[400,1311,1312],{"class":409},".toolbar  ",[400,1314,1315],{"class":1194},"# removes the toolbar widget from the main window\n",[400,1317,1318,1320,1322,1324],{"class":220,"line":824},[400,1319,710],{"class":484},[400,1321,1063],{"class":409},[400,1323,500],{"class":405},[400,1325,1068],{"class":484},[400,1327,1328],{"class":220,"line":830},[400,1329,457],{"emptyLinePlaceholder":456},[400,1331,1332,1334,1337],{"class":220,"line":836},[400,1333,481],{"class":405},[400,1335,1336],{"class":471}," run_centroids",[400,1338,565],{"class":409},[400,1340,1341,1343,1346,1349,1351,1354,1356,1358,1360,1363],{"class":220,"line":843},[400,1342,494],{"class":484},[400,1344,1345],{"class":409},".iface.messageBar().pushMessage(",[400,1347,1348],{"class":535},"\"Toolkit\"",[400,1350,588],{"class":409},[400,1352,1353],{"class":535},"\"Centroids tool\"",[400,1355,588],{"class":409},[400,1357,815],{"class":806},[400,1359,500],{"class":405},[400,1361,1362],{"class":484},"2",[400,1364,522],{"class":409},[400,1366,1367],{"class":220,"line":871},[400,1368,457],{"emptyLinePlaceholder":456},[400,1370,1371,1373,1376],{"class":220,"line":890},[400,1372,481],{"class":405},[400,1374,1375],{"class":471}," run_simplify",[400,1377,565],{"class":409},[400,1379,1381,1383,1385,1387,1389,1392,1394,1396,1398,1400],{"class":220,"line":1380},42,[400,1382,494],{"class":484},[400,1384,1345],{"class":409},[400,1386,1348],{"class":535},[400,1388,588],{"class":409},[400,1390,1391],{"class":535},"\"Simplify tool\"",[400,1393,588],{"class":409},[400,1395,815],{"class":806},[400,1397,500],{"class":405},[400,1399,1362],{"class":484},[400,1401,522],{"class":409},[14,1403,1404,899,1406,1409,1410,1412,1413,1416,1417,1421,1422,1425,1426,1428,1429,588,1431,1434],{},[68,1405,898],{},[18,1407,1408],{},"self.iface.addToolBar(name)"," returns a ",[18,1411,40],{}," already attached to the main window. Setting ",[18,1414,1415],{},"setObjectName"," is not cosmetic — QGIS uses the object name to save and restore toolbar position between sessions, and omitting it triggers a Qt warning. Each action is added to ",[1418,1419,1420],"em",{},"this"," toolbar via ",[18,1423,1424],{},"self.toolbar.addAction()"," rather than ",[18,1427,114],{},". In ",[18,1430,36],{},[18,1432,1433],{},"del self.toolbar"," releases the last Python reference so Qt removes the empty toolbar; without it, an empty toolbar remains after the plugin unloads.",[57,1436,1438],{"id":1437},"toggling-enabling-and-checkable-buttons","Toggling, Enabling, and Checkable Buttons",[14,1440,1441,1442,1445],{},"A button that controls a persistent state — say, a map tool that stays active — should be ",[68,1443,1444],{},"checkable"," so its pressed state reflects whether the tool is engaged. You can also enable or disable a button reactively based on the active layer.",[391,1447,1449],{"className":393,"code":1448,"language":395,"meta":396,"style":396},"def initGui(self):\n    icon = QIcon(os.path.join(self.plugin_dir, \"icons\", \"measure.svg\"))\n    self.action = QAction(icon, \"Measure mode\", self.iface.mainWindow())\n    self.action.setCheckable(True)               # button can stay pressed\n    self.action.toggled.connect(self.set_mode)   # fires with True\u002FFalse\n    self.iface.addToolBarIcon(self.action)\n    self.actions.append(self.action)\n\n    # React to layer changes: only enable for vector layers\n    self.iface.currentLayerChanged.connect(self._update_enabled)\n    self._update_enabled(self.iface.activeLayer())\n\ndef _update_enabled(self, layer):\n    from qgis.core import QgsVectorLayer\n    self.action.setEnabled(isinstance(layer, QgsVectorLayer))\n\ndef set_mode(self, checked):\n    state = \"on\" if checked else \"off\"\n    self.iface.messageBar().pushMessage(\"Measure\", f\"Mode {state}\", duration=2)\n",[18,1450,1451,1460,1482,1503,1519,1534,1546,1557,1561,1566,1578,1590,1594,1604,1616,1629,1633,1643,1665],{"__ignoreMap":396},[400,1452,1453,1456,1458],{"class":220,"line":402},[400,1454,1455],{"class":405},"def",[400,1457,562],{"class":471},[400,1459,565],{"class":409},[400,1461,1462,1465,1467,1469,1471,1473,1475,1477,1480],{"class":220,"line":413},[400,1463,1464],{"class":409},"    icon ",[400,1466,500],{"class":405},[400,1468,576],{"class":409},[400,1470,579],{"class":484},[400,1472,582],{"class":409},[400,1474,585],{"class":535},[400,1476,588],{"class":409},[400,1478,1479],{"class":535},"\"measure.svg\"",[400,1481,594],{"class":409},[400,1483,1484,1487,1490,1492,1494,1497,1499,1501],{"class":220,"line":427},[400,1485,1486],{"class":484},"    self",[400,1488,1489],{"class":409},".action ",[400,1491,500],{"class":405},[400,1493,605],{"class":409},[400,1495,1496],{"class":535},"\"Measure mode\"",[400,1498,588],{"class":409},[400,1500,579],{"class":484},[400,1502,615],{"class":409},[400,1504,1505,1507,1510,1513,1516],{"class":220,"line":440},[400,1506,1486],{"class":484},[400,1508,1509],{"class":409},".action.setCheckable(",[400,1511,1512],{"class":484},"True",[400,1514,1515],{"class":409},")               ",[400,1517,1518],{"class":1194},"# button can stay pressed\n",[400,1520,1521,1523,1526,1528,1531],{"class":220,"line":453},[400,1522,1486],{"class":484},[400,1524,1525],{"class":409},".action.toggled.connect(",[400,1527,579],{"class":484},[400,1529,1530],{"class":409},".set_mode)   ",[400,1532,1533],{"class":1194},"# fires with True\u002FFalse\n",[400,1535,1536,1538,1541,1543],{"class":220,"line":460},[400,1537,1486],{"class":484},[400,1539,1540],{"class":409},".iface.addToolBarIcon(",[400,1542,579],{"class":484},[400,1544,1545],{"class":409},".action)\n",[400,1547,1548,1550,1553,1555],{"class":220,"line":465},[400,1549,1486],{"class":484},[400,1551,1552],{"class":409},".actions.append(",[400,1554,579],{"class":484},[400,1556,1545],{"class":409},[400,1558,1559],{"class":220,"line":478},[400,1560,457],{"emptyLinePlaceholder":456},[400,1562,1563],{"class":220,"line":491},[400,1564,1565],{"class":1194},"    # React to layer changes: only enable for vector layers\n",[400,1567,1568,1570,1573,1575],{"class":220,"line":506},[400,1569,1486],{"class":484},[400,1571,1572],{"class":409},".iface.currentLayerChanged.connect(",[400,1574,579],{"class":484},[400,1576,1577],{"class":409},"._update_enabled)\n",[400,1579,1580,1582,1585,1587],{"class":220,"line":525},[400,1581,1486],{"class":484},[400,1583,1584],{"class":409},"._update_enabled(",[400,1586,579],{"class":484},[400,1588,1589],{"class":409},".iface.activeLayer())\n",[400,1591,1592],{"class":220,"line":539},[400,1593,457],{"emptyLinePlaceholder":456},[400,1595,1596,1598,1601],{"class":220,"line":552},[400,1597,1455],{"class":405},[400,1599,1600],{"class":471}," _update_enabled",[400,1602,1603],{"class":409},"(self, layer):\n",[400,1605,1606,1609,1611,1613],{"class":220,"line":557},[400,1607,1608],{"class":405},"    from",[400,1610,445],{"class":409},[400,1612,406],{"class":405},[400,1614,1615],{"class":409}," QgsVectorLayer\n",[400,1617,1618,1620,1623,1626],{"class":220,"line":568},[400,1619,1486],{"class":484},[400,1621,1622],{"class":409},".action.setEnabled(",[400,1624,1625],{"class":484},"isinstance",[400,1627,1628],{"class":409},"(layer, QgsVectorLayer))\n",[400,1630,1631],{"class":220,"line":597},[400,1632,457],{"emptyLinePlaceholder":456},[400,1634,1635,1637,1640],{"class":220,"line":618},[400,1636,1455],{"class":405},[400,1638,1639],{"class":471}," set_mode",[400,1641,1642],{"class":409},"(self, checked):\n",[400,1644,1645,1648,1650,1653,1656,1659,1662],{"class":220,"line":629},[400,1646,1647],{"class":409},"    state ",[400,1649,500],{"class":405},[400,1651,1652],{"class":535}," \"on\"",[400,1654,1655],{"class":405}," if",[400,1657,1658],{"class":409}," checked ",[400,1660,1661],{"class":405},"else",[400,1663,1664],{"class":535}," \"off\"\n",[400,1666,1667,1669,1671,1674,1676,1678,1681,1683,1686,1688,1691,1693,1695,1697,1699],{"class":220,"line":640},[400,1668,1486],{"class":484},[400,1670,1345],{"class":409},[400,1672,1673],{"class":535},"\"Measure\"",[400,1675,588],{"class":409},[400,1677,851],{"class":405},[400,1679,1680],{"class":535},"\"Mode ",[400,1682,857],{"class":484},[400,1684,1685],{"class":409},"state",[400,1687,863],{"class":484},[400,1689,1690],{"class":535},"\"",[400,1692,588],{"class":409},[400,1694,815],{"class":806},[400,1696,500],{"class":405},[400,1698,1362],{"class":484},[400,1700,522],{"class":409},[14,1702,1703,899,1705,1708,1709,1712,1713,1715,1716,1719,1720,1722,1723,1726,1727,955],{},[68,1704,898],{},[18,1706,1707],{},"setCheckable(True)"," lets the action hold a pressed\u002Funpressed state; the ",[18,1710,1711],{},"toggled"," signal then delivers the new boolean to your slot, which is more useful than ",[18,1714,32],{}," for stateful tools. Connecting to ",[18,1717,1718],{},"iface.currentLayerChanged"," lets the button grey itself out when the active layer is not a vector — disconnect this signal in ",[18,1721,36],{}," (",[18,1724,1725],{},"self.iface.currentLayerChanged.disconnect(self._update_enabled)",") so the slot does not fire after teardown. For a more elaborate stateful interface, pair the button with a panel as shown in ",[46,1728,1730],{"href":1729},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","Add a Custom Dock Widget in PyQGIS",[57,1732,1734],{"id":1733},"where-a-button-can-live","Where a button can live",[14,1736,1737,1738,1740],{},"The same ",[18,1739,20],{}," can be surfaced in four places, and a plugin that puts it in all of them clutters the interface rather than making itself easier to find.",[14,1742,1743],{},[132,1744,1747,1750,1753,1757,1763,1808,1834,1850],{"viewBox":1745,"role":135,"ariaLabel":1746,"xmlns":137},"0 0 760 244","Four places a plugin action can appear: the plugins toolbar, a dedicated toolbar, the plugins menu and a layer context menu, with what each is suited to",[140,1748,1749],{},"Four homes for the same action",[144,1751,1752],{},"The shared plugins toolbar suits a single frequently-used action. A dedicated toolbar suits a plugin with several related tools. The plugins menu is where every plugin should appear regardless. A layer context menu suits an action that operates on whatever the user right-clicked.",[177,1754],{"x":179,"y":179,"width":1755,"height":1756,"fill":182},"760","244",[184,1758,1762],{"x":1759,"y":1760,"style":1761,"fill":195,"textAnchor":190},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Put it where a user would look for it, not everywhere",[1764,1765,1766,1772,1778,1785,1790,1793,1796,1799,1804],"g",{},[177,1767],{"x":1768,"y":1769,"width":1770,"height":1771,"rx":248,"fill":249,"stroke":188,"style":251},"16","46","356","88",[184,1773,1777],{"x":1774,"y":1775,"style":1776,"fill":188},"40","70","font-size:11.5px;font-weight:bold;font-family:sans-serif","the shared plugins toolbar",[177,1779],{"x":1774,"y":1780,"width":1781,"height":1760,"rx":263,"fill":1782,"stroke":1783,"style":1784},"82","180","#e7e2d4","#59645f","stroke-width:1.2",[177,1786],{"x":1769,"y":1787,"width":1788,"height":1788,"rx":270,"fill":1783,"fillOpacity":1789},"86","18",0.4,[177,1791],{"x":1792,"y":1787,"width":1788,"height":1788,"rx":270,"fill":1783,"fillOpacity":1789},"68",[177,1794],{"x":1795,"y":1787,"width":1788,"height":1788,"rx":270,"fill":188},"90",[177,1797],{"x":1798,"y":1787,"width":1788,"height":1788,"rx":270,"fill":1783,"fillOpacity":1789},"112",[184,1800,1803],{"x":1801,"y":260,"style":1802,"fill":163},"236","font-size:11px;font-family:sans-serif","one frequent action",[184,1805,1807],{"x":1774,"y":186,"style":1806,"fill":1783},"font-size:10.5px;font-family:sans-serif","addToolBarIcon()",[1764,1809,1810,1813,1816,1818,1821,1824,1827,1831],{},[177,1811],{"x":1812,"y":1769,"width":1770,"height":1771,"rx":248,"fill":249,"stroke":250,"style":251},"388",[184,1814,1815],{"x":292,"y":1775,"style":1776,"fill":250},"a dedicated toolbar",[177,1817],{"x":292,"y":1780,"width":300,"height":1760,"rx":263,"fill":1782,"stroke":250,"style":283},[177,1819],{"x":1820,"y":1787,"width":1788,"height":1788,"rx":270,"fill":250},"418",[177,1822],{"x":1823,"y":1787,"width":1788,"height":1788,"rx":270,"fill":250},"440",[177,1825],{"x":1826,"y":1787,"width":1788,"height":1788,"rx":270,"fill":250},"462",[184,1828,1830],{"x":1829,"y":260,"style":1802,"fill":163},"548","several related tools",[184,1832,1833],{"x":292,"y":186,"style":1806,"fill":1783},"addToolBar(\"My Plugin\")",[1764,1835,1836,1839,1843,1847],{},[177,1837],{"x":1768,"y":1838,"width":1770,"height":1780,"rx":248,"fill":249,"stroke":175,"style":251},"146",[184,1840,1842],{"x":1774,"y":1841,"style":1776,"fill":175},"170","the Plugins menu",[184,1844,1846],{"x":1774,"y":1845,"style":1802,"fill":163},"192","where users go when they cannot find an icon",[184,1848,1849],{"x":1774,"y":239,"style":1806,"fill":1783},"addPluginToMenu() — always do this one",[1764,1851,1852,1854,1857,1860],{},[177,1853],{"x":1812,"y":1838,"width":1770,"height":1780,"rx":248,"fill":249,"stroke":169,"style":251},[184,1855,1856],{"x":292,"y":1841,"style":1776,"fill":169},"a layer context menu",[184,1858,1859],{"x":292,"y":1845,"style":1802,"fill":163},"acts on whatever was right-clicked",[184,1861,1862],{"x":292,"y":239,"style":1806,"fill":1783},"addCustomActionForLayerType()",[57,1864,1866],{"id":1865},"icons-that-survive-packaging","Icons that survive packaging",[14,1868,1869],{},"An icon that works on your machine and vanishes for everyone else is nearly always a path problem, and the resource system is what solves it.",[14,1871,1872],{},[132,1873,1876,1879,1882,1884,1896,1899,1902,1907,1912,1916,1919,1924,1929,1933,1935,1939,1942,1945,1948,1952,1955],{"viewBox":1874,"role":135,"ariaLabel":1875,"xmlns":137},"0 0 760 232","An icon referenced by a filesystem path breaking when the plugin is installed elsewhere, compared with a compiled resource path that always resolves",[140,1877,1878],{},"Filesystem path versus a compiled resource",[144,1880,1881],{},"Referencing an icon by an absolute filesystem path works during development and breaks once the plugin is installed under a different profile directory. Compiling the icon into resources_rc.py and referencing it with a resource path resolves identically on every machine because the data is inside the module.",[177,1883],{"x":179,"y":179,"width":1755,"height":334,"fill":182},[148,1885,1886],{},[152,1887,1893],{"id":1888,"viewBox":1889,"refX":1890,"refY":1891,"markerWidth":156,"markerHeight":156,"orient":1892},"btnIconArrow","0 0 10 10","8","5","auto-start-reverse",[160,1894],{"d":1895,"fill":163},"M0 0 L10 5 L0 10 z",[184,1897,1898],{"x":1759,"y":1760,"style":1761,"fill":195,"textAnchor":190},"A blank button is almost always an unresolved icon path",[177,1900],{"x":1768,"y":1769,"width":1770,"height":1841,"rx":248,"fill":249,"stroke":1901,"style":251},"#b91c1c",[184,1903,1906],{"x":1904,"y":1775,"style":1905,"fill":1901,"textAnchor":190},"194","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","a filesystem path",[184,1908,1911],{"x":1904,"y":1909,"style":1910,"fill":163,"textAnchor":190},"96","text-anchor:middle;font-size:10px;font-family:monospace","\u002Fhome\u002Fme\u002Fdev\u002Fmyplugin\u002Ficon.svg",[177,1913],{"x":300,"y":1798,"width":316,"height":1914,"rx":206,"fill":1782,"stroke":1783,"style":1915},"44","stroke-width:1.4",[177,1917],{"x":223,"y":1918,"width":203,"height":203,"rx":263,"fill":169},"122",[184,1920,1923],{"x":236,"y":1921,"style":1922,"fill":163,"textAnchor":190},"140","text-anchor:middle;font-size:10.5px;font-family:sans-serif","on your machine",[184,1925,1928],{"x":1904,"y":1926,"style":1927,"fill":1901,"textAnchor":190},"182","text-anchor:middle;font-size:11px;font-family:sans-serif","blank once installed elsewhere",[184,1930,1932],{"x":1904,"y":1931,"style":1922,"fill":163,"textAnchor":190},"202","the profile directory differs",[177,1934],{"x":1812,"y":1769,"width":1770,"height":1841,"rx":248,"fill":249,"stroke":169,"style":251},[184,1936,1938],{"x":1937,"y":1775,"style":1905,"fill":169,"textAnchor":190},"566","a compiled resource",[184,1940,1941],{"x":1937,"y":1909,"style":1910,"fill":163,"textAnchor":190},":\u002Fplugins\u002Fmyplugin\u002Ficon.svg",[177,1943],{"x":1944,"y":1798,"width":316,"height":1914,"rx":206,"fill":1782,"stroke":1783,"style":1915},"492",[177,1946],{"x":1947,"y":1918,"width":203,"height":203,"rx":263,"fill":169},"504",[184,1949,1951],{"x":1950,"y":1921,"style":1922,"fill":163,"textAnchor":190},"584","everywhere",[184,1953,1954],{"x":1937,"y":1926,"style":1927,"fill":169,"textAnchor":190},"the bytes live inside resources_rc.py",[184,1956,1957],{"x":1937,"y":1931,"style":1922,"fill":163,"textAnchor":190},"rebuild it with pyrcc5 after any change",[57,1959,1961],{"id":1960},"qgis-version-compatibility","QGIS Version Compatibility",[14,1963,100,1964,1966,1967,1969],{},[18,1965,20],{}," API and the ",[18,1968,81],{}," toolbar\u002Fmenu methods have been stable across the entire 3.x line, so this code runs unchanged on every modern QGIS. Only peripheral details vary.",[1971,1972,1973,1992],"table",{},[1974,1975,1976],"thead",{},[1977,1978,1979,1983,1986,1989],"tr",{},[1980,1981,1982],"th",{},"Aspect",[1980,1984,1985],{},"QGIS 3.28 LTR (Py 3.9)",[1980,1987,1988],{},"QGIS 3.34 LTR (Py 3.12)",[1980,1990,1991],{},"QGIS 3.40 \u002F 3.44 (Py 3.12)",[1993,1994,1995,2012,2026,2039],"tbody",{},[1977,1996,1997,2005,2008,2010],{},[1998,1999,2000,2002,2003],"td",{},[18,2001,114],{}," \u002F ",[18,2004,934],{},[1998,2006,2007],{},"stable",[1998,2009,2007],{},[1998,2011,2007],{},[1977,2013,2014,2020,2022,2024],{},[1998,2015,2016,2002,2018],{},[18,2017,122],{},[18,2019,938],{},[1998,2021,2007],{},[1998,2023,2007],{},[1998,2025,2007],{},[1977,2027,2028,2032,2035,2037],{},[1998,2029,2030],{},[18,2031,1718],{},[1998,2033,2034],{},"available",[1998,2036,2034],{},[1998,2038,2034],{},[1977,2040,2041,2044,2047,2049],{},[1998,2042,2043],{},"Icon format",[1998,2045,2046],{},"PNG \u002F SVG",[1998,2048,2046],{},[1998,2050,2046],{},[14,2052,2053],{},"Prefer SVG icons: they stay crisp on high-DPI displays, where PNGs at fixed pixel sizes can look soft.",[57,2055,2057],{"id":2056},"troubleshooting","Troubleshooting",[14,2059,2060,899,2063,2065,2066,935,2068,2070,2071,955],{},[68,2061,2062],{},"Button stays visible after disabling the plugin.",[18,2064,36],{}," is not reversing every registration. For each action call both ",[18,2067,934],{},[18,2069,938],{},"; for a custom toolbar also release it with ",[18,2072,1433],{},[14,2074,2075,2083,2084,2086,2087,955],{},[68,2076,2077,2080,2081,955],{},[18,2078,2079],{},"AttributeError"," on ",[18,2082,114],{}," You called it on the wrong object. The method belongs to ",[18,2085,81],{},", not to your plugin class — use ",[18,2088,2089],{},"self.iface.addToolBarIcon(action)",[14,2091,2092,2095,2096,2099,2100,2102],{},[68,2093,2094],{},"Qt warning about a missing object name."," Set ",[18,2097,2098],{},"toolbar.setObjectName(\"...\")"," immediately after ",[18,2101,129],{},", otherwise QGIS cannot persist its position and logs a warning.",[14,2104,2105,2108,2109,2111,2112,2115],{},[68,2106,2107],{},"Clicking the button does nothing."," The ",[18,2110,32],{}," connection is missing or points at an unbound method. Connect to a bound method (",[18,2113,2114],{},"self.run",") and confirm no exception is swallowed — check the Python console.",[14,2117,2118,2121,2122,2125,2126,899,2129,2132,2133,2135],{},[68,2119,2120],{},"Button does not disable for non-vector layers."," Ensure ",[18,2123,2124],{},"_update_enabled"," is connected to ",[18,2127,2128],{},"currentLayerChanged",[1418,2130,2131],{},"and"," called once during ",[18,2134,28],{}," with the current layer, since the signal only fires on subsequent changes.",[57,2137,2139],{"id":2138},"conclusion","Conclusion",[14,2141,2142,2143,2145,2146,2148,2149,935,2151,2153,2154,2156,2157,2159,2160,2162],{},"A QGIS toolbar button is a ",[18,2144,20],{}," plus disciplined lifecycle management. Build the action with a ",[18,2147,24],{},", register it through ",[18,2150,114],{},[18,2152,122],{}," (or onto a dedicated ",[18,2155,40],{},"), wire ",[18,2158,32],{}," to your callback, and mirror every registration with a matching removal in ",[18,2161,36],{},". Keep your actions in a list, set object names on custom toolbars, and disconnect any interface signals you subscribed to. That symmetry is the whole game — get it right and your plugin loads and unloads without leaving a trace.",[57,2164,2166],{"id":2165},"frequently-asked-questions","Frequently Asked Questions",[14,2168,2169,899,2178,2180,2181,2183,2184,2187],{},[68,2170,2171,2172,2174,2175,2177],{},"What is the difference between ",[18,2173,114],{}," and a custom ",[18,2176,40],{},"?",[18,2179,114],{}," drops your action onto the shared QGIS Plugins toolbar — fine for one button. A custom ",[18,2182,40],{}," from ",[18,2185,2186],{},"iface.addToolBar"," gives your plugin its own dockable, hideable bar, which suits a suite of related tools.",[14,2189,2190,2193],{},[68,2191,2192],{},"Do I have to add the action to a menu as well?"," No, but it is good practice. Toolbars can be hidden, so a menu entry guarantees users can still reach the command.",[14,2195,2196,899,2199,2201],{},[68,2197,2198],{},"Why store actions in a list?",[18,2200,36],{}," needs a reference to every action to remove it. A list makes teardown a simple loop and prevents forgotten orphans.",[14,2203,2204,2209,2210,2212,2213,2215],{},[68,2205,2206,2207,2177],{},"Should I disconnect signals in ",[18,2208,36],{}," Yes, for any signal you connected to ",[18,2211,81],{}," (such as ",[18,2214,2128],{},"). Signals on the action itself are cleaned up with the action, but interface-level connections outlive it and will fire into a dead slot.",[14,2217,2218,2221,2222,935,2225,2228,2229,2232],{},[68,2219,2220],{},"Should the button be enabled when no layer is loaded?","\nUsually not. Connecting ",[18,2223,2224],{},"QgsProject.layersAdded",[18,2226,2227],{},"layersRemoved"," to a handler that calls ",[18,2230,2231],{},"action.setEnabled()"," keeps the button honest, and a greyed-out button explains itself better than an error message does.",[57,2234,2236],{"id":2235},"related","Related",[62,2238,2239,2243,2247],{},[65,2240,2241],{},[46,2242,49],{"href":48},[65,2244,2245],{},[46,2246,54],{"href":53},[65,2248,2249],{},[46,2250,1730],{"href":1729},[2252,2253,2254],"style",{},"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":396,"searchDepth":413,"depth":413,"links":2256},[2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268],{"id":59,"depth":413,"text":60},{"id":96,"depth":413,"text":97},{"id":379,"depth":413,"text":380},{"id":942,"depth":413,"text":943},{"id":1437,"depth":413,"text":1438},{"id":1733,"depth":413,"text":1734},{"id":1865,"depth":413,"text":1866},{"id":1960,"depth":413,"text":1961},{"id":2056,"depth":413,"text":2057},{"id":2138,"depth":413,"text":2139},{"id":2165,"depth":413,"text":2166},{"id":2235,"depth":413,"text":2236},"Add a toolbar button to a QGIS plugin with PyQGIS. Create a QAction and QIcon, register it in initGui, connect triggered, and clean up symmetrically in unload.","md",{"slug":2272,"type":2273,"breadcrumb":2274,"datePublished":2275,"dateModified":2276},"add-toolbar-button-to-qgis-plugin","article","Add a Toolbar Button","2025-04-22","2026-07-18","\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin",{"title":5,"description":2269},"qgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002Findex","2iyMdpZgDrPbMVsOZVOT6T5eV8fro3xVo1C8dVnhgj4",1787823360573]