[{"data":1,"prerenderedAt":1820},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings":3},{"id":4,"title":5,"body":6,"description":1809,"extension":1810,"meta":1811,"navigation":314,"path":1816,"seo":1817,"stem":1818,"__hash__":1819},"docs\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings\u002Findex.md","Add a Plugin Options Page to QGIS Settings",{"type":7,"value":8,"toc":1796},"minimark",[9,13,17,26,206,211,246,250,831,856,860,994,1003,1007,1152,1166,1241,1245,1250,1405,1417,1512,1516,1519,1525,1531,1537,1543,1549,1553,1622,1628,1632,1691,1695,1710,1714,1724,1730,1736,1745,1759,1763,1792],[10,11,5],"h1",{"id":12},"add-a-plugin-options-page-to-qgis-settings",[14,15,16],"p",{},"Every plugin with preferences faces the same small design question, and most answer it badly: a separate \"Settings\" item in the plugin menu, opening a bespoke dialog that looks nothing like QGIS and that users forget exists. QGIS offers the alternative in a few dozen lines — a page inside Settings → Options, next to the application's own, searchable, consistently styled, and exactly where somebody looking for preferences will look.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002F","Plugin Settings and Localization",". It covers the page widget, the factory that publishes it, loading and applying values, validation, and the unregistration that stops a reloaded plugin from crashing the options dialog.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,99,105,108,112,115,119,124,129,132,136,140,145,150,155,158,161,164,168,175,180,185,190,194,197,203],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 272","img","Diagram of the QGIS options dialog with its built-in pages listed on the left and a registered plugin page appearing among them, showing the widget, its apply method and the settings it writes","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Where a registered options page appears",[40,41,42],"desc",{},"The QGIS options dialog lists its built-in pages down the left side: general, system, coordinate reference systems, rendering and others. A registered plugin page appears in the same list with its own title and icon. Selecting it shows the plugin's widget on the right, and pressing OK calls the widget's apply method, which writes to settings.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","272","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"optPageArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Your preferences, in the place preferences live",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"20","48","440","200","10","#fffdf7","#59645f","stroke-width:2",[68,87,91],{"x":88,"y":89,"style":90,"fill":73,"textAnchor":74},"240","72","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","Settings and Options",[44,93],{"x":94,"y":95,"width":96,"height":97,"rx":59,"fill":49,"stroke":84,"style":98},"36","86","150","26","stroke-width:1.2",[68,100,104],{"x":101,"y":102,"style":103,"fill":84},"50","104","font-size:11px;font-family:sans-serif","General",[44,106],{"x":94,"y":107,"width":96,"height":97,"rx":59,"fill":49,"stroke":84,"style":98},"116",[68,109,111],{"x":101,"y":110,"style":103,"fill":84},"134","System",[44,113],{"x":94,"y":114,"width":96,"height":97,"rx":59,"fill":49,"stroke":84,"style":98},"146",[68,116,118],{"x":101,"y":117,"style":103,"fill":84},"164","Rendering",[44,120],{"x":94,"y":121,"width":96,"height":97,"rx":59,"fill":122,"stroke":123,"style":85},"176","#eef7f4","#0f766e",[68,125,128],{"x":101,"y":126,"style":127,"fill":123},"194","font-size:11px;font-weight:bold;font-family:sans-serif","Parcel Tools",[44,130],{"x":94,"y":131,"width":96,"height":97,"rx":59,"fill":49,"stroke":84,"style":98},"206",[68,133,135],{"x":101,"y":134,"style":103,"fill":84},"224","Advanced",[44,137],{"x":81,"y":95,"width":138,"height":114,"rx":139,"fill":122,"stroke":123,"style":85},"244","6",[68,141,144],{"x":142,"y":143,"style":90,"fill":123,"textAnchor":74},"322","112","your widget",[44,146],{"x":147,"y":148,"width":149,"height":97,"rx":59,"fill":83,"stroke":84,"style":98},"216","126","212",[68,151,154],{"x":152,"y":153,"style":103,"fill":66},"230","144","output folder",[44,156],{"x":147,"y":157,"width":149,"height":97,"rx":59,"fill":83,"stroke":84,"style":98},"158",[68,159,160],{"x":152,"y":121,"style":103,"fill":66},"add result to map",[44,162],{"x":147,"y":163,"width":149,"height":97,"rx":59,"fill":83,"stroke":84,"style":98},"190",[68,165,167],{"x":152,"y":166,"style":103,"fill":66},"208","buffer distance",[44,169],{"x":170,"y":95,"width":88,"height":171,"rx":58,"fill":172,"stroke":173,"style":174},"500","60","#edf8e9","#15803d","stroke-width:2.5",[68,176,179],{"x":177,"y":178,"style":90,"fill":173,"textAnchor":74},"620","110","OK pressed",[68,181,184],{"x":177,"y":182,"style":183,"fill":66,"textAnchor":74},"130","text-anchor:middle;font-size:11px;font-family:sans-serif","apply() is called",[44,186],{"x":170,"y":187,"width":88,"height":171,"rx":58,"fill":188,"stroke":189,"style":174},"172","#eff3ff","#2563eb",[68,191,193],{"x":177,"y":192,"style":90,"fill":189,"textAnchor":74},"196","QgsSettings",[68,195,196],{"x":177,"y":147,"style":183,"fill":66,"textAnchor":74},"values persisted",[198,199],"line",{"x1":200,"y1":107,"x2":201,"y2":107,"stroke":66,"style":202},"460","494","stroke-width:2;marker-end:url(#optPageArrow)",[198,204],{"x1":177,"y1":114,"x2":177,"y2":205,"stroke":66,"style":202},"166",[207,208,210],"h2",{"id":209},"prerequisites","Prerequisites",[212,213,214,222,239],"ul",{},[215,216,217,221],"li",{},[218,219,220],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[215,223,224,225,229,230,233,234,238],{},"A working plugin with ",[226,227,228],"code",{},"initGui()"," and ",[226,231,232],{},"unload()"," — see ",[21,235,237],{"href":236},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate and Structure",".",[215,240,241,242,238],{},"Somewhere to store the values; this page assumes the conventions in ",[21,243,245],{"href":244},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fstore-plugin-settings-with-qgssettings\u002F","Store Plugin Settings with QgsSettings",[207,247,249],{"id":248},"build-the-page-widget","Build the page widget",[251,252,257],"pre",{"className":253,"code":254,"language":255,"meta":256,"style":256},"language-python shiki shiki-themes github-dark","from qgis.PyQt.QtWidgets import (QVBoxLayout, QFormLayout, QLineEdit,\n                                 QCheckBox, QDoubleSpinBox)\nfrom qgis.core import QgsSettings\nfrom qgis.gui import QgsOptionsPageWidget\n\nPREFIX = \"parcel_tools\"\n\n\nclass ParcelToolsOptionsPage(QgsOptionsPageWidget):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n\n        self.output_folder = QLineEdit()\n        self.add_to_map = QCheckBox()\n        self.buffer_distance = QDoubleSpinBox()\n        self.buffer_distance.setRange(0.0, 10000.0)\n        self.buffer_distance.setSuffix(\" m\")\n\n        form = QFormLayout()\n        form.addRow(self.tr(\"Output folder\"), self.output_folder)\n        form.addRow(self.tr(\"Add result to map\"), self.add_to_map)\n        form.addRow(self.tr(\"Default buffer distance\"), self.buffer_distance)\n\n        layout = QVBoxLayout()\n        layout.addLayout(form)\n        layout.addStretch()\n        self.setLayout(layout)\n\n        self.load_settings()\n\n    def load_settings(self):\n        settings = QgsSettings()\n        self.output_folder.setText(\n            settings.value(f\"{PREFIX}\u002Foutput_folder\", \"\", type=str))\n        self.add_to_map.setChecked(\n            settings.value(f\"{PREFIX}\u002Fadd_to_map\", True, type=bool))\n        self.buffer_distance.setValue(\n            settings.value(f\"{PREFIX}\u002Fbuffer_distance\", 25.0, type=float))\n\n    def apply(self):\n        settings = QgsSettings()\n        settings.setValue(f\"{PREFIX}\u002Foutput_folder\", self.output_folder.text())\n        settings.setValue(f\"{PREFIX}\u002Fadd_to_map\", self.add_to_map.isChecked())\n        settings.setValue(f\"{PREFIX}\u002Fbuffer_distance\", self.buffer_distance.value())\n","python","",[226,258,259,277,283,296,309,316,330,335,340,359,379,394,399,413,426,439,459,472,477,488,511,530,549,554,565,571,577,585,590,598,603,614,625,633,670,678,708,716,746,751,761,770,791,811],{"__ignoreMap":256},[260,261,263,267,271,274],"span",{"class":198,"line":262},1,[260,264,266],{"class":265},"snl16","from",[260,268,270],{"class":269},"s95oV"," qgis.PyQt.QtWidgets ",[260,272,273],{"class":265},"import",[260,275,276],{"class":269}," (QVBoxLayout, QFormLayout, QLineEdit,\n",[260,278,280],{"class":198,"line":279},2,[260,281,282],{"class":269},"                                 QCheckBox, QDoubleSpinBox)\n",[260,284,286,288,291,293],{"class":198,"line":285},3,[260,287,266],{"class":265},[260,289,290],{"class":269}," qgis.core ",[260,292,273],{"class":265},[260,294,295],{"class":269}," QgsSettings\n",[260,297,299,301,304,306],{"class":198,"line":298},4,[260,300,266],{"class":265},[260,302,303],{"class":269}," qgis.gui ",[260,305,273],{"class":265},[260,307,308],{"class":269}," QgsOptionsPageWidget\n",[260,310,312],{"class":198,"line":311},5,[260,313,315],{"emptyLinePlaceholder":314},true,"\n",[260,317,319,323,326],{"class":198,"line":318},6,[260,320,322],{"class":321},"sDLfK","PREFIX",[260,324,325],{"class":265}," =",[260,327,329],{"class":328},"sU2Wk"," \"parcel_tools\"\n",[260,331,333],{"class":198,"line":332},7,[260,334,315],{"emptyLinePlaceholder":314},[260,336,338],{"class":198,"line":337},8,[260,339,315],{"emptyLinePlaceholder":314},[260,341,343,346,350,353,356],{"class":198,"line":342},9,[260,344,345],{"class":265},"class",[260,347,349],{"class":348},"svObZ"," ParcelToolsOptionsPage",[260,351,352],{"class":269},"(",[260,354,355],{"class":348},"QgsOptionsPageWidget",[260,357,358],{"class":269},"):\n",[260,360,362,365,368,371,374,377],{"class":198,"line":361},10,[260,363,364],{"class":265},"    def",[260,366,367],{"class":321}," __init__",[260,369,370],{"class":269},"(self, parent",[260,372,373],{"class":265},"=",[260,375,376],{"class":321},"None",[260,378,358],{"class":269},[260,380,382,385,388,391],{"class":198,"line":381},11,[260,383,384],{"class":321},"        super",[260,386,387],{"class":269},"().",[260,389,390],{"class":321},"__init__",[260,392,393],{"class":269},"(parent)\n",[260,395,397],{"class":198,"line":396},12,[260,398,315],{"emptyLinePlaceholder":314},[260,400,402,405,408,410],{"class":198,"line":401},13,[260,403,404],{"class":321},"        self",[260,406,407],{"class":269},".output_folder ",[260,409,373],{"class":265},[260,411,412],{"class":269}," QLineEdit()\n",[260,414,416,418,421,423],{"class":198,"line":415},14,[260,417,404],{"class":321},[260,419,420],{"class":269},".add_to_map ",[260,422,373],{"class":265},[260,424,425],{"class":269}," QCheckBox()\n",[260,427,429,431,434,436],{"class":198,"line":428},15,[260,430,404],{"class":321},[260,432,433],{"class":269},".buffer_distance ",[260,435,373],{"class":265},[260,437,438],{"class":269}," QDoubleSpinBox()\n",[260,440,442,444,447,450,453,456],{"class":198,"line":441},16,[260,443,404],{"class":321},[260,445,446],{"class":269},".buffer_distance.setRange(",[260,448,449],{"class":321},"0.0",[260,451,452],{"class":269},", ",[260,454,455],{"class":321},"10000.0",[260,457,458],{"class":269},")\n",[260,460,462,464,467,470],{"class":198,"line":461},17,[260,463,404],{"class":321},[260,465,466],{"class":269},".buffer_distance.setSuffix(",[260,468,469],{"class":328},"\" m\"",[260,471,458],{"class":269},[260,473,475],{"class":198,"line":474},18,[260,476,315],{"emptyLinePlaceholder":314},[260,478,480,483,485],{"class":198,"line":479},19,[260,481,482],{"class":269},"        form ",[260,484,373],{"class":265},[260,486,487],{"class":269}," QFormLayout()\n",[260,489,491,494,497,500,503,506,508],{"class":198,"line":490},20,[260,492,493],{"class":269},"        form.addRow(",[260,495,496],{"class":321},"self",[260,498,499],{"class":269},".tr(",[260,501,502],{"class":328},"\"Output folder\"",[260,504,505],{"class":269},"), ",[260,507,496],{"class":321},[260,509,510],{"class":269},".output_folder)\n",[260,512,514,516,518,520,523,525,527],{"class":198,"line":513},21,[260,515,493],{"class":269},[260,517,496],{"class":321},[260,519,499],{"class":269},[260,521,522],{"class":328},"\"Add result to map\"",[260,524,505],{"class":269},[260,526,496],{"class":321},[260,528,529],{"class":269},".add_to_map)\n",[260,531,533,535,537,539,542,544,546],{"class":198,"line":532},22,[260,534,493],{"class":269},[260,536,496],{"class":321},[260,538,499],{"class":269},[260,540,541],{"class":328},"\"Default buffer distance\"",[260,543,505],{"class":269},[260,545,496],{"class":321},[260,547,548],{"class":269},".buffer_distance)\n",[260,550,552],{"class":198,"line":551},23,[260,553,315],{"emptyLinePlaceholder":314},[260,555,557,560,562],{"class":198,"line":556},24,[260,558,559],{"class":269},"        layout ",[260,561,373],{"class":265},[260,563,564],{"class":269}," QVBoxLayout()\n",[260,566,568],{"class":198,"line":567},25,[260,569,570],{"class":269},"        layout.addLayout(form)\n",[260,572,574],{"class":198,"line":573},26,[260,575,576],{"class":269},"        layout.addStretch()\n",[260,578,580,582],{"class":198,"line":579},27,[260,581,404],{"class":321},[260,583,584],{"class":269},".setLayout(layout)\n",[260,586,588],{"class":198,"line":587},28,[260,589,315],{"emptyLinePlaceholder":314},[260,591,593,595],{"class":198,"line":592},29,[260,594,404],{"class":321},[260,596,597],{"class":269},".load_settings()\n",[260,599,601],{"class":198,"line":600},30,[260,602,315],{"emptyLinePlaceholder":314},[260,604,606,608,611],{"class":198,"line":605},31,[260,607,364],{"class":265},[260,609,610],{"class":348}," load_settings",[260,612,613],{"class":269},"(self):\n",[260,615,617,620,622],{"class":198,"line":616},32,[260,618,619],{"class":269},"        settings ",[260,621,373],{"class":265},[260,623,624],{"class":269}," QgsSettings()\n",[260,626,628,630],{"class":198,"line":627},33,[260,629,404],{"class":321},[260,631,632],{"class":269},".output_folder.setText(\n",[260,634,636,639,642,645,648,651,653,656,658,662,664,667],{"class":198,"line":635},34,[260,637,638],{"class":269},"            settings.value(",[260,640,641],{"class":265},"f",[260,643,644],{"class":328},"\"",[260,646,647],{"class":321},"{PREFIX}",[260,649,650],{"class":328},"\u002Foutput_folder\"",[260,652,452],{"class":269},[260,654,655],{"class":328},"\"\"",[260,657,452],{"class":269},[260,659,661],{"class":660},"s9osk","type",[260,663,373],{"class":265},[260,665,666],{"class":321},"str",[260,668,669],{"class":269},"))\n",[260,671,673,675],{"class":198,"line":672},35,[260,674,404],{"class":321},[260,676,677],{"class":269},".add_to_map.setChecked(\n",[260,679,681,683,685,687,689,692,694,697,699,701,703,706],{"class":198,"line":680},36,[260,682,638],{"class":269},[260,684,641],{"class":265},[260,686,644],{"class":328},[260,688,647],{"class":321},[260,690,691],{"class":328},"\u002Fadd_to_map\"",[260,693,452],{"class":269},[260,695,696],{"class":321},"True",[260,698,452],{"class":269},[260,700,661],{"class":660},[260,702,373],{"class":265},[260,704,705],{"class":321},"bool",[260,707,669],{"class":269},[260,709,711,713],{"class":198,"line":710},37,[260,712,404],{"class":321},[260,714,715],{"class":269},".buffer_distance.setValue(\n",[260,717,719,721,723,725,727,730,732,735,737,739,741,744],{"class":198,"line":718},38,[260,720,638],{"class":269},[260,722,641],{"class":265},[260,724,644],{"class":328},[260,726,647],{"class":321},[260,728,729],{"class":328},"\u002Fbuffer_distance\"",[260,731,452],{"class":269},[260,733,734],{"class":321},"25.0",[260,736,452],{"class":269},[260,738,661],{"class":660},[260,740,373],{"class":265},[260,742,743],{"class":321},"float",[260,745,669],{"class":269},[260,747,749],{"class":198,"line":748},39,[260,750,315],{"emptyLinePlaceholder":314},[260,752,754,756,759],{"class":198,"line":753},40,[260,755,364],{"class":265},[260,757,758],{"class":348}," apply",[260,760,613],{"class":269},[260,762,764,766,768],{"class":198,"line":763},41,[260,765,619],{"class":269},[260,767,373],{"class":265},[260,769,624],{"class":269},[260,771,773,776,778,780,782,784,786,788],{"class":198,"line":772},42,[260,774,775],{"class":269},"        settings.setValue(",[260,777,641],{"class":265},[260,779,644],{"class":328},[260,781,647],{"class":321},[260,783,650],{"class":328},[260,785,452],{"class":269},[260,787,496],{"class":321},[260,789,790],{"class":269},".output_folder.text())\n",[260,792,794,796,798,800,802,804,806,808],{"class":198,"line":793},43,[260,795,775],{"class":269},[260,797,641],{"class":265},[260,799,644],{"class":328},[260,801,647],{"class":321},[260,803,691],{"class":328},[260,805,452],{"class":269},[260,807,496],{"class":321},[260,809,810],{"class":269},".add_to_map.isChecked())\n",[260,812,814,816,818,820,822,824,826,828],{"class":198,"line":813},44,[260,815,775],{"class":269},[260,817,641],{"class":265},[260,819,644],{"class":328},[260,821,647],{"class":321},[260,823,729],{"class":328},[260,825,452],{"class":269},[260,827,496],{"class":321},[260,829,830],{"class":269},".buffer_distance.value())\n",[14,832,833,836,837,839,840,843,844,847,848,851,852,855],{},[218,834,835],{},"Breakdown:"," ",[226,838,355],{}," is a plain widget with one contract — QGIS calls ",[226,841,842],{},"apply()"," when the user accepts the dialog. Loading in the constructor is right because the widget is created each time the page is opened, so it always shows current values. Using a ",[226,845,846],{},"QFormLayout"," rather than absolute positions is what lets the page survive translation, where German labels can be half again as long; the trailing ",[226,849,850],{},"addStretch()"," keeps the fields at the top instead of spread down the page. Wrapping every label in ",[226,853,854],{},"self.tr()"," costs nothing now and makes the page translatable later.",[207,857,859],{"id":858},"publish-it-with-a-factory","Publish it with a factory",[251,861,863],{"className":253,"code":862,"language":255,"meta":256,"style":256},"from qgis.PyQt.QtGui import QIcon\nfrom qgis.gui import QgsOptionsWidgetFactory\n\n\nclass ParcelToolsOptionsFactory(QgsOptionsWidgetFactory):\n    def __init__(self, plugin_dir):\n        super().__init__()\n        self.setTitle(\"Parcel Tools\")\n        self.setIcon(QIcon(f\"{plugin_dir}\u002Ficon.png\"))\n\n    def createWidget(self, parent=None):\n        return ParcelToolsOptionsPage(parent)\n",[226,864,865,877,888,892,896,910,919,930,942,967,971,986],{"__ignoreMap":256},[260,866,867,869,872,874],{"class":198,"line":262},[260,868,266],{"class":265},[260,870,871],{"class":269}," qgis.PyQt.QtGui ",[260,873,273],{"class":265},[260,875,876],{"class":269}," QIcon\n",[260,878,879,881,883,885],{"class":198,"line":279},[260,880,266],{"class":265},[260,882,303],{"class":269},[260,884,273],{"class":265},[260,886,887],{"class":269}," QgsOptionsWidgetFactory\n",[260,889,890],{"class":198,"line":285},[260,891,315],{"emptyLinePlaceholder":314},[260,893,894],{"class":198,"line":298},[260,895,315],{"emptyLinePlaceholder":314},[260,897,898,900,903,905,908],{"class":198,"line":311},[260,899,345],{"class":265},[260,901,902],{"class":348}," ParcelToolsOptionsFactory",[260,904,352],{"class":269},[260,906,907],{"class":348},"QgsOptionsWidgetFactory",[260,909,358],{"class":269},[260,911,912,914,916],{"class":198,"line":318},[260,913,364],{"class":265},[260,915,367],{"class":321},[260,917,918],{"class":269},"(self, plugin_dir):\n",[260,920,921,923,925,927],{"class":198,"line":332},[260,922,384],{"class":321},[260,924,387],{"class":269},[260,926,390],{"class":321},[260,928,929],{"class":269},"()\n",[260,931,932,934,937,940],{"class":198,"line":337},[260,933,404],{"class":321},[260,935,936],{"class":269},".setTitle(",[260,938,939],{"class":328},"\"Parcel Tools\"",[260,941,458],{"class":269},[260,943,944,946,949,951,953,956,959,962,965],{"class":198,"line":342},[260,945,404],{"class":321},[260,947,948],{"class":269},".setIcon(QIcon(",[260,950,641],{"class":265},[260,952,644],{"class":328},[260,954,955],{"class":321},"{",[260,957,958],{"class":269},"plugin_dir",[260,960,961],{"class":321},"}",[260,963,964],{"class":328},"\u002Ficon.png\"",[260,966,669],{"class":269},[260,968,969],{"class":198,"line":361},[260,970,315],{"emptyLinePlaceholder":314},[260,972,973,975,978,980,982,984],{"class":198,"line":381},[260,974,364],{"class":265},[260,976,977],{"class":348}," createWidget",[260,979,370],{"class":269},[260,981,373],{"class":265},[260,983,376],{"class":321},[260,985,358],{"class":269},[260,987,988,991],{"class":198,"line":396},[260,989,990],{"class":265},"        return",[260,992,993],{"class":269}," ParcelToolsOptionsPage(parent)\n",[14,995,996,998,999,1002],{},[218,997,835],{}," The factory exists so QGIS can create the page lazily — nothing is built until the user opens that page, which keeps the options dialog fast however many plugins register. The title is what appears in the list on the left, so keep it to the plugin's name rather than \"Settings\". An icon is optional but makes the entry findable in a long list. ",[226,1000,1001],{},"createWidget()"," must return a new instance each time; returning a cached one produces a page that shows stale values after a cancel.",[207,1004,1006],{"id":1005},"register-and-unregister","Register and unregister",[251,1008,1010],{"className":253,"code":1009,"language":255,"meta":256,"style":256},"class ParcelTools:\n    def __init__(self, iface):\n        self.iface = iface\n        self.options_factory = None\n\n    def initGui(self):\n        self.options_factory = ParcelToolsOptionsFactory(self.plugin_dir)\n        self.iface.registerOptionsWidgetFactory(self.options_factory)\n\n    def unload(self):\n        if self.options_factory is not None:\n            self.iface.unregisterOptionsWidgetFactory(self.options_factory)\n            self.options_factory = None\n",[226,1011,1012,1022,1031,1043,1055,1059,1068,1084,1096,1100,1109,1130,1142],{"__ignoreMap":256},[260,1013,1014,1016,1019],{"class":198,"line":262},[260,1015,345],{"class":265},[260,1017,1018],{"class":348}," ParcelTools",[260,1020,1021],{"class":269},":\n",[260,1023,1024,1026,1028],{"class":198,"line":279},[260,1025,364],{"class":265},[260,1027,367],{"class":321},[260,1029,1030],{"class":269},"(self, iface):\n",[260,1032,1033,1035,1038,1040],{"class":198,"line":285},[260,1034,404],{"class":321},[260,1036,1037],{"class":269},".iface ",[260,1039,373],{"class":265},[260,1041,1042],{"class":269}," iface\n",[260,1044,1045,1047,1050,1052],{"class":198,"line":298},[260,1046,404],{"class":321},[260,1048,1049],{"class":269},".options_factory ",[260,1051,373],{"class":265},[260,1053,1054],{"class":321}," None\n",[260,1056,1057],{"class":198,"line":311},[260,1058,315],{"emptyLinePlaceholder":314},[260,1060,1061,1063,1066],{"class":198,"line":318},[260,1062,364],{"class":265},[260,1064,1065],{"class":348}," initGui",[260,1067,613],{"class":269},[260,1069,1070,1072,1074,1076,1079,1081],{"class":198,"line":332},[260,1071,404],{"class":321},[260,1073,1049],{"class":269},[260,1075,373],{"class":265},[260,1077,1078],{"class":269}," ParcelToolsOptionsFactory(",[260,1080,496],{"class":321},[260,1082,1083],{"class":269},".plugin_dir)\n",[260,1085,1086,1088,1091,1093],{"class":198,"line":337},[260,1087,404],{"class":321},[260,1089,1090],{"class":269},".iface.registerOptionsWidgetFactory(",[260,1092,496],{"class":321},[260,1094,1095],{"class":269},".options_factory)\n",[260,1097,1098],{"class":198,"line":342},[260,1099,315],{"emptyLinePlaceholder":314},[260,1101,1102,1104,1107],{"class":198,"line":361},[260,1103,364],{"class":265},[260,1105,1106],{"class":348}," unload",[260,1108,613],{"class":269},[260,1110,1111,1114,1117,1119,1122,1125,1128],{"class":198,"line":381},[260,1112,1113],{"class":265},"        if",[260,1115,1116],{"class":321}," self",[260,1118,1049],{"class":269},[260,1120,1121],{"class":265},"is",[260,1123,1124],{"class":265}," not",[260,1126,1127],{"class":321}," None",[260,1129,1021],{"class":269},[260,1131,1132,1135,1138,1140],{"class":198,"line":396},[260,1133,1134],{"class":321},"            self",[260,1136,1137],{"class":269},".iface.unregisterOptionsWidgetFactory(",[260,1139,496],{"class":321},[260,1141,1095],{"class":269},[260,1143,1144,1146,1148,1150],{"class":198,"line":401},[260,1145,1134],{"class":321},[260,1147,1049],{"class":269},[260,1149,373],{"class":265},[260,1151,1054],{"class":321},[14,1153,1154,1156,1157,1159,1160,1162,1163,1165],{},[218,1155,835],{}," Keeping a reference on the plugin object is not optional: a factory created as a local variable is garbage-collected while QGIS still holds a pointer to it, and the options dialog then crashes the application rather than raising. The unregister in ",[226,1158,232],{}," is equally load-bearing — ",[226,1161,232],{}," runs on every plugin reload during development, and without it each reload adds another page, all but one of which point at torn-down objects. Setting the attribute back to ",[226,1164,376],{}," makes a double unload harmless, which happens more often than you would expect during a reload cycle.",[14,1167,1168],{},[29,1169,1172,1175,1178,1181,1184,1188,1193,1200,1204,1206,1210,1212,1216,1221,1225,1228,1232,1234,1236,1238],{"viewBox":1170,"role":32,"ariaLabel":1171,"xmlns":34},"0 0 760 250","Comparison of a correct register and unregister cycle producing one options page against a missing unregister producing duplicate pages that reference dead plugin objects",[36,1173,1174],{},"What three reloads look like, with and without unregistering",[40,1176,1177],{},"With unregistration in unload, each reload replaces the previous page and the options dialog shows exactly one entry. Without it, each reload adds another entry, and the older entries point at plugin objects that have been destroyed, so opening them crashes QGIS.",[44,1179],{"x":46,"y":46,"width":47,"height":1180,"fill":49},"250",[68,1182,1183],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"The bug appears only after you reload during development",[44,1185],{"x":78,"y":79,"width":1186,"height":1187,"rx":82,"fill":172,"stroke":173,"style":174},"348","180",[68,1189,1192],{"x":126,"y":1190,"style":1191,"fill":173,"textAnchor":74},"74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","unregistered in unload()",[44,1194],{"x":1195,"y":1196,"width":1197,"height":1198,"rx":139,"fill":83,"stroke":84,"style":1199},"44","92","300","34","stroke-width:1.5",[68,1201,1203],{"x":171,"y":1202,"style":103,"fill":66},"114","reload 1 — Parcel Tools",[44,1205],{"x":1195,"y":110,"width":1197,"height":1198,"rx":139,"fill":83,"stroke":84,"style":1199},[68,1207,1209],{"x":171,"y":1208,"style":103,"fill":66},"156","reload 2 — Parcel Tools",[44,1211],{"x":1195,"y":121,"width":1197,"height":1198,"rx":139,"fill":83,"stroke":173,"style":85},[68,1213,1215],{"x":171,"y":1214,"style":127,"fill":173},"198","still exactly one page",[44,1217],{"x":1218,"y":79,"width":1186,"height":1187,"rx":82,"fill":1219,"stroke":1220,"style":174},"392","#fdf2e2","#b91c1c",[68,1222,1224],{"x":1223,"y":1190,"style":1191,"fill":1220,"textAnchor":74},"566","unregister forgotten",[44,1226],{"x":1227,"y":1196,"width":1197,"height":1198,"rx":139,"fill":83,"stroke":84,"style":1199},"416",[68,1229,1231],{"x":1230,"y":1202,"style":103,"fill":66},"432","Parcel Tools — dead object",[44,1233],{"x":1227,"y":110,"width":1197,"height":1198,"rx":139,"fill":83,"stroke":84,"style":1199},[68,1235,1231],{"x":1230,"y":1208,"style":103,"fill":66},[44,1237],{"x":1227,"y":121,"width":1197,"height":1198,"rx":139,"fill":49,"stroke":1220,"style":85},[68,1239,1240],{"x":1230,"y":1214,"style":127,"fill":1220},"opening one crashes QGIS",[207,1242,1244],{"id":1243},"validate-before-applying","Validate before applying",[14,1246,1247,1249],{},[226,1248,842],{}," is called when the user presses OK, and QGIS does not expect it to fail. Validate in a way that guides rather than blocks.",[251,1251,1253],{"className":253,"code":1252,"language":255,"meta":256,"style":256},"import os\nfrom qgis.core import Qgis\n\n    def apply(self):\n        folder = self.output_folder.text().strip()\n\n        if folder and not os.path.isdir(folder):\n            self.iface.messageBar().pushMessage(\n                self.tr(\"Parcel Tools\"),\n                self.tr(\"Output folder does not exist; keeping the previous value.\"),\n                level=Qgis.Warning,\n                duration=6,\n            )\n            return\n\n        settings = QgsSettings()\n        settings.setValue(f\"{PREFIX}\u002Foutput_folder\", folder)\n",[226,1254,1255,1262,1273,1277,1285,1297,1301,1316,1323,1335,1346,1356,1368,1373,1378,1382,1390],{"__ignoreMap":256},[260,1256,1257,1259],{"class":198,"line":262},[260,1258,273],{"class":265},[260,1260,1261],{"class":269}," os\n",[260,1263,1264,1266,1268,1270],{"class":198,"line":279},[260,1265,266],{"class":265},[260,1267,290],{"class":269},[260,1269,273],{"class":265},[260,1271,1272],{"class":269}," Qgis\n",[260,1274,1275],{"class":198,"line":285},[260,1276,315],{"emptyLinePlaceholder":314},[260,1278,1279,1281,1283],{"class":198,"line":298},[260,1280,364],{"class":265},[260,1282,758],{"class":348},[260,1284,613],{"class":269},[260,1286,1287,1290,1292,1294],{"class":198,"line":311},[260,1288,1289],{"class":269},"        folder ",[260,1291,373],{"class":265},[260,1293,1116],{"class":321},[260,1295,1296],{"class":269},".output_folder.text().strip()\n",[260,1298,1299],{"class":198,"line":318},[260,1300,315],{"emptyLinePlaceholder":314},[260,1302,1303,1305,1308,1311,1313],{"class":198,"line":332},[260,1304,1113],{"class":265},[260,1306,1307],{"class":269}," folder ",[260,1309,1310],{"class":265},"and",[260,1312,1124],{"class":265},[260,1314,1315],{"class":269}," os.path.isdir(folder):\n",[260,1317,1318,1320],{"class":198,"line":337},[260,1319,1134],{"class":321},[260,1321,1322],{"class":269},".iface.messageBar().pushMessage(\n",[260,1324,1325,1328,1330,1332],{"class":198,"line":342},[260,1326,1327],{"class":321},"                self",[260,1329,499],{"class":269},[260,1331,939],{"class":328},[260,1333,1334],{"class":269},"),\n",[260,1336,1337,1339,1341,1344],{"class":198,"line":361},[260,1338,1327],{"class":321},[260,1340,499],{"class":269},[260,1342,1343],{"class":328},"\"Output folder does not exist; keeping the previous value.\"",[260,1345,1334],{"class":269},[260,1347,1348,1351,1353],{"class":198,"line":381},[260,1349,1350],{"class":660},"                level",[260,1352,373],{"class":265},[260,1354,1355],{"class":269},"Qgis.Warning,\n",[260,1357,1358,1361,1363,1365],{"class":198,"line":396},[260,1359,1360],{"class":660},"                duration",[260,1362,373],{"class":265},[260,1364,139],{"class":321},[260,1366,1367],{"class":269},",\n",[260,1369,1370],{"class":198,"line":401},[260,1371,1372],{"class":269},"            )\n",[260,1374,1375],{"class":198,"line":415},[260,1376,1377],{"class":265},"            return\n",[260,1379,1380],{"class":198,"line":428},[260,1381,315],{"emptyLinePlaceholder":314},[260,1383,1384,1386,1388],{"class":198,"line":441},[260,1385,619],{"class":269},[260,1387,373],{"class":265},[260,1389,624],{"class":269},[260,1391,1392,1394,1396,1398,1400,1402],{"class":198,"line":461},[260,1393,775],{"class":269},[260,1395,641],{"class":265},[260,1397,644],{"class":328},[260,1399,647],{"class":321},[260,1401,650],{"class":328},[260,1403,1404],{"class":269},", folder)\n",[14,1406,1407,1409,1410,1412,1413,1416],{},[218,1408,835],{}," Refusing to store an invalid value and telling the user why is better than either storing nonsense or throwing an exception out of ",[226,1411,842],{},", which QGIS surfaces as an unhelpful stack trace. The message bar is the right channel because the dialog is closing as this runs — a modal warning at this moment fights with the dialog's own dismissal. Better still, prevent the problem at input: a ",[226,1414,1415],{},"QgsFileWidget"," in directory mode gives a browse button and only produces real paths, which removes most of this validation entirely.",[14,1418,1419],{},[29,1420,1423,1426,1429,1431,1434,1439,1444,1448,1452,1456,1460,1463,1467,1469,1473,1475,1479,1481,1485,1487,1490,1492,1495,1498,1502,1504,1507,1509],{"viewBox":1421,"role":32,"ariaLabel":1422,"xmlns":34},"0 0 760 244","Comparison of a bespoke settings dialog buried in a plugin menu against a page in the QGIS options dialog, across discoverability, styling and search",[36,1424,1425],{},"Two places to put the same five settings",[40,1427,1428],{},"A bespoke dialog reached through the plugin menu has to be found, looks different from the rest of QGIS, and is invisible to the options search. A page registered in the QGIS options dialog sits where users already look for preferences, inherits the application styling, and is indexed by the search box.",[44,1430],{"x":46,"y":46,"width":47,"height":138,"fill":49},[68,1432,1433],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Same settings, very different odds of being found",[44,1435],{"x":1436,"y":79,"width":152,"height":1437,"rx":139,"fill":1438,"stroke":84,"style":1199},"16","30","#efeadd",[68,1440,1443],{"x":1441,"y":1442,"style":90,"fill":73,"textAnchor":74},"131","68","question",[44,1445],{"x":1446,"y":79,"width":88,"height":1437,"rx":139,"fill":1219,"stroke":1447,"style":1199},"254","#b45309",[68,1449,1451],{"x":1450,"y":1442,"style":90,"fill":1447,"textAnchor":74},"374","a dialog in your menu",[44,1453],{"x":1454,"y":79,"width":1455,"height":1437,"rx":139,"fill":172,"stroke":173,"style":1199},"502","242",[68,1457,1459],{"x":1458,"y":1442,"style":90,"fill":173,"textAnchor":74},"623","a page in Options",[44,1461],{"x":1436,"y":1462,"width":152,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},"84",[68,1464,1466],{"x":1437,"y":1465,"style":103,"fill":66},"107","where users look first",[44,1468],{"x":1446,"y":1462,"width":88,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1470,1472],{"x":1471,"y":1465,"style":103,"fill":66},"268","not there",[44,1474],{"x":1454,"y":1462,"width":1455,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1476,1478],{"x":1477,"y":1465,"style":103,"fill":66},"516","exactly there",[44,1480],{"x":1436,"y":148,"width":152,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1482,1484],{"x":1437,"y":1483,"style":103,"fill":66},"149","looks like QGIS",[44,1486],{"x":1446,"y":148,"width":88,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1488,1489],{"x":1471,"y":1483,"style":103,"fill":66},"only if you copy the style",[44,1491],{"x":1454,"y":148,"width":1455,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1493,1494],{"x":1477,"y":1483,"style":103,"fill":66},"inherited for free",[44,1496],{"x":1436,"y":1497,"width":152,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},"168",[68,1499,1501],{"x":1437,"y":1500,"style":103,"fill":66},"191","found by the search box",[44,1503],{"x":1446,"y":1497,"width":88,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1505,1506],{"x":1471,"y":1500,"style":103,"fill":66},"never",[44,1508],{"x":1454,"y":1497,"width":1455,"height":94,"rx":139,"fill":83,"stroke":84,"style":98},[68,1510,1511],{"x":1477,"y":1500,"style":103,"fill":66},"yes, by label text",[207,1513,1515],{"id":1514},"design-the-page-like-the-rest-of-qgis","Design the page like the rest of QGIS",[14,1517,1518],{},"A page that lives inside the application's own settings dialog is held to the application's conventions, and a handful of habits keep it from looking like a visitor.",[14,1520,1521,1524],{},[218,1522,1523],{},"Group by concern, not by data type."," Three group boxes labelled \"Output\", \"Processing defaults\" and \"Connections\" are far easier to scan than twelve rows in one list. QGIS's own pages do this consistently, and a plugin that follows suit reads as part of the software rather than bolted onto it.",[14,1526,1527,1530],{},[218,1528,1529],{},"Say what the setting does, not what it is called internally."," \"Add result to map after running\" beats \"auto_add\"; the key name belongs in the code and nowhere else. Where a setting has a non-obvious consequence, a short description under the control is better than a tooltip nobody hovers over.",[14,1532,1533,1536],{},[218,1534,1535],{},"Give every control a sensible default and show it."," A spin box that starts at zero when the real default is twenty-five metres teaches the user the wrong thing. Load from settings with the same default the rest of the plugin uses, so the page always shows what will actually happen.",[14,1538,1539,1542],{},[218,1540,1541],{},"Do not put actions on a settings page."," Buttons that run something, clear a cache or test a connection are borderline; a test-connection button is genuinely useful and widely accepted, but anything that modifies data belongs in the plugin's own interface. Users reasonably expect that pressing Cancel undoes everything they did in this dialog, and an action button breaks that expectation.",[14,1544,1545,1548],{},[218,1546,1547],{},"Keep it short."," If the page needs a scroll bar, some of those settings are probably decisions your plugin could make for itself. Every option is a question asked of every user forever, so the bar for adding one should be higher than \"somebody might want this\".",[207,1550,1552],{"id":1551},"qgis-version-compatibility","QGIS version compatibility",[1554,1555,1556,1572],"table",{},[1557,1558,1559],"thead",{},[1560,1561,1562,1566,1569],"tr",{},[1563,1564,1565],"th",{},"QGIS version",[1563,1567,1568],{},"Python",[1563,1570,1571],{},"Notes",[1573,1574,1575,1591,1601,1612],"tbody",{},[1560,1576,1577,1581,1584],{},[1578,1579,1580],"td",{},"3.22 LTR",[1578,1582,1583],{},"3.9",[1578,1585,1586,452,1588,1590],{},[226,1587,907],{},[226,1589,355],{}," and the register calls as described.",[1560,1592,1593,1596,1598],{},[1578,1594,1595],{},"3.28 LTR",[1578,1597,1583],{},[1578,1599,1600],{},"Identical.",[1560,1602,1603,1606,1609],{},[1578,1604,1605],{},"3.34 LTR",[1578,1607,1608],{},"3.12",[1578,1610,1611],{},"Baseline for this page.",[1560,1613,1614,1617,1619],{},[1578,1615,1616],{},"3.40 \u002F 3.44",[1578,1618,1608],{},[1578,1620,1621],{},"Identical; the options dialog gained a search box that indexes your page's labels, so clear label text now has an extra payoff.",[14,1623,1624,1627],{},[226,1625,1626],{},"setKey()"," on the factory sets a stable identifier used for deep-linking to a page, and is worth setting on newer releases if you want a menu action that opens your page directly.",[207,1629,1631],{"id":1630},"troubleshooting","Troubleshooting",[212,1633,1634,1647,1656,1668,1676,1685],{},[215,1635,1636,1639,1640,1643,1644,1646],{},[218,1637,1638],{},"The page does not appear."," The factory was not kept as an attribute and has been garbage-collected, or ",[226,1641,1642],{},"registerOptionsWidgetFactory"," was never reached because ",[226,1645,228],{}," raised earlier.",[215,1648,1649,1652,1653,1655],{},[218,1650,1651],{},"QGIS crashes when the options dialog opens."," A stale factory from a previous load. Unregister in ",[226,1654,232],{}," and restart once to clear the existing ones.",[215,1657,1658,836,1661,1663,1664,1667],{},[218,1659,1660],{},"Values are not saved.",[226,1662,842],{}," is not being reached, usually because the method name is misspelled — it must be exactly ",[226,1665,1666],{},"apply",", with no arguments.",[215,1669,1670,836,1673,1675],{},[218,1671,1672],{},"Old values reappear after cancel.",[226,1674,1001],{}," returned a cached widget instead of a new one.",[215,1677,1678,1681,1682,1684],{},[218,1679,1680],{},"Labels are clipped in another language."," A fixed-width layout. Use ",[226,1683,846],{}," or a grid and let Qt size the fields.",[215,1686,1687,1690],{},[218,1688,1689],{},"Two pages with the same title."," Two registrations, one per reload. See the crash case above.",[207,1692,1694],{"id":1693},"conclusion","Conclusion",[14,1696,1697,1698,1700,1701,1703,1704,1706,1707,1709],{},"Subclass ",[226,1699,355],{},", load settings in the constructor and write them in ",[226,1702,842],{},", publish it with a ",[226,1705,907],{}," kept as an attribute on the plugin, and unregister in ",[226,1708,232],{},". The result is preferences where users expect them, styled like the rest of QGIS, searchable, and translatable — for roughly the same amount of code as the bespoke dialog nobody finds.",[207,1711,1713],{"id":1712},"frequently-asked-questions","Frequently Asked Questions",[14,1715,1716,1719,1720,1723],{},[218,1717,1718],{},"Can I open my options page from a toolbar button?","\nYes — call ",[226,1721,1722],{},"iface.showOptionsDialog()"," with the page name, or set a key on the factory and use it as the current page on newer releases.",[14,1725,1726,1729],{},[218,1727,1728],{},"Where should the page's help text go?","\nA short description at the top of the widget, and a link to your documentation. QGIS does not provide a standard help slot for plugin pages.",[14,1731,1732,1735],{},[218,1733,1734],{},"Can I add settings for a Processing provider this way?","\nProcessing providers have their own settings mechanism inside the Processing options page. Use that for algorithm-level configuration and this page for the plugin's own.",[14,1737,1738,1741,1742,1744],{},[218,1739,1740],{},"Does the page need to handle Cancel?","\nNo. Nothing is written until ",[226,1743,842],{}," is called, so cancelling simply discards the widget.",[14,1746,1747,1750,1751,1754,1755,1758],{},[218,1748,1749],{},"How do I group many settings on one page?","\nUse ",[226,1752,1753],{},"QGroupBox"," sections inside the layout, or a ",[226,1756,1757],{},"QTabWidget"," for genuinely separate concerns. Two pages registered by one plugin is also acceptable when the concerns are unrelated.",[207,1760,1762],{"id":1761},"related","Related",[212,1764,1765,1770,1774,1780,1786],{},[215,1766,1767,1769],{},[21,1768,24],{"href":23}," — the guide this recipe belongs to",[215,1771,1772],{},[21,1773,245],{"href":244},[215,1775,1776],{},[21,1777,1779],{"href":1778},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","Add a Custom Dock Widget in PyQGIS",[215,1781,1782],{},[21,1783,1785],{"href":1784},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Freload-qgis-plugin-without-restart\u002F","Reload a QGIS Plugin Without Restarting",[215,1787,1788],{},[21,1789,1791],{"href":1790},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002F","Qt Designer for GIS Interfaces",[1793,1794,1795],"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 .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 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);}",{"title":256,"searchDepth":279,"depth":279,"links":1797},[1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808],{"id":209,"depth":279,"text":210},{"id":248,"depth":279,"text":249},{"id":858,"depth":279,"text":859},{"id":1005,"depth":279,"text":1006},{"id":1243,"depth":279,"text":1244},{"id":1514,"depth":279,"text":1515},{"id":1551,"depth":279,"text":1552},{"id":1630,"depth":279,"text":1631},{"id":1693,"depth":279,"text":1694},{"id":1712,"depth":279,"text":1713},{"id":1761,"depth":279,"text":1762},"Put your plugin's preferences where users look for them — build a QgsOptionsPageWidget, register the factory in initGui, apply values on OK, and unregister cleanly so a reload never leaves a dead page behind.","md",{"slug":1812,"type":1813,"breadcrumb":1814,"datePublished":1815,"dateModified":1815},"add-plugin-options-page-to-qgis-settings","article","Options Page","2026-08-15","\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings",{"title":5,"description":1809},"qgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings\u002Findex","0rmnbKIF_xUojPfhW9QO9Xn4kMI0kKDzCcD7iBtZPEk",1786789584641]