[{"data":1,"prerenderedAt":1908},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin":3},{"id":4,"title":5,"body":6,"description":1901,"extension":1902,"meta":1903,"navigation":333,"path":1904,"seo":1905,"stem":1906,"__hash__":1907},"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":1889},"minimark",[9,13,42,56,61,94,98,131,254,258,267,772,818,822,834,1280,1313,1317,1324,1579,1609,1613,1621,1702,1705,1709,1724,1741,1754,1767,1787,1791,1814,1818,1839,1845,1853,1867,1871,1885],[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,154,138,163,138,167,138,174,138,177,138,185,138,191,138,194,138,199,138,202,138,206,138,211,138,215,138,218,138,221,138,223,138,227,138,234,138,237],"svg",{"viewBox":134,"role":135,"ariaLabel":136,"xmlns":137},"0 0 640 300","img","Diagram mapping initGui registration calls to unload teardown calls for a QGIS toolbar button","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[140,141,142],"title",{},"QAction lifecycle across initGui and unload",[144,145,146],"desc",{},"initGui creates a QAction with a QIcon and registers it via addToolBarIcon and addPluginToMenu; unload reverses each call with removeToolBarIcon and removePluginMenu.",[148,149],"rect",{"x":150,"y":150,"width":151,"height":152,"fill":153},"0","640","300","#f6f3ea",[148,155],{"x":156,"y":156,"width":157,"height":158,"rx":159,"fill":160,"stroke":161,"style":162},"24","270","252","8","#fffdf7","#0f766e","stroke-width:2.5",[148,164],{"x":165,"y":156,"width":157,"height":158,"rx":159,"fill":160,"stroke":166,"style":162},"346","#b45309",[168,169,28],"text",{"x":170,"y":171,"fill":172,"style":173},"159","52","#17211d","text-anchor:middle;font-family:sans-serif;font-size:18px;font-weight:bold",[168,175,36],{"x":176,"y":171,"fill":172,"style":173},"481",[148,178],{"x":179,"y":180,"width":181,"height":182,"rx":183,"fill":184},"48","70","222","40","5","#26322d",[168,186,190],{"x":170,"y":187,"fill":188,"style":189},"95","#d9f99d","text-anchor:middle;font-family:monospace;font-size:13px","QAction(QIcon, text)",[148,192],{"x":179,"y":193,"width":181,"height":182,"rx":183,"fill":184},"124",[168,195,198],{"x":170,"y":196,"fill":188,"style":197},"149","text-anchor:middle;font-family:monospace;font-size:12px","addToolBarIcon(action)",[148,200],{"x":179,"y":201,"width":181,"height":182,"rx":183,"fill":184},"178",[168,203,205],{"x":170,"y":204,"fill":188,"style":197},"203","addPluginToMenu(menu, action)",[148,207],{"x":179,"y":208,"width":181,"height":209,"rx":183,"fill":210},"232","34","#2563eb",[168,212,214],{"x":170,"y":213,"fill":160,"style":197},"254","triggered.connect(run)",[148,216],{"x":217,"y":193,"width":181,"height":182,"rx":183,"fill":184},"370",[168,219,220],{"x":176,"y":196,"fill":188,"style":197},"removeToolBarIcon(action)",[148,222],{"x":217,"y":201,"width":181,"height":182,"rx":183,"fill":184},[168,224,226],{"x":176,"y":204,"fill":188,"style":225},"text-anchor:middle;font-family:monospace;font-size:11.5px","removePluginMenu(menu, action)",[228,229],"line",{"x1":230,"y1":231,"x2":217,"y2":231,"stroke":232,"style":233},"294","144","#15803d","stroke-width:2.5;marker-end:url(#ar)",[228,235],{"x1":230,"y1":236,"x2":217,"y2":236,"stroke":232,"style":233},"198",[238,239,240,241,138],"defs",{},"\n    ",[242,243,249,250,240],"marker",{"id":244,"markerWidth":245,"markerHeight":245,"refX":246,"refY":247,"orient":248},"ar","9","7","4","auto","\n      ",[251,252],"path",{"d":253,"fill":232},"M0,0 L8,4 L0,8 Z",[57,255,257],{"id":256},"recipe-1-a-single-button-on-the-plugins-toolbar","Recipe 1: A Single Button on the Plugins Toolbar",[14,259,260,261,263,264,266],{},"The simplest case adds one icon to the shared QGIS Plugins toolbar and the same command to a plugin menu. Store the ",[18,262,20],{}," so ",[18,265,36],{}," can find it later.",[268,269,274],"pre",{"className":270,"code":271,"language":272,"meta":273,"style":273},"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,275,276,288,302,315,328,335,340,353,366,381,400,414,427,432,443,472,493,504,515,520,528,541,549,554,564,582,591,603,611,616,626,639,656,664,678,699,705,711,718,746,766],{"__ignoreMap":273},[277,278,280,284],"span",{"class":228,"line":279},1,[277,281,283],{"class":282},"snl16","import",[277,285,287],{"class":286},"s95oV"," os\n",[277,289,291,294,297,299],{"class":228,"line":290},2,[277,292,293],{"class":282},"from",[277,295,296],{"class":286}," qgis.PyQt.QtGui ",[277,298,283],{"class":282},[277,300,301],{"class":286}," QIcon\n",[277,303,305,307,310,312],{"class":228,"line":304},3,[277,306,293],{"class":282},[277,308,309],{"class":286}," qgis.PyQt.QtWidgets ",[277,311,283],{"class":282},[277,313,314],{"class":286}," QAction\n",[277,316,318,320,323,325],{"class":228,"line":317},4,[277,319,293],{"class":282},[277,321,322],{"class":286}," qgis.core ",[277,324,283],{"class":282},[277,326,327],{"class":286}," Qgis\n",[277,329,331],{"class":228,"line":330},5,[277,332,334],{"emptyLinePlaceholder":333},true,"\n",[277,336,338],{"class":228,"line":337},6,[277,339,334],{"emptyLinePlaceholder":333},[277,341,343,346,350],{"class":228,"line":342},7,[277,344,345],{"class":282},"class",[277,347,349],{"class":348},"svObZ"," BufferTool",[277,351,352],{"class":286},":\n",[277,354,356,359,363],{"class":228,"line":355},8,[277,357,358],{"class":282},"    def",[277,360,362],{"class":361},"sDLfK"," __init__",[277,364,365],{"class":286},"(self, iface):\n",[277,367,369,372,375,378],{"class":228,"line":368},9,[277,370,371],{"class":361},"        self",[277,373,374],{"class":286},".iface ",[277,376,377],{"class":282},"=",[277,379,380],{"class":286}," iface\n",[277,382,384,386,389,391,394,397],{"class":228,"line":383},10,[277,385,371],{"class":361},[277,387,388],{"class":286},".plugin_dir ",[277,390,377],{"class":282},[277,392,393],{"class":286}," os.path.dirname(",[277,395,396],{"class":361},"__file__",[277,398,399],{"class":286},")\n",[277,401,403,405,408,410],{"class":228,"line":402},11,[277,404,371],{"class":361},[277,406,407],{"class":286},".menu ",[277,409,377],{"class":282},[277,411,413],{"class":412},"sU2Wk"," \"&Buffer Tool\"\n",[277,415,417,419,422,424],{"class":228,"line":416},12,[277,418,371],{"class":361},[277,420,421],{"class":286},".actions ",[277,423,377],{"class":282},[277,425,426],{"class":286}," []\n",[277,428,430],{"class":228,"line":429},13,[277,431,334],{"emptyLinePlaceholder":333},[277,433,435,437,440],{"class":228,"line":434},14,[277,436,358],{"class":282},[277,438,439],{"class":348}," initGui",[277,441,442],{"class":286},"(self):\n",[277,444,446,449,451,454,457,460,463,466,469],{"class":228,"line":445},15,[277,447,448],{"class":286},"        icon ",[277,450,377],{"class":282},[277,452,453],{"class":286}," QIcon(os.path.join(",[277,455,456],{"class":361},"self",[277,458,459],{"class":286},".plugin_dir, ",[277,461,462],{"class":412},"\"icons\"",[277,464,465],{"class":286},", ",[277,467,468],{"class":412},"\"buffer.svg\"",[277,470,471],{"class":286},"))\n",[277,473,475,478,480,483,486,488,490],{"class":228,"line":474},16,[277,476,477],{"class":286},"        action ",[277,479,377],{"class":282},[277,481,482],{"class":286}," QAction(icon, ",[277,484,485],{"class":412},"\"Buffer active layer\"",[277,487,465],{"class":286},[277,489,456],{"class":361},[277,491,492],{"class":286},".iface.mainWindow())\n",[277,494,496,499,502],{"class":228,"line":495},17,[277,497,498],{"class":286},"        action.setStatusTip(",[277,500,501],{"class":412},"\"Create a buffer around the active vector layer\"",[277,503,399],{"class":286},[277,505,507,510,512],{"class":228,"line":506},18,[277,508,509],{"class":286},"        action.triggered.connect(",[277,511,456],{"class":361},[277,513,514],{"class":286},".run)\n",[277,516,518],{"class":228,"line":517},19,[277,519,334],{"emptyLinePlaceholder":333},[277,521,523,525],{"class":228,"line":522},20,[277,524,371],{"class":361},[277,526,527],{"class":286},".iface.addToolBarIcon(action)\n",[277,529,531,533,536,538],{"class":228,"line":530},21,[277,532,371],{"class":361},[277,534,535],{"class":286},".iface.addPluginToMenu(",[277,537,456],{"class":361},[277,539,540],{"class":286},".menu, action)\n",[277,542,544,546],{"class":228,"line":543},22,[277,545,371],{"class":361},[277,547,548],{"class":286},".actions.append(action)\n",[277,550,552],{"class":228,"line":551},23,[277,553,334],{"emptyLinePlaceholder":333},[277,555,557,559,562],{"class":228,"line":556},24,[277,558,358],{"class":282},[277,560,561],{"class":348}," unload",[277,563,442],{"class":286},[277,565,567,570,573,576,579],{"class":228,"line":566},25,[277,568,569],{"class":282},"        for",[277,571,572],{"class":286}," action ",[277,574,575],{"class":282},"in",[277,577,578],{"class":361}," self",[277,580,581],{"class":286},".actions:\n",[277,583,585,588],{"class":228,"line":584},26,[277,586,587],{"class":361},"            self",[277,589,590],{"class":286},".iface.removeToolBarIcon(action)\n",[277,592,594,596,599,601],{"class":228,"line":593},27,[277,595,587],{"class":361},[277,597,598],{"class":286},".iface.removePluginMenu(",[277,600,456],{"class":361},[277,602,540],{"class":286},[277,604,606,608],{"class":228,"line":605},28,[277,607,371],{"class":361},[277,609,610],{"class":286},".actions.clear()\n",[277,612,614],{"class":228,"line":613},29,[277,615,334],{"emptyLinePlaceholder":333},[277,617,619,621,624],{"class":228,"line":618},30,[277,620,358],{"class":282},[277,622,623],{"class":348}," run",[277,625,442],{"class":286},[277,627,629,632,634,636],{"class":228,"line":628},31,[277,630,631],{"class":286},"        layer ",[277,633,377],{"class":282},[277,635,578],{"class":361},[277,637,638],{"class":286},".iface.activeLayer()\n",[277,640,642,645,648,651,654],{"class":228,"line":641},32,[277,643,644],{"class":282},"        if",[277,646,647],{"class":286}," layer ",[277,649,650],{"class":282},"is",[277,652,653],{"class":361}," None",[277,655,352],{"class":286},[277,657,659,661],{"class":228,"line":658},33,[277,660,587],{"class":361},[277,662,663],{"class":286},".iface.messageBar().pushMessage(\n",[277,665,667,670,672,675],{"class":228,"line":666},34,[277,668,669],{"class":412},"                \"Buffer Tool\"",[277,671,465],{"class":286},[277,673,674],{"class":412},"\"Select a layer first.\"",[277,676,677],{"class":286},",\n",[277,679,681,685,687,690,693,695,697],{"class":228,"line":680},35,[277,682,684],{"class":683},"s9osk","                level",[277,686,377],{"class":282},[277,688,689],{"class":286},"Qgis.Warning, ",[277,691,692],{"class":683},"duration",[277,694,377],{"class":282},[277,696,247],{"class":361},[277,698,677],{"class":286},[277,700,702],{"class":228,"line":701},36,[277,703,704],{"class":286},"            )\n",[277,706,708],{"class":228,"line":707},37,[277,709,710],{"class":282},"            return\n",[277,712,714,716],{"class":228,"line":713},38,[277,715,371],{"class":361},[277,717,663],{"class":286},[277,719,721,724,726,729,732,735,738,741,744],{"class":228,"line":720},39,[277,722,723],{"class":412},"            \"Buffer Tool\"",[277,725,465],{"class":286},[277,727,728],{"class":282},"f",[277,730,731],{"class":412},"\"Buffering ",[277,733,734],{"class":361},"{",[277,736,737],{"class":286},"layer.name()",[277,739,740],{"class":361},"}",[277,742,743],{"class":412},"...\"",[277,745,677],{"class":286},[277,747,749,752,754,757,759,761,764],{"class":228,"line":748},40,[277,750,751],{"class":683},"            level",[277,753,377],{"class":282},[277,755,756],{"class":286},"Qgis.Info, ",[277,758,692],{"class":683},[277,760,377],{"class":282},[277,762,763],{"class":361},"3",[277,765,677],{"class":286},[277,767,769],{"class":228,"line":768},41,[277,770,771],{"class":286},"        )\n",[14,773,774,777,778,781,782,785,786,789,790,793,794,796,797,799,800,803,804,465,807,809,810,813,814,817],{},[68,775,776],{},"Breakdown:"," ",[18,779,780],{},"QAction(icon, text, parent)"," builds the clickable command; passing ",[18,783,784],{},"self.iface.mainWindow()"," as the parent ties its Qt lifetime to the main window. ",[18,787,788],{},"triggered.connect(self.run)"," fires ",[18,791,792],{},"run()"," on every click. ",[18,795,114],{}," places it on the shared toolbar; ",[18,798,122],{}," mirrors it into a named submenu under ",[18,801,802],{},"Plugins",". Because the action is kept in ",[18,805,806],{},"self.actions",[18,808,36],{}," can reverse both registrations — ",[18,811,812],{},"removeToolBarIcon"," and ",[18,815,816],{},"removePluginMenu"," must each be called, or the orphaned widget lingers after the plugin is disabled.",[57,819,821],{"id":820},"recipe-2-a-dedicated-custom-toolbar","Recipe 2: A Dedicated Custom Toolbar",[14,823,824,825,827,828,830,831,833],{},"When a plugin ships several related tools, a dedicated ",[18,826,40],{}," groups them and lets users dock or hide the set as a unit. Create it once in ",[18,829,28],{}," and destroy it in ",[18,832,36],{},".",[268,835,837],{"className":270,"code":836,"language":272,"meta":273,"style":273},"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,838,839,845,855,865,869,873,882,890,900,914,925,935,947,951,961,978,991,996,1003,1013,1019,1027,1031,1039,1057,1074,1096,1117,1121,1129,1141,1151,1158,1164,1181,1194,1204,1208,1217,1243,1247,1256],{"__ignoreMap":273},[277,840,841,843],{"class":228,"line":279},[277,842,283],{"class":282},[277,844,287],{"class":286},[277,846,847,849,851,853],{"class":228,"line":290},[277,848,293],{"class":282},[277,850,296],{"class":286},[277,852,283],{"class":282},[277,854,301],{"class":286},[277,856,857,859,861,863],{"class":228,"line":304},[277,858,293],{"class":282},[277,860,309],{"class":286},[277,862,283],{"class":282},[277,864,314],{"class":286},[277,866,867],{"class":228,"line":317},[277,868,334],{"emptyLinePlaceholder":333},[277,870,871],{"class":228,"line":330},[277,872,334],{"emptyLinePlaceholder":333},[277,874,875,877,880],{"class":228,"line":337},[277,876,345],{"class":282},[277,878,879],{"class":348}," GeometryToolkit",[277,881,352],{"class":286},[277,883,884,886,888],{"class":228,"line":342},[277,885,358],{"class":282},[277,887,362],{"class":361},[277,889,365],{"class":286},[277,891,892,894,896,898],{"class":228,"line":355},[277,893,371],{"class":361},[277,895,374],{"class":286},[277,897,377],{"class":282},[277,899,380],{"class":286},[277,901,902,904,906,908,910,912],{"class":228,"line":368},[277,903,371],{"class":361},[277,905,388],{"class":286},[277,907,377],{"class":282},[277,909,393],{"class":286},[277,911,396],{"class":361},[277,913,399],{"class":286},[277,915,916,918,920,922],{"class":228,"line":383},[277,917,371],{"class":361},[277,919,407],{"class":286},[277,921,377],{"class":282},[277,923,924],{"class":412}," \"&Geometry Toolkit\"\n",[277,926,927,929,931,933],{"class":228,"line":402},[277,928,371],{"class":361},[277,930,421],{"class":286},[277,932,377],{"class":282},[277,934,426],{"class":286},[277,936,937,939,942,944],{"class":228,"line":416},[277,938,371],{"class":361},[277,940,941],{"class":286},".toolbar ",[277,943,377],{"class":282},[277,945,946],{"class":361}," None\n",[277,948,949],{"class":228,"line":429},[277,950,334],{"emptyLinePlaceholder":333},[277,952,953,955,958],{"class":228,"line":434},[277,954,358],{"class":282},[277,956,957],{"class":348}," _make_action",[277,959,960],{"class":286},"(self, icon_name, text, callback):\n",[277,962,963,965,967,969,971,973,975],{"class":228,"line":445},[277,964,448],{"class":286},[277,966,377],{"class":282},[277,968,453],{"class":286},[277,970,456],{"class":361},[277,972,459],{"class":286},[277,974,462],{"class":412},[277,976,977],{"class":286},", icon_name))\n",[277,979,980,982,984,987,989],{"class":228,"line":474},[277,981,477],{"class":286},[277,983,377],{"class":282},[277,985,986],{"class":286}," QAction(icon, text, ",[277,988,456],{"class":361},[277,990,492],{"class":286},[277,992,993],{"class":228,"line":495},[277,994,995],{"class":286},"        action.triggered.connect(callback)\n",[277,997,998,1000],{"class":228,"line":506},[277,999,371],{"class":361},[277,1001,1002],{"class":286},".toolbar.addAction(action)\n",[277,1004,1005,1007,1009,1011],{"class":228,"line":517},[277,1006,371],{"class":361},[277,1008,535],{"class":286},[277,1010,456],{"class":361},[277,1012,540],{"class":286},[277,1014,1015,1017],{"class":228,"line":522},[277,1016,371],{"class":361},[277,1018,548],{"class":286},[277,1020,1021,1024],{"class":228,"line":530},[277,1022,1023],{"class":282},"        return",[277,1025,1026],{"class":286}," action\n",[277,1028,1029],{"class":228,"line":543},[277,1030,334],{"emptyLinePlaceholder":333},[277,1032,1033,1035,1037],{"class":228,"line":551},[277,1034,358],{"class":282},[277,1036,439],{"class":348},[277,1038,442],{"class":286},[277,1040,1041,1043,1045,1047,1049,1052,1055],{"class":228,"line":556},[277,1042,371],{"class":361},[277,1044,941],{"class":286},[277,1046,377],{"class":282},[277,1048,578],{"class":361},[277,1050,1051],{"class":286},".iface.addToolBar(",[277,1053,1054],{"class":412},"\"Geometry Toolkit\"",[277,1056,399],{"class":286},[277,1058,1059,1061,1064,1067,1070],{"class":228,"line":566},[277,1060,371],{"class":361},[277,1062,1063],{"class":286},".toolbar.setObjectName(",[277,1065,1066],{"class":412},"\"GeometryToolkit\"",[277,1068,1069],{"class":286},")  ",[277,1071,1073],{"class":1072},"sAwPA","# required for state persistence\n",[277,1075,1076,1078,1081,1084,1086,1089,1091,1093],{"class":228,"line":584},[277,1077,371],{"class":361},[277,1079,1080],{"class":286},"._make_action(",[277,1082,1083],{"class":412},"\"centroid.svg\"",[277,1085,465],{"class":286},[277,1087,1088],{"class":412},"\"Centroids\"",[277,1090,465],{"class":286},[277,1092,456],{"class":361},[277,1094,1095],{"class":286},".run_centroids)\n",[277,1097,1098,1100,1102,1105,1107,1110,1112,1114],{"class":228,"line":593},[277,1099,371],{"class":361},[277,1101,1080],{"class":286},[277,1103,1104],{"class":412},"\"simplify.svg\"",[277,1106,465],{"class":286},[277,1108,1109],{"class":412},"\"Simplify\"",[277,1111,465],{"class":286},[277,1113,456],{"class":361},[277,1115,1116],{"class":286},".run_simplify)\n",[277,1118,1119],{"class":228,"line":605},[277,1120,334],{"emptyLinePlaceholder":333},[277,1122,1123,1125,1127],{"class":228,"line":613},[277,1124,358],{"class":282},[277,1126,561],{"class":348},[277,1128,442],{"class":286},[277,1130,1131,1133,1135,1137,1139],{"class":228,"line":618},[277,1132,569],{"class":282},[277,1134,572],{"class":286},[277,1136,575],{"class":282},[277,1138,578],{"class":361},[277,1140,581],{"class":286},[277,1142,1143,1145,1147,1149],{"class":228,"line":628},[277,1144,587],{"class":361},[277,1146,598],{"class":286},[277,1148,456],{"class":361},[277,1150,540],{"class":286},[277,1152,1153,1155],{"class":228,"line":641},[277,1154,587],{"class":361},[277,1156,1157],{"class":286},".toolbar.removeAction(action)\n",[277,1159,1160,1162],{"class":228,"line":658},[277,1161,371],{"class":361},[277,1163,610],{"class":286},[277,1165,1166,1168,1170,1172,1174,1177,1179],{"class":228,"line":666},[277,1167,644],{"class":282},[277,1169,578],{"class":361},[277,1171,941],{"class":286},[277,1173,650],{"class":282},[277,1175,1176],{"class":282}," not",[277,1178,653],{"class":361},[277,1180,352],{"class":286},[277,1182,1183,1186,1188,1191],{"class":228,"line":680},[277,1184,1185],{"class":282},"            del",[277,1187,578],{"class":361},[277,1189,1190],{"class":286},".toolbar  ",[277,1192,1193],{"class":1072},"# removes the toolbar widget from the main window\n",[277,1195,1196,1198,1200,1202],{"class":228,"line":701},[277,1197,587],{"class":361},[277,1199,941],{"class":286},[277,1201,377],{"class":282},[277,1203,946],{"class":361},[277,1205,1206],{"class":228,"line":707},[277,1207,334],{"emptyLinePlaceholder":333},[277,1209,1210,1212,1215],{"class":228,"line":713},[277,1211,358],{"class":282},[277,1213,1214],{"class":348}," run_centroids",[277,1216,442],{"class":286},[277,1218,1219,1221,1224,1227,1229,1232,1234,1236,1238,1241],{"class":228,"line":720},[277,1220,371],{"class":361},[277,1222,1223],{"class":286},".iface.messageBar().pushMessage(",[277,1225,1226],{"class":412},"\"Toolkit\"",[277,1228,465],{"class":286},[277,1230,1231],{"class":412},"\"Centroids tool\"",[277,1233,465],{"class":286},[277,1235,692],{"class":683},[277,1237,377],{"class":282},[277,1239,1240],{"class":361},"2",[277,1242,399],{"class":286},[277,1244,1245],{"class":228,"line":748},[277,1246,334],{"emptyLinePlaceholder":333},[277,1248,1249,1251,1254],{"class":228,"line":768},[277,1250,358],{"class":282},[277,1252,1253],{"class":348}," run_simplify",[277,1255,442],{"class":286},[277,1257,1259,1261,1263,1265,1267,1270,1272,1274,1276,1278],{"class":228,"line":1258},42,[277,1260,371],{"class":361},[277,1262,1223],{"class":286},[277,1264,1226],{"class":412},[277,1266,465],{"class":286},[277,1268,1269],{"class":412},"\"Simplify tool\"",[277,1271,465],{"class":286},[277,1273,692],{"class":683},[277,1275,377],{"class":282},[277,1277,1240],{"class":361},[277,1279,399],{"class":286},[14,1281,1282,777,1284,1287,1288,1290,1291,1294,1295,1299,1300,1303,1304,1306,1307,465,1309,1312],{},[68,1283,776],{},[18,1285,1286],{},"self.iface.addToolBar(name)"," returns a ",[18,1289,40],{}," already attached to the main window. Setting ",[18,1292,1293],{},"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 ",[1296,1297,1298],"em",{},"this"," toolbar via ",[18,1301,1302],{},"self.toolbar.addAction()"," rather than ",[18,1305,114],{},". In ",[18,1308,36],{},[18,1310,1311],{},"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,1314,1316],{"id":1315},"toggling-enabling-and-checkable-buttons","Toggling, Enabling, and Checkable Buttons",[14,1318,1319,1320,1323],{},"A button that controls a persistent state — say, a map tool that stays active — should be ",[68,1321,1322],{},"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.",[268,1325,1327],{"className":270,"code":1326,"language":272,"meta":273,"style":273},"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,1328,1329,1338,1360,1381,1397,1412,1424,1435,1439,1444,1456,1468,1472,1482,1494,1507,1511,1521,1543],{"__ignoreMap":273},[277,1330,1331,1334,1336],{"class":228,"line":279},[277,1332,1333],{"class":282},"def",[277,1335,439],{"class":348},[277,1337,442],{"class":286},[277,1339,1340,1343,1345,1347,1349,1351,1353,1355,1358],{"class":228,"line":290},[277,1341,1342],{"class":286},"    icon ",[277,1344,377],{"class":282},[277,1346,453],{"class":286},[277,1348,456],{"class":361},[277,1350,459],{"class":286},[277,1352,462],{"class":412},[277,1354,465],{"class":286},[277,1356,1357],{"class":412},"\"measure.svg\"",[277,1359,471],{"class":286},[277,1361,1362,1365,1368,1370,1372,1375,1377,1379],{"class":228,"line":304},[277,1363,1364],{"class":361},"    self",[277,1366,1367],{"class":286},".action ",[277,1369,377],{"class":282},[277,1371,482],{"class":286},[277,1373,1374],{"class":412},"\"Measure mode\"",[277,1376,465],{"class":286},[277,1378,456],{"class":361},[277,1380,492],{"class":286},[277,1382,1383,1385,1388,1391,1394],{"class":228,"line":317},[277,1384,1364],{"class":361},[277,1386,1387],{"class":286},".action.setCheckable(",[277,1389,1390],{"class":361},"True",[277,1392,1393],{"class":286},")               ",[277,1395,1396],{"class":1072},"# button can stay pressed\n",[277,1398,1399,1401,1404,1406,1409],{"class":228,"line":330},[277,1400,1364],{"class":361},[277,1402,1403],{"class":286},".action.toggled.connect(",[277,1405,456],{"class":361},[277,1407,1408],{"class":286},".set_mode)   ",[277,1410,1411],{"class":1072},"# fires with True\u002FFalse\n",[277,1413,1414,1416,1419,1421],{"class":228,"line":337},[277,1415,1364],{"class":361},[277,1417,1418],{"class":286},".iface.addToolBarIcon(",[277,1420,456],{"class":361},[277,1422,1423],{"class":286},".action)\n",[277,1425,1426,1428,1431,1433],{"class":228,"line":342},[277,1427,1364],{"class":361},[277,1429,1430],{"class":286},".actions.append(",[277,1432,456],{"class":361},[277,1434,1423],{"class":286},[277,1436,1437],{"class":228,"line":355},[277,1438,334],{"emptyLinePlaceholder":333},[277,1440,1441],{"class":228,"line":368},[277,1442,1443],{"class":1072},"    # React to layer changes: only enable for vector layers\n",[277,1445,1446,1448,1451,1453],{"class":228,"line":383},[277,1447,1364],{"class":361},[277,1449,1450],{"class":286},".iface.currentLayerChanged.connect(",[277,1452,456],{"class":361},[277,1454,1455],{"class":286},"._update_enabled)\n",[277,1457,1458,1460,1463,1465],{"class":228,"line":402},[277,1459,1364],{"class":361},[277,1461,1462],{"class":286},"._update_enabled(",[277,1464,456],{"class":361},[277,1466,1467],{"class":286},".iface.activeLayer())\n",[277,1469,1470],{"class":228,"line":416},[277,1471,334],{"emptyLinePlaceholder":333},[277,1473,1474,1476,1479],{"class":228,"line":429},[277,1475,1333],{"class":282},[277,1477,1478],{"class":348}," _update_enabled",[277,1480,1481],{"class":286},"(self, layer):\n",[277,1483,1484,1487,1489,1491],{"class":228,"line":434},[277,1485,1486],{"class":282},"    from",[277,1488,322],{"class":286},[277,1490,283],{"class":282},[277,1492,1493],{"class":286}," QgsVectorLayer\n",[277,1495,1496,1498,1501,1504],{"class":228,"line":445},[277,1497,1364],{"class":361},[277,1499,1500],{"class":286},".action.setEnabled(",[277,1502,1503],{"class":361},"isinstance",[277,1505,1506],{"class":286},"(layer, QgsVectorLayer))\n",[277,1508,1509],{"class":228,"line":474},[277,1510,334],{"emptyLinePlaceholder":333},[277,1512,1513,1515,1518],{"class":228,"line":495},[277,1514,1333],{"class":282},[277,1516,1517],{"class":348}," set_mode",[277,1519,1520],{"class":286},"(self, checked):\n",[277,1522,1523,1526,1528,1531,1534,1537,1540],{"class":228,"line":506},[277,1524,1525],{"class":286},"    state ",[277,1527,377],{"class":282},[277,1529,1530],{"class":412}," \"on\"",[277,1532,1533],{"class":282}," if",[277,1535,1536],{"class":286}," checked ",[277,1538,1539],{"class":282},"else",[277,1541,1542],{"class":412}," \"off\"\n",[277,1544,1545,1547,1549,1552,1554,1556,1559,1561,1564,1566,1569,1571,1573,1575,1577],{"class":228,"line":517},[277,1546,1364],{"class":361},[277,1548,1223],{"class":286},[277,1550,1551],{"class":412},"\"Measure\"",[277,1553,465],{"class":286},[277,1555,728],{"class":282},[277,1557,1558],{"class":412},"\"Mode ",[277,1560,734],{"class":361},[277,1562,1563],{"class":286},"state",[277,1565,740],{"class":361},[277,1567,1568],{"class":412},"\"",[277,1570,465],{"class":286},[277,1572,692],{"class":683},[277,1574,377],{"class":282},[277,1576,1240],{"class":361},[277,1578,399],{"class":286},[14,1580,1581,777,1583,1586,1587,1590,1591,1593,1594,1597,1598,1600,1601,1604,1605,833],{},[68,1582,776],{},[18,1584,1585],{},"setCheckable(True)"," lets the action hold a pressed\u002Funpressed state; the ",[18,1588,1589],{},"toggled"," signal then delivers the new boolean to your slot, which is more useful than ",[18,1592,32],{}," for stateful tools. Connecting to ",[18,1595,1596],{},"iface.currentLayerChanged"," lets the button grey itself out when the active layer is not a vector — disconnect this signal in ",[18,1599,36],{}," (",[18,1602,1603],{},"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,1606,1608],{"href":1607},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","Add a Custom Dock Widget in PyQGIS",[57,1610,1612],{"id":1611},"qgis-version-compatibility","QGIS Version Compatibility",[14,1614,100,1615,1617,1618,1620],{},[18,1616,20],{}," API and the ",[18,1619,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.",[1622,1623,1624,1643],"table",{},[1625,1626,1627],"thead",{},[1628,1629,1630,1634,1637,1640],"tr",{},[1631,1632,1633],"th",{},"Aspect",[1631,1635,1636],{},"QGIS 3.28 LTR (Py 3.9)",[1631,1638,1639],{},"QGIS 3.34 LTR (Py 3.12)",[1631,1641,1642],{},"QGIS 3.40 \u002F 3.44 (Py 3.12)",[1644,1645,1646,1663,1677,1690],"tbody",{},[1628,1647,1648,1656,1659,1661],{},[1649,1650,1651,1653,1654],"td",{},[18,1652,114],{}," \u002F ",[18,1655,812],{},[1649,1657,1658],{},"stable",[1649,1660,1658],{},[1649,1662,1658],{},[1628,1664,1665,1671,1673,1675],{},[1649,1666,1667,1653,1669],{},[18,1668,122],{},[18,1670,816],{},[1649,1672,1658],{},[1649,1674,1658],{},[1649,1676,1658],{},[1628,1678,1679,1683,1686,1688],{},[1649,1680,1681],{},[18,1682,1596],{},[1649,1684,1685],{},"available",[1649,1687,1685],{},[1649,1689,1685],{},[1628,1691,1692,1695,1698,1700],{},[1649,1693,1694],{},"Icon format",[1649,1696,1697],{},"PNG \u002F SVG",[1649,1699,1697],{},[1649,1701,1697],{},[14,1703,1704],{},"Prefer SVG icons: they stay crisp on high-DPI displays, where PNGs at fixed pixel sizes can look soft.",[57,1706,1708],{"id":1707},"troubleshooting","Troubleshooting",[14,1710,1711,777,1714,1716,1717,813,1719,1721,1722,833],{},[68,1712,1713],{},"Button stays visible after disabling the plugin.",[18,1715,36],{}," is not reversing every registration. For each action call both ",[18,1718,812],{},[18,1720,816],{},"; for a custom toolbar also release it with ",[18,1723,1311],{},[14,1725,1726,1734,1735,1737,1738,833],{},[68,1727,1728,1731,1732,833],{},[18,1729,1730],{},"AttributeError"," on ",[18,1733,114],{}," You called it on the wrong object. The method belongs to ",[18,1736,81],{},", not to your plugin class — use ",[18,1739,1740],{},"self.iface.addToolBarIcon(action)",[14,1742,1743,1746,1747,1750,1751,1753],{},[68,1744,1745],{},"Qt warning about a missing object name."," Set ",[18,1748,1749],{},"toolbar.setObjectName(\"...\")"," immediately after ",[18,1752,129],{},", otherwise QGIS cannot persist its position and logs a warning.",[14,1755,1756,1759,1760,1762,1763,1766],{},[68,1757,1758],{},"Clicking the button does nothing."," The ",[18,1761,32],{}," connection is missing or points at an unbound method. Connect to a bound method (",[18,1764,1765],{},"self.run",") and confirm no exception is swallowed — check the Python console.",[14,1768,1769,1772,1773,1776,1777,777,1780,1783,1784,1786],{},[68,1770,1771],{},"Button does not disable for non-vector layers."," Ensure ",[18,1774,1775],{},"_update_enabled"," is connected to ",[18,1778,1779],{},"currentLayerChanged",[1296,1781,1782],{},"and"," called once during ",[18,1785,28],{}," with the current layer, since the signal only fires on subsequent changes.",[57,1788,1790],{"id":1789},"conclusion","Conclusion",[14,1792,1793,1794,1796,1797,1799,1800,813,1802,1804,1805,1807,1808,1810,1811,1813],{},"A QGIS toolbar button is a ",[18,1795,20],{}," plus disciplined lifecycle management. Build the action with a ",[18,1798,24],{},", register it through ",[18,1801,114],{},[18,1803,122],{}," (or onto a dedicated ",[18,1806,40],{},"), wire ",[18,1809,32],{}," to your callback, and mirror every registration with a matching removal in ",[18,1812,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,1815,1817],{"id":1816},"frequently-asked-questions","Frequently Asked Questions",[14,1819,1820,777,1829,1831,1832,1834,1835,1838],{},[68,1821,1822,1823,1825,1826,1828],{},"What is the difference between ",[18,1824,114],{}," and a custom ",[18,1827,40],{},"?",[18,1830,114],{}," drops your action onto the shared QGIS Plugins toolbar — fine for one button. A custom ",[18,1833,40],{}," from ",[18,1836,1837],{},"iface.addToolBar"," gives your plugin its own dockable, hideable bar, which suits a suite of related tools.",[14,1840,1841,1844],{},[68,1842,1843],{},"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,1846,1847,777,1850,1852],{},[68,1848,1849],{},"Why store actions in a list?",[18,1851,36],{}," needs a reference to every action to remove it. A list makes teardown a simple loop and prevents forgotten orphans.",[14,1854,1855,1860,1861,1863,1864,1866],{},[68,1856,1857,1858,1828],{},"Should I disconnect signals in ",[18,1859,36],{}," Yes, for any signal you connected to ",[18,1862,81],{}," (such as ",[18,1865,1779],{},"). Signals on the action itself are cleaned up with the action, but interface-level connections outlive it and will fire into a dead slot.",[57,1868,1870],{"id":1869},"related","Related",[62,1872,1873,1877,1881],{},[65,1874,1875],{},[46,1876,49],{"href":48},[65,1878,1879],{},[46,1880,54],{"href":53},[65,1882,1883],{},[46,1884,1608],{"href":1607},[1886,1887,1888],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":273,"searchDepth":290,"depth":290,"links":1890},[1891,1892,1893,1894,1895,1896,1897,1898,1899,1900],{"id":59,"depth":290,"text":60},{"id":96,"depth":290,"text":97},{"id":256,"depth":290,"text":257},{"id":820,"depth":290,"text":821},{"id":1315,"depth":290,"text":1316},{"id":1611,"depth":290,"text":1612},{"id":1707,"depth":290,"text":1708},{"id":1789,"depth":290,"text":1790},{"id":1816,"depth":290,"text":1817},{"id":1869,"depth":290,"text":1870},"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",{},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin",{"title":5,"description":1901},"qgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002Findex","eyYQE8oV8-4ywDVds3H_tHDyZXMdCHo66Kf2qJdRZg0",1781792483474]