[{"data":1,"prerenderedAt":1390},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder":3},{"id":4,"title":5,"body":6,"description":1383,"extension":1384,"meta":1385,"navigation":688,"path":1386,"seo":1387,"stem":1388,"__hash__":1389},"docs\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder\u002Findex.md","Create a QGIS Plugin with Plugin Builder 3",{"type":7,"value":8,"toc":1369},"minimark",[9,13,37,46,51,96,99,126,130,140,143,194,216,246,250,259,269,272,373,377,395,398,433,464,471,483,504,508,522,537,570,577,581,588,608,612,618,726,733,1045,1075,1079,1082,1180,1192,1196,1217,1235,1256,1262,1268,1272,1287,1291,1297,1312,1331,1347,1351,1365],[10,11,5],"h1",{"id":12},"create-a-qgis-plugin-with-plugin-builder-3",[14,15,16,17,21,22,25,26,29,30,32,33,36],"p",{},"Writing a plugin skeleton by hand is error-prone: a single typo in ",[18,19,20],"code",{},"metadata.txt"," or a missing ",[18,23,24],{},"classFactory"," keeps your plugin out of the manager. Plugin Builder 3 removes that friction. It is itself a QGIS plugin that runs a wizard, asks a handful of questions, and emits a complete, loadable plugin directory — ",[18,27,28],{},"__init__.py",", ",[18,31,20],{},", a main class wired into the toolbar and menu, a Qt Designer dialog, a ",[18,34,35],{},"resources.qrc",", and a build helper. This page walks through running the wizard, compiling resources, deploying the output to your profile, enabling it, and replacing the placeholder logic with your own.",[14,38,39,40,45],{},"This task sits inside the broader topic of ",[41,42,44],"a",{"href":43},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate & Structure",". If you would rather understand every file before generating it, read that cluster first; Plugin Builder produces exactly the structure described there, so the two pair well.",[47,48,50],"h2",{"id":49},"prerequisites","Prerequisites",[52,53,54,62,72,78,93],"ul",{},[55,56,57,61],"li",{},[58,59,60],"strong",{},"QGIS 3.34 LTR"," (bundles Python 3.12) or any 3.x release with a working Python console",[55,63,64,67,68,71],{},[58,65,66],{},"Plugin Builder 3"," installed via ",[18,69,70],{},"Plugins → Manage and Install Plugins",", then search for \"Plugin Builder\" and install",[55,73,74,77],{},[58,75,76],{},"Plugin Reloader"," (optional but strongly recommended) for fast iteration without restarting QGIS",[55,79,80,81,84,85,88,89,92],{},"A resource compiler on your ",[18,82,83],{},"PATH"," — ",[18,86,87],{},"pyrcc5"," (ships with PyQt5) or ",[18,90,91],{},"pyqgis"," toolchain access",[55,94,95],{},"Write access to your QGIS profile plugins directory",[14,97,98],{},"The profile plugins directory is where QGIS scans for installed plugins:",[52,100,101,110,118],{},[55,102,103,106,107],{},[58,104,105],{},"Windows:"," ",[18,108,109],{},"%APPDATA%\\QGIS\\QGIS3\\profiles\\default\\python\\plugins\\",[55,111,112,106,115],{},[58,113,114],{},"macOS:",[18,116,117],{},"~\u002FLibrary\u002FApplication Support\u002FQGIS\u002FQGIS3\u002Fprofiles\u002Fdefault\u002Fpython\u002Fplugins\u002F",[55,119,120,106,123],{},[58,121,122],{},"Linux:",[18,124,125],{},"~\u002F.local\u002Fshare\u002FQGIS\u002FQGIS3\u002Fprofiles\u002Fdefault\u002Fpython\u002Fplugins\u002F",[47,127,129],{"id":128},"step-1-run-the-plugin-builder-wizard","Step 1: Run the Plugin Builder Wizard",[14,131,132,133,136,137,139],{},"Launch the wizard from ",[18,134,135],{},"Plugins → Plugin Builder → Plugin Builder",". The form is divided into pages; the values you enter map directly onto the generated ",[18,138,20],{}," and class names, so choose them deliberately.",[14,141,142],{},"The first page collects identity:",[52,144,145,161,170,182],{},[55,146,147,84,150,153,154,157,158,160],{},[58,148,149],{},"Class name",[18,151,152],{},"PascalCase",", e.g. ",[18,155,156],{},"FieldAreaCalculator",". This becomes the main plugin class and the value returned by ",[18,159,24],{},".",[55,162,163,166,167,160],{},[58,164,165],{},"Plugin name"," — the human-readable label shown in the plugin manager and menu, e.g. ",[18,168,169],{},"Field Area Calculator",[55,171,172,84,175,153,178,181],{},[58,173,174],{},"Module name",[18,176,177],{},"snake_case",[18,179,180],{},"field_area_calculator",". This becomes the output folder name and the Python module, so it must be a valid identifier with no spaces or hyphens.",[55,183,184,187,188,191,192,160],{},[58,185,186],{},"Description"," and ",[58,189,190],{},"About"," — short and long summaries; both land in ",[18,193,20],{},[14,195,196,197,200,201,204,205,208,209,212,213,160],{},"The second page asks for the ",[58,198,199],{},"plugin template",". Choose ",[58,202,203],{},"\"Tool button with dialog\""," for a classic dialog-based tool, ",[58,206,207],{},"\"Tool button with dock widget\""," for a persistent panel, or ",[58,210,211],{},"\"Processing provider\""," if you are building algorithms. For this walkthrough select ",[58,214,215],{},"Tool button with dialog",[14,217,218,219,222,223,226,227,29,230,233,234,237,238,241,242,245],{},"Subsequent pages collect the ",[58,220,221],{},"menu location"," (Vector, Raster, Database, Web, or a custom plugin menu), text strings, and ",[58,224,225],{},"author details"," (",[18,228,229],{},"author",[18,231,232],{},"email",", the tracker\u002Frepository\u002Fhomepage URLs, and the minimum QGIS version). Set ",[58,235,236],{},"Minimum QGIS version"," to ",[18,239,240],{},"3.34"," to match the LTR baseline. Finally choose an ",[58,243,244],{},"output directory"," — generate into a scratch folder, not directly into your live plugins directory, so you can compile resources before deploying.",[47,247,249],{"id":248},"step-2-inspect-the-generated-file-tree","Step 2: Inspect the Generated File Tree",[14,251,252,253,106,256,258],{},"With ",[58,254,255],{},"module name",[18,257,180],{},", Plugin Builder writes a folder like this:",[260,261,267],"pre",{"className":262,"code":264,"language":265,"meta":266},[263],"language-text","field_area_calculator\u002F\n├── __init__.py\n├── metadata.txt\n├── field_area_calculator.py          # main plugin class\n├── field_area_calculator_dialog.py   # dialog wrapper class\n├── field_area_calculator_dialog_base.ui  # Qt Designer layout\n├── resources.qrc                     # Qt resource manifest\n├── resources.py                      # compiled (may need regeneration)\n├── icon.png\n├── Makefile\n├── pb_tool.cfg                       # pb_tool build config\n├── plugin_upload.py\n├── pylintrc\n├── README.html\n├── help\u002F\n├── i18n\u002F\n│   └── af.ts\n├── scripts\u002F\n└── test\u002F\n","text","",[18,268,264],{"__ignoreMap":266},[14,270,271],{},"The files that matter day to day:",[52,273,274,285,301,323,342,350,361],{},[55,275,276,280,281,284],{},[58,277,278],{},[18,279,28],{}," — contains the ",[18,282,283],{},"classFactory(iface)"," function that imports and returns your main class. QGIS calls this during discovery.",[55,286,287,291,292,295,296,300],{},[58,288,289],{},[18,290,20],{}," — every value you typed in the wizard, now in ",[18,293,294],{},"[general]"," key\u002Fvalue form. This is the single source of truth the plugin manager reads. The companion page on ",[41,297,299],{"href":298},"\u002Fqgis-plugin-development\u002Fpublishing-to-the-qgis-plugin-repository\u002Fwrite-metadata-txt-qgis-plugin\u002F","Write metadata.txt for a QGIS Plugin"," covers each key in depth.",[55,302,303,308,309,29,312,29,315,318,319,322],{},[58,304,305],{},[18,306,307],{},"field_area_calculator.py"," — the main class with ",[18,310,311],{},"initGui()",[18,313,314],{},"unload()",[18,316,317],{},"run()",", and the helper ",[18,320,321],{},"add_action()",". This is where your toolbar button and menu entry are registered.",[55,324,325,330,331,334,335,338,339,160],{},[58,326,327],{},[18,328,329],{},"field_area_calculator_dialog.py"," — a thin ",[18,332,333],{},"QDialog"," subclass that loads the ",[18,336,337],{},".ui"," file with ",[18,340,341],{},"uic.loadUiType",[55,343,344,349],{},[58,345,346],{},[18,347,348],{},"field_area_calculator_dialog_base.ui"," — the editable Qt Designer layout. Open it in Qt Designer to add widgets.",[55,351,352,356,357,360],{},[58,353,354],{},[18,355,35],{}," — lists ",[18,358,359],{},"icon.png"," (and any other assets) for the Qt resource system.",[55,362,363,372],{},[58,364,365,368,369],{},[18,366,367],{},"Makefile"," \u002F ",[18,370,371],{},"pb_tool.cfg"," — two alternative build front-ends; you use one or the other to compile and deploy.",[47,374,376],{"id":375},"step-3-compile-the-resources","Step 3: Compile the Resources",[14,378,379,380,383,384,387,388,390,391,394],{},"The generated main class imports icons through a compiled resource module (",[18,381,382],{},"from .resources import *","). The ",[18,385,386],{},".qrc"," manifest is plain XML; it must be turned into importable Python with ",[18,389,87],{},". Even though Plugin Builder ships a ",[18,392,393],{},"resources.py",", regenerate it so it matches your local PyQt5 version and any icon you swapped in.",[14,396,397],{},"Run this from inside the plugin folder:",[260,399,403],{"className":400,"code":401,"language":402,"meta":266,"style":266},"language-bash shiki shiki-themes github-dark","cd field_area_calculator\npyrcc5 -o resources.py resources.qrc\n","bash",[18,404,405,418],{"__ignoreMap":266},[406,407,410,414],"span",{"class":408,"line":409},"line",1,[406,411,413],{"class":412},"sDLfK","cd",[406,415,417],{"class":416},"sU2Wk"," field_area_calculator\n",[406,419,421,424,427,430],{"class":408,"line":420},2,[406,422,87],{"class":423},"svObZ",[406,425,426],{"class":412}," -o",[406,428,429],{"class":416}," resources.py",[406,431,432],{"class":416}," resources.qrc\n",[14,434,435,106,438,440,441,443,444,447,448,450,451,454,455,457,458,461,462,160],{},[58,436,437],{},"Breakdown:",[18,439,87],{}," reads ",[18,442,35],{},", base64-encodes every referenced ",[18,445,446],{},"\u003Cfile>"," (here, ",[18,449,359],{},"), and writes a Python module that registers them with Qt's virtual resource filesystem under paths like ",[18,452,453],{},":\u002Fplugins\u002Ffield_area_calculator\u002Ficon.png",". Because the icon is embedded, the deployed plugin needs no loose PNG at runtime. If ",[18,456,87],{}," is not found, install it with ",[18,459,460],{},"pip install pyqt5-tools"," outside QGIS, or use the OSGeo4W shell on Windows where it is already on the ",[18,463,83],{},[14,465,466,467,470],{},"If you prefer not to manage the command yourself, ",[18,468,469],{},"pb_tool"," wraps it. From the plugin folder:",[260,472,474],{"className":400,"code":473,"language":402,"meta":266,"style":266},"pb_tool compile\n",[18,475,476],{"__ignoreMap":266},[406,477,478,480],{"class":408,"line":409},[406,479,469],{"class":423},[406,481,482],{"class":416}," compile\n",[14,484,485,106,487,440,490,492,493,187,495,497,498,500,501,160],{},[58,486,437],{},[18,488,489],{},"pb_tool compile",[18,491,371],{},", finds every ",[18,494,386],{},[18,496,337],{}," listed there, and runs the appropriate compiler for each. It is the more portable option in CI because it does not depend on remembering individual ",[18,499,87],{}," invocations. Install it once with ",[18,502,503],{},"pip install pb_tool",[47,505,507],{"id":506},"step-4-deploy-to-the-profile-plugins-directory","Step 4: Deploy to the Profile Plugins Directory",[14,509,510,511,514,515,518,519,521],{},"QGIS only loads plugins from a profile's ",[18,512,513],{},"python\u002Fplugins"," directory. Move the compiled folder there. The cleanest approach during development is ",[18,516,517],{},"pb_tool deploy",", which copies only the files declared in ",[18,520,371],{}," and skips build artifacts:",[260,523,525],{"className":400,"code":524,"language":402,"meta":266,"style":266},"pb_tool deploy -y\n",[18,526,527],{"__ignoreMap":266},[406,528,529,531,534],{"class":408,"line":409},[406,530,469],{"class":423},[406,532,533],{"class":416}," deploy",[406,535,536],{"class":412}," -y\n",[14,538,539,106,541,544,545,548,549,551,552,555,556,559,560,562,563,566,567,569],{},[58,540,437],{},[18,542,543],{},"deploy"," reads the ",[18,546,547],{},"home_plugin"," path resolved from your profile and copies the source files plus the freshly compiled ",[18,550,393],{}," into ",[18,553,554],{},"...\u002Fpython\u002Fplugins\u002Ffield_area_calculator\u002F",". The ",[18,557,558],{},"-y"," flag skips the interactive confirmation, which is convenient when you redeploy frequently. The ",[18,561,367],{}," equivalent is ",[18,564,565],{},"make deploy",", which depends on ",[18,568,87],{}," and a Unix-like shell.",[14,571,572,573,576],{},"If you would rather copy by hand, drop the entire ",[18,574,575],{},"field_area_calculator\u002F"," folder into the profile path listed in the prerequisites. Make sure the folder name exactly equals the module name — a mismatch causes an import failure.",[47,578,580],{"id":579},"step-5-enable-and-test-the-plugin","Step 5: Enable and Test the Plugin",[14,582,583,584,587],{},"Open ",[18,585,586],{},"Plugins → Manage and Install Plugins → Installed",". Your plugin appears in the list; tick its checkbox to enable it. A new toolbar button and a menu entry under the location you chose in the wizard should appear immediately.",[14,589,590,591,594,595,597,598,600,601,603,604,607],{},"Watch the Python console (",[18,592,593],{},"Plugins → Python Console",") while enabling — any traceback during ",[18,596,311],{}," prints there. Click the button: the generated ",[18,599,317],{}," simply shows the empty dialog. To iterate, edit the source in the profile directory, then use ",[58,602,76],{}," (its toolbar button or ",[18,605,606],{},"Ctrl+F5",") to reload without restarting QGIS.",[47,609,611],{"id":610},"step-6-customize-the-generated-code","Step 6: Customize the Generated Code",[14,613,614,615,617],{},"The scaffold is a starting point, not a finished plugin. The default ",[18,616,317],{}," looks like this:",[260,619,623],{"className":620,"code":621,"language":622,"meta":266,"style":266},"language-python shiki shiki-themes github-dark","def run(self):\n    \"\"\"Run method that performs all the real work.\"\"\"\n    if self.first_start:\n        self.first_start = False\n        self.dlg = FieldAreaCalculatorDialog()\n\n    self.dlg.show()\n    result = self.dlg.exec_()\n    if result:\n        pass\n","python",[18,624,625,638,643,655,670,683,690,699,712,720],{"__ignoreMap":266},[406,626,627,631,634],{"class":408,"line":409},[406,628,630],{"class":629},"snl16","def",[406,632,633],{"class":423}," run",[406,635,637],{"class":636},"s95oV","(self):\n",[406,639,640],{"class":408,"line":420},[406,641,642],{"class":416},"    \"\"\"Run method that performs all the real work.\"\"\"\n",[406,644,646,649,652],{"class":408,"line":645},3,[406,647,648],{"class":629},"    if",[406,650,651],{"class":412}," self",[406,653,654],{"class":636},".first_start:\n",[406,656,658,661,664,667],{"class":408,"line":657},4,[406,659,660],{"class":412},"        self",[406,662,663],{"class":636},".first_start ",[406,665,666],{"class":629},"=",[406,668,669],{"class":412}," False\n",[406,671,673,675,678,680],{"class":408,"line":672},5,[406,674,660],{"class":412},[406,676,677],{"class":636},".dlg ",[406,679,666],{"class":629},[406,681,682],{"class":636}," FieldAreaCalculatorDialog()\n",[406,684,686],{"class":408,"line":685},6,[406,687,689],{"emptyLinePlaceholder":688},true,"\n",[406,691,693,696],{"class":408,"line":692},7,[406,694,695],{"class":412},"    self",[406,697,698],{"class":636},".dlg.show()\n",[406,700,702,705,707,709],{"class":408,"line":701},8,[406,703,704],{"class":636},"    result ",[406,706,666],{"class":629},[406,708,651],{"class":412},[406,710,711],{"class":636},".dlg.exec_()\n",[406,713,715,717],{"class":408,"line":714},9,[406,716,648],{"class":629},[406,718,719],{"class":636}," result:\n",[406,721,723],{"class":408,"line":722},10,[406,724,725],{"class":629},"        pass\n",[14,727,728,729,732],{},"Replace the ",[18,730,731],{},"pass"," block with real logic. The example below reads the active vector layer, sums polygon areas in the layer's CRS units, and reports the total — turning the empty wizard output into a working tool:",[260,734,736],{"className":620,"code":735,"language":622,"meta":266,"style":266},"from qgis.core import Qgis, QgsWkbTypes\n\n\ndef run(self):\n    \"\"\"Show the dialog and total the polygon areas on confirmation.\"\"\"\n    if self.first_start:\n        self.first_start = False\n        self.dlg = FieldAreaCalculatorDialog()\n\n    self.dlg.show()\n    if not self.dlg.exec_():\n        return\n\n    layer = self.iface.activeLayer()\n    if layer is None or layer.geometryType() != QgsWkbTypes.PolygonGeometry:\n        self.iface.messageBar().pushMessage(\n            \"Field Area Calculator\",\n            \"Select a polygon layer first.\",\n            level=Qgis.Warning,\n            duration=4,\n        )\n        return\n\n    total = sum(feature.geometry().area() for feature in layer.getFeatures())\n    self.iface.messageBar().pushMessage(\n        \"Field Area Calculator\",\n        f\"Total area: {total:,.2f} {layer.crs().mapUnits()} units\",\n        level=Qgis.Success,\n        duration=6,\n    )\n",[18,737,738,752,756,760,768,773,781,791,801,805,811,824,830,835,848,874,882,891,899,911,924,930,935,940,966,973,981,1015,1026,1039],{"__ignoreMap":266},[406,739,740,743,746,749],{"class":408,"line":409},[406,741,742],{"class":629},"from",[406,744,745],{"class":636}," qgis.core ",[406,747,748],{"class":629},"import",[406,750,751],{"class":636}," Qgis, QgsWkbTypes\n",[406,753,754],{"class":408,"line":420},[406,755,689],{"emptyLinePlaceholder":688},[406,757,758],{"class":408,"line":645},[406,759,689],{"emptyLinePlaceholder":688},[406,761,762,764,766],{"class":408,"line":657},[406,763,630],{"class":629},[406,765,633],{"class":423},[406,767,637],{"class":636},[406,769,770],{"class":408,"line":672},[406,771,772],{"class":416},"    \"\"\"Show the dialog and total the polygon areas on confirmation.\"\"\"\n",[406,774,775,777,779],{"class":408,"line":685},[406,776,648],{"class":629},[406,778,651],{"class":412},[406,780,654],{"class":636},[406,782,783,785,787,789],{"class":408,"line":692},[406,784,660],{"class":412},[406,786,663],{"class":636},[406,788,666],{"class":629},[406,790,669],{"class":412},[406,792,793,795,797,799],{"class":408,"line":701},[406,794,660],{"class":412},[406,796,677],{"class":636},[406,798,666],{"class":629},[406,800,682],{"class":636},[406,802,803],{"class":408,"line":714},[406,804,689],{"emptyLinePlaceholder":688},[406,806,807,809],{"class":408,"line":722},[406,808,695],{"class":412},[406,810,698],{"class":636},[406,812,814,816,819,821],{"class":408,"line":813},11,[406,815,648],{"class":629},[406,817,818],{"class":629}," not",[406,820,651],{"class":412},[406,822,823],{"class":636},".dlg.exec_():\n",[406,825,827],{"class":408,"line":826},12,[406,828,829],{"class":629},"        return\n",[406,831,833],{"class":408,"line":832},13,[406,834,689],{"emptyLinePlaceholder":688},[406,836,838,841,843,845],{"class":408,"line":837},14,[406,839,840],{"class":636},"    layer ",[406,842,666],{"class":629},[406,844,651],{"class":412},[406,846,847],{"class":636},".iface.activeLayer()\n",[406,849,851,853,856,859,862,865,868,871],{"class":408,"line":850},15,[406,852,648],{"class":629},[406,854,855],{"class":636}," layer ",[406,857,858],{"class":629},"is",[406,860,861],{"class":412}," None",[406,863,864],{"class":629}," or",[406,866,867],{"class":636}," layer.geometryType() ",[406,869,870],{"class":629},"!=",[406,872,873],{"class":636}," QgsWkbTypes.PolygonGeometry:\n",[406,875,877,879],{"class":408,"line":876},16,[406,878,660],{"class":412},[406,880,881],{"class":636},".iface.messageBar().pushMessage(\n",[406,883,885,888],{"class":408,"line":884},17,[406,886,887],{"class":416},"            \"Field Area Calculator\"",[406,889,890],{"class":636},",\n",[406,892,894,897],{"class":408,"line":893},18,[406,895,896],{"class":416},"            \"Select a polygon layer first.\"",[406,898,890],{"class":636},[406,900,902,906,908],{"class":408,"line":901},19,[406,903,905],{"class":904},"s9osk","            level",[406,907,666],{"class":629},[406,909,910],{"class":636},"Qgis.Warning,\n",[406,912,914,917,919,922],{"class":408,"line":913},20,[406,915,916],{"class":904},"            duration",[406,918,666],{"class":629},[406,920,921],{"class":412},"4",[406,923,890],{"class":636},[406,925,927],{"class":408,"line":926},21,[406,928,929],{"class":636},"        )\n",[406,931,933],{"class":408,"line":932},22,[406,934,829],{"class":629},[406,936,938],{"class":408,"line":937},23,[406,939,689],{"emptyLinePlaceholder":688},[406,941,943,946,948,951,954,957,960,963],{"class":408,"line":942},24,[406,944,945],{"class":636},"    total ",[406,947,666],{"class":629},[406,949,950],{"class":412}," sum",[406,952,953],{"class":636},"(feature.geometry().area() ",[406,955,956],{"class":629},"for",[406,958,959],{"class":636}," feature ",[406,961,962],{"class":629},"in",[406,964,965],{"class":636}," layer.getFeatures())\n",[406,967,969,971],{"class":408,"line":968},25,[406,970,695],{"class":412},[406,972,881],{"class":636},[406,974,976,979],{"class":408,"line":975},26,[406,977,978],{"class":416},"        \"Field Area Calculator\"",[406,980,890],{"class":636},[406,982,984,987,990,993,996,999,1002,1005,1008,1010,1013],{"class":408,"line":983},27,[406,985,986],{"class":629},"        f",[406,988,989],{"class":416},"\"Total area: ",[406,991,992],{"class":412},"{",[406,994,995],{"class":636},"total",[406,997,998],{"class":629},":,.2f",[406,1000,1001],{"class":412},"}",[406,1003,1004],{"class":412}," {",[406,1006,1007],{"class":636},"layer.crs().mapUnits()",[406,1009,1001],{"class":412},[406,1011,1012],{"class":416}," units\"",[406,1014,890],{"class":636},[406,1016,1018,1021,1023],{"class":408,"line":1017},28,[406,1019,1020],{"class":904},"        level",[406,1022,666],{"class":629},[406,1024,1025],{"class":636},"Qgis.Success,\n",[406,1027,1029,1032,1034,1037],{"class":408,"line":1028},29,[406,1030,1031],{"class":904},"        duration",[406,1033,666],{"class":629},[406,1035,1036],{"class":412},"6",[406,1038,890],{"class":636},[406,1040,1042],{"class":408,"line":1041},30,[406,1043,1044],{"class":636},"    )\n",[14,1046,1047,106,1049,1052,1053,1056,1057,1060,1061,555,1064,1067,1068,1070,1071,160],{},[58,1048,437],{},[18,1050,1051],{},"self.iface.activeLayer()"," returns the layer highlighted in the Layers panel. The guard rejects anything that is not a polygon layer via ",[18,1054,1055],{},"geometryType()",". ",[18,1058,1059],{},"feature.geometry().area()"," returns the planar area in the layer's CRS units, so the result is only meaningful in a projected CRS — for geodesic measurements you would use ",[18,1062,1063],{},"QgsDistanceArea",[18,1065,1066],{},"messageBar()"," calls surface feedback without a blocking dialog. To add inputs to the dialog itself — for example a layer picker — open ",[18,1069,348],{}," in Qt Designer and follow the toolbar and widget patterns in ",[41,1072,1074],{"href":1073},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002F","Add a Toolbar Button to a QGIS Plugin",[47,1076,1078],{"id":1077},"qgis-version-compatibility","QGIS Version Compatibility",[14,1080,1081],{},"Plugin Builder 3 targets the QGIS 3.x \u002F PyQt5 line. The generated code is portable across recent releases, but the resource compiler and a few signal names differ by version.",[1083,1084,1085,1104],"table",{},[1086,1087,1088],"thead",{},[1089,1090,1091,1095,1098,1101],"tr",{},[1092,1093,1094],"th",{},"Component",[1092,1096,1097],{},"QGIS 3.28 LTR (Py 3.9)",[1092,1099,1100],{},"QGIS 3.34 LTR (Py 3.12)",[1092,1102,1103],{},"QGIS 3.40 \u002F 3.44 (Py 3.12)",[1105,1106,1107,1125,1146,1167],"tbody",{},[1089,1108,1109,1113,1117,1121],{},[1110,1111,1112],"td",{},"Resource compiler",[1110,1114,1115],{},[18,1116,87],{},[1110,1118,1119],{},[18,1120,87],{},[1110,1122,1123],{},[18,1124,87],{},[1089,1126,1127,1130,1136,1140],{},[1110,1128,1129],{},"Dialog exec",[1110,1131,1132,1135],{},[18,1133,1134],{},"exec_()"," works",[1110,1137,1138,1135],{},[18,1139,1134],{},[1110,1141,1142,1143],{},"prefer ",[18,1144,1145],{},"exec()",[1089,1147,1148,1153,1158,1162],{},[1110,1149,1150],{},[18,1151,1152],{},"qgisMinimumVersion",[1110,1154,1155],{},[18,1156,1157],{},"3.28",[1110,1159,1160],{},[18,1161,240],{},[1110,1163,1164],{},[18,1165,1166],{},"3.40",[1089,1168,1169,1172,1175,1177],{},[1110,1170,1171],{},"Qt",[1110,1173,1174],{},"Qt 5",[1110,1176,1174],{},[1110,1178,1179],{},"Qt 5 (Qt 6 builds emerging)",[14,1181,1182,1183,1185,1186,1188,1189,1191],{},"Set ",[18,1184,1152],{}," no higher than the oldest QGIS you intend to support. The deprecated ",[18,1187,1134],{}," still works on 3.34 but emits a warning on the newest builds; ",[18,1190,1145],{}," is safe on all 3.x releases.",[47,1193,1195],{"id":1194},"troubleshooting","Troubleshooting",[14,1197,1198,1201,1202,1204,1205,1207,1208,1212,1213,1216],{},[58,1199,1200],{},"Plugin does not appear after deploying."," The folder name must equal the module name and contain ",[18,1203,28],{}," with a ",[18,1206,24],{},". Confirm you deployed to the ",[1209,1210,1211],"em",{},"active"," profile's plugins directory — check ",[18,1214,1215],{},"Settings → User Profiles"," for which profile is loaded.",[14,1218,1219,1224,1225,1227,1228,1231,1232,1234],{},[58,1220,1221,160],{},[18,1222,1223],{},"ModuleNotFoundError: No module named '...resources'"," You skipped compilation, or ",[18,1226,393],{}," did not deploy. Run ",[18,1229,1230],{},"pyrcc5 -o resources.py resources.qrc"," (or ",[18,1233,489],{},") and redeploy.",[14,1236,1237,1240,1241,1243,1244,1246,1247,1249,1250,1252,1253,1255],{},[58,1238,1239],{},"Icon shows as a broken placeholder."," The ",[18,1242,386],{}," path and the actual file disagree, or ",[18,1245,393],{}," is stale. Open ",[18,1248,35],{},", confirm the ",[18,1251,446],{}," entry matches ",[18,1254,359],{},", recompile, and reload.",[14,1257,1258,1261],{},[58,1259,1260],{},"Wizard rejects the module name."," Plugin Builder requires a valid Python identifier. Use lowercase letters, digits, and underscores only — no hyphens or spaces.",[14,1263,1264,1267],{},[58,1265,1266],{},"Changes do not take effect."," QGIS caches imported modules. Use Plugin Reloader rather than re-ticking the checkbox, which does not always re-import edited files.",[47,1269,1271],{"id":1270},"conclusion","Conclusion",[14,1273,1274,1275,1277,1278,1280,1281,1283,1284,1286],{},"Plugin Builder 3 gives you a loadable, conventional plugin in minutes, eliminating the most common boilerplate mistakes. The workflow is consistent: run the wizard, compile the ",[18,1276,386],{}," with ",[18,1279,87],{}," or ",[18,1282,469],{},", deploy to your profile, enable, and then replace the placeholder ",[18,1285,317],{}," with real logic. From here, the natural next steps are wiring richer toolbar actions and building out the dialog interface, both of which build directly on the scaffold you just generated.",[47,1288,1290],{"id":1289},"frequently-asked-questions","Frequently Asked Questions",[14,1292,1293,1296],{},[58,1294,1295],{},"Do I need Plugin Builder installed to ship the plugin?"," No. Plugin Builder only generates the source. Once created, the plugin is self-contained and your users never need Plugin Builder — they just install your packaged zip.",[14,1298,1299,1305,1306,1308,1309,1311],{},[58,1300,1301,1302,1304],{},"Should I commit ",[18,1303,393],{}," to version control?"," It is generated, so many developers gitignore it and compile in CI with ",[18,1307,489],{},". If your contributors lack ",[18,1310,87],{},", committing it is a pragmatic convenience.",[14,1313,1314,1317,1318,1320,1321,1324,1325,1327,1328,1330],{},[58,1315,1316],{},"What is the difference between the Makefile and pb_tool?"," Both compile resources and deploy. The ",[18,1319,367],{}," assumes a Unix shell and ",[18,1322,1323],{},"make","; ",[18,1326,469],{}," is a cross-platform Python CLI driven by ",[18,1329,371],{},", which makes it the friendlier choice on Windows and in CI.",[14,1332,1333,1336,1337,1339,1340,1231,1343,1346],{},[58,1334,1335],{},"Can I change the menu location after generation?"," Yes. The menu is set in ",[18,1338,311],{}," via ",[18,1341,1342],{},"self.iface.addPluginToMenu",[18,1344,1345],{},"addPluginToVectorMenu",", etc.). Edit that call rather than regenerating the whole plugin.",[47,1348,1350],{"id":1349},"related","Related",[52,1352,1353,1357,1361],{},[55,1354,1355],{},[41,1356,44],{"href":43},[55,1358,1359],{},[41,1360,1074],{"href":1073},[55,1362,1363],{},[41,1364,299],{"href":298},[1366,1367,1368],"style",{},"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":266,"searchDepth":420,"depth":420,"links":1370},[1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382],{"id":49,"depth":420,"text":50},{"id":128,"depth":420,"text":129},{"id":248,"depth":420,"text":249},{"id":375,"depth":420,"text":376},{"id":506,"depth":420,"text":507},{"id":579,"depth":420,"text":580},{"id":610,"depth":420,"text":611},{"id":1077,"depth":420,"text":1078},{"id":1194,"depth":420,"text":1195},{"id":1270,"depth":420,"text":1271},{"id":1289,"depth":420,"text":1290},{"id":1349,"depth":420,"text":1350},"Scaffold a working QGIS plugin with Plugin Builder 3. Fill the wizard, compile resources, deploy to your profile, enable it, and customize the generated code.","md",{},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder",{"title":5,"description":1383},"qgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder\u002Findex","EyPDKrFe5X5kbaNrstb7bGLC3rEPXbnc0LeVHREWcrk",1781792483474]