[{"data":1,"prerenderedAt":1569},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fstore-plugin-settings-with-qgssettings":3},{"id":4,"title":5,"body":6,"description":1559,"extension":1560,"meta":1561,"navigation":217,"path":1565,"seo":1566,"stem":1567,"__hash__":1568},"docs\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fstore-plugin-settings-with-qgssettings\u002Findex.md","Store Plugin Settings with QgsSettings",{"type":7,"value":8,"toc":1545},"minimark",[9,13,25,34,152,157,180,184,483,500,608,611,654,663,667,761,784,788,791,968,980,1079,1083,1086,1210,1219,1223,1262,1274,1278,1281,1284,1287,1298,1301,1305,1379,1388,1392,1451,1455,1464,1468,1474,1483,1494,1500,1506,1510,1541],[10,11,5],"h1",{"id":12},"store-plugin-settings-with-qgssettings",[14,15,16,20,21,24],"p",{},[17,18,19],"code",{},"QgsSettings"," looks like a dictionary that survives restarts, and treating it as one works right up until the day a user reports that your checkbox will not stay off. The cause is almost always the same: values come back from the settings backend as strings on some platforms, the string ",[17,22,23],{},"\"false\""," is truthy in Python, and the bug never reproduces on the machine that wrote the value.",[14,26,27,28,33],{},"This recipe belongs to ",[29,30,32],"a",{"href":31},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002F","Plugin Settings and Localization",". It covers the key conventions that keep your plugin out of everybody else's namespace, typed reads with defaults, groups, storing structured values, reading QGIS's own configuration, and removing your keys cleanly.",[14,35,36],{},[37,38,43,47,51,58,67,77,83,93,100,106,110,113,117,122,127,131,134,138,140,143,145,149],"svg",{"viewBox":39,"role":40,"ariaLabel":41,"xmlns":42},"0 0 760 264","img","Diagram of the settings key namespace showing QGIS's own keys alongside plugin keys, with a well namespaced plugin group contrasted against an unnamespaced key that risks collision","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[44,45,46],"title",{},"One namespace, shared by everything",[48,49,50],"desc",{},"The settings tree contains QGIS's own groups such as qgis and app, alongside groups created by plugins. A plugin that writes keys under its own folder name is safely isolated. A plugin that writes a top level key called output folder shares that name with any other plugin that does the same, and the last write wins.",[52,53],"rect",{"x":54,"y":54,"width":55,"height":56,"fill":57},"0","760","264","#f6f3ea",[59,60,66],"text",{"x":61,"y":62,"style":63,"fill":64,"textAnchor":65},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Your keys share a tree with QGIS and every other plugin",[52,68],{"x":69,"y":70,"width":71,"height":72,"rx":73,"fill":74,"stroke":75,"style":76},"24","48","340","196","10","#edf8e9","#15803d","stroke-width:2.5",[59,78,82],{"x":79,"y":80,"style":81,"fill":75,"textAnchor":65},"194","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","namespaced — safe",[52,84],{"x":85,"y":86,"width":87,"height":88,"rx":89,"fill":90,"stroke":91,"style":92},"46","88","296","32","6","#fffdf7","#59645f","stroke-width:1.5",[59,94,99],{"x":95,"y":96,"style":97,"fill":98},"62","109","font-size:11px;font-family:sans-serif","#2f3b35","qgis\u002F... — the application's own",[52,101],{"x":85,"y":102,"width":87,"height":88,"rx":89,"fill":103,"stroke":104,"style":105},"126","#eef7f4","#0f766e","stroke-width:2",[59,107,109],{"x":95,"y":108,"style":97,"fill":98},"147","parcel tools\u002Foutput folder",[52,111],{"x":85,"y":112,"width":87,"height":88,"rx":89,"fill":103,"stroke":104,"style":105},"164",[59,114,116],{"x":95,"y":115,"style":97,"fill":98},"185","parcel tools\u002Fadd to map",[59,118,121],{"x":79,"y":119,"style":120,"fill":75,"textAnchor":65},"222","text-anchor:middle;font-size:11px;font-family:sans-serif","one group, removable in one call",[52,123],{"x":124,"y":70,"width":71,"height":72,"rx":73,"fill":125,"stroke":126,"style":76},"396","#fdf2e2","#b91c1c",[59,128,130],{"x":129,"y":80,"style":81,"fill":126,"textAnchor":65},"566","unnamespaced — collision",[52,132],{"x":133,"y":86,"width":87,"height":88,"rx":89,"fill":90,"stroke":91,"style":92},"418",[59,135,137],{"x":136,"y":96,"style":97,"fill":98},"434","output folder — written by you",[52,139],{"x":133,"y":102,"width":87,"height":88,"rx":89,"fill":90,"stroke":126,"style":105},[59,141,142],{"x":136,"y":108,"style":97,"fill":98},"output folder — overwritten by another plugin",[52,144],{"x":133,"y":112,"width":87,"height":88,"rx":89,"fill":57,"stroke":126,"style":105},[59,146,148],{"x":136,"y":115,"style":147,"fill":126},"font-size:11px;font-weight:bold;font-family:sans-serif","whichever wrote last wins",[59,150,151],{"x":129,"y":119,"style":120,"fill":126,"textAnchor":65},"and neither plugin can tell",[153,154,156],"h2",{"id":155},"prerequisites","Prerequisites",[158,159,160,168,174],"ul",{},[161,162,163,167],"li",{},[164,165,166],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[161,169,170,171,173],{},"A plugin, or the Python console — ",[17,172,19],{}," works anywhere.",[161,175,176,177,179],{},"A settled decision about what belongs in settings at all; the scope question is in ",[29,178,32],{"href":31},".",[153,181,183],{"id":182},"write-and-read-with-types","Write and read with types",[185,186,191],"pre",{"className":187,"code":188,"language":189,"meta":190,"style":190},"language-python shiki shiki-themes github-dark","from qgis.core import QgsSettings\n\nPREFIX = \"parcel_tools\"\n\nsettings = QgsSettings()\nsettings.setValue(f\"{PREFIX}\u002Foutput_folder\", \"\u002Fdata\u002Fexports\")\nsettings.setValue(f\"{PREFIX}\u002Fadd_to_map\", True)\nsettings.setValue(f\"{PREFIX}\u002Fbuffer_distance\", 25.0)\nsettings.setValue(f\"{PREFIX}\u002Fmax_features\", 5000)\n\nfolder = settings.value(f\"{PREFIX}\u002Foutput_folder\", \"\", type=str)\nadd_to_map = settings.value(f\"{PREFIX}\u002Fadd_to_map\", True, type=bool)\ndistance = settings.value(f\"{PREFIX}\u002Fbuffer_distance\", 10.0, type=float)\nlimit = settings.value(f\"{PREFIX}\u002Fmax_features\", 1000, type=int)\n","python","",[17,192,193,212,219,233,238,250,277,298,319,340,345,382,415,449],{"__ignoreMap":190},[194,195,198,202,206,209],"span",{"class":196,"line":197},"line",1,[194,199,201],{"class":200},"snl16","from",[194,203,205],{"class":204},"s95oV"," qgis.core ",[194,207,208],{"class":200},"import",[194,210,211],{"class":204}," QgsSettings\n",[194,213,215],{"class":196,"line":214},2,[194,216,218],{"emptyLinePlaceholder":217},true,"\n",[194,220,222,226,229],{"class":196,"line":221},3,[194,223,225],{"class":224},"sDLfK","PREFIX",[194,227,228],{"class":200}," =",[194,230,232],{"class":231},"sU2Wk"," \"parcel_tools\"\n",[194,234,236],{"class":196,"line":235},4,[194,237,218],{"emptyLinePlaceholder":217},[194,239,241,244,247],{"class":196,"line":240},5,[194,242,243],{"class":204},"settings ",[194,245,246],{"class":200},"=",[194,248,249],{"class":204}," QgsSettings()\n",[194,251,253,256,259,262,265,268,271,274],{"class":196,"line":252},6,[194,254,255],{"class":204},"settings.setValue(",[194,257,258],{"class":200},"f",[194,260,261],{"class":231},"\"",[194,263,264],{"class":224},"{PREFIX}",[194,266,267],{"class":231},"\u002Foutput_folder\"",[194,269,270],{"class":204},", ",[194,272,273],{"class":231},"\"\u002Fdata\u002Fexports\"",[194,275,276],{"class":204},")\n",[194,278,280,282,284,286,288,291,293,296],{"class":196,"line":279},7,[194,281,255],{"class":204},[194,283,258],{"class":200},[194,285,261],{"class":231},[194,287,264],{"class":224},[194,289,290],{"class":231},"\u002Fadd_to_map\"",[194,292,270],{"class":204},[194,294,295],{"class":224},"True",[194,297,276],{"class":204},[194,299,301,303,305,307,309,312,314,317],{"class":196,"line":300},8,[194,302,255],{"class":204},[194,304,258],{"class":200},[194,306,261],{"class":231},[194,308,264],{"class":224},[194,310,311],{"class":231},"\u002Fbuffer_distance\"",[194,313,270],{"class":204},[194,315,316],{"class":224},"25.0",[194,318,276],{"class":204},[194,320,322,324,326,328,330,333,335,338],{"class":196,"line":321},9,[194,323,255],{"class":204},[194,325,258],{"class":200},[194,327,261],{"class":231},[194,329,264],{"class":224},[194,331,332],{"class":231},"\u002Fmax_features\"",[194,334,270],{"class":204},[194,336,337],{"class":224},"5000",[194,339,276],{"class":204},[194,341,343],{"class":196,"line":342},10,[194,344,218],{"emptyLinePlaceholder":217},[194,346,348,351,353,356,358,360,362,364,366,369,371,375,377,380],{"class":196,"line":347},11,[194,349,350],{"class":204},"folder ",[194,352,246],{"class":200},[194,354,355],{"class":204}," settings.value(",[194,357,258],{"class":200},[194,359,261],{"class":231},[194,361,264],{"class":224},[194,363,267],{"class":231},[194,365,270],{"class":204},[194,367,368],{"class":231},"\"\"",[194,370,270],{"class":204},[194,372,374],{"class":373},"s9osk","type",[194,376,246],{"class":200},[194,378,379],{"class":224},"str",[194,381,276],{"class":204},[194,383,385,388,390,392,394,396,398,400,402,404,406,408,410,413],{"class":196,"line":384},12,[194,386,387],{"class":204},"add_to_map ",[194,389,246],{"class":200},[194,391,355],{"class":204},[194,393,258],{"class":200},[194,395,261],{"class":231},[194,397,264],{"class":224},[194,399,290],{"class":231},[194,401,270],{"class":204},[194,403,295],{"class":224},[194,405,270],{"class":204},[194,407,374],{"class":373},[194,409,246],{"class":200},[194,411,412],{"class":224},"bool",[194,414,276],{"class":204},[194,416,418,421,423,425,427,429,431,433,435,438,440,442,444,447],{"class":196,"line":417},13,[194,419,420],{"class":204},"distance ",[194,422,246],{"class":200},[194,424,355],{"class":204},[194,426,258],{"class":200},[194,428,261],{"class":231},[194,430,264],{"class":224},[194,432,311],{"class":231},[194,434,270],{"class":204},[194,436,437],{"class":224},"10.0",[194,439,270],{"class":204},[194,441,374],{"class":373},[194,443,246],{"class":200},[194,445,446],{"class":224},"float",[194,448,276],{"class":204},[194,450,452,455,457,459,461,463,465,467,469,472,474,476,478,481],{"class":196,"line":451},14,[194,453,454],{"class":204},"limit ",[194,456,246],{"class":200},[194,458,355],{"class":204},[194,460,258],{"class":200},[194,462,261],{"class":231},[194,464,264],{"class":224},[194,466,332],{"class":231},[194,468,270],{"class":204},[194,470,471],{"class":224},"1000",[194,473,270],{"class":204},[194,475,374],{"class":373},[194,477,246],{"class":200},[194,479,480],{"class":224},"int",[194,482,276],{"class":204},[14,484,485,488,489,492,493,495,496,499],{},[164,486,487],{},"Breakdown:"," A module-level prefix constant means the plugin's namespace is written once and never mistyped. Every read passes two things: a default that is used when the key is absent, and a ",[17,490,491],{},"type="," that forces the conversion. The type argument is the important one — without it, a boolean written on Windows and read back on Linux, or read after a settings file round-trip, can arrive as the string ",[17,494,23],{},", and ",[17,497,498],{},"if settings.value(key):"," is then always true. The failure is invisible in testing because the value in memory during the same session is still a real boolean.",[14,501,502],{},[37,503,506,509,512,515,531,534,539,545,549,552,556,559,565,570,574,577,581,585,591,596,599,604],{"viewBox":504,"role":40,"ariaLabel":505,"xmlns":42},"0 0 760 246","Diagram showing a boolean written to settings, read back as a string on a later session, and evaluating as true regardless of its value unless a type is given",[44,507,508],{},"The checkbox that will not stay off",[48,510,511],{},"A boolean false is written to settings and stored as text. On a later session it is read back as the string false, which Python treats as true because it is a non-empty string, so the checkbox appears switched on. Passing the type argument to the read converts it properly and the value survives the round trip.",[52,513],{"x":54,"y":54,"width":55,"height":514,"fill":57},"246",[516,517,518],"defs",{},[519,520,527],"marker",{"id":521,"viewBox":522,"refX":523,"refY":524,"markerWidth":525,"markerHeight":525,"orient":526},"boolArrow","0 0 10 10","8","5","7","auto-start-reverse",[528,529],"path",{"d":530,"fill":98},"M0 0 L10 5 L0 10 z",[59,532,533],{"x":61,"y":62,"style":63,"fill":64,"textAnchor":65},"A non-empty string is always true",[52,535],{"x":536,"y":537,"width":538,"height":537,"rx":523,"fill":103,"stroke":104,"style":105},"20","60","180",[59,540,544],{"x":541,"y":542,"style":543,"fill":104,"textAnchor":65},"110","86","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","setValue(key, False)",[59,546,548],{"x":541,"y":547,"style":120,"fill":98,"textAnchor":65},"106","a real boolean",[52,550],{"x":551,"y":537,"width":538,"height":537,"rx":523,"fill":90,"stroke":91,"style":105},"236",[59,553,555],{"x":554,"y":542,"style":543,"fill":64,"textAnchor":65},"326","stored as text",[59,557,558],{"x":554,"y":547,"style":120,"fill":98,"textAnchor":65},"false",[52,560],{"x":561,"y":562,"width":563,"height":564,"rx":523,"fill":125,"stroke":126,"style":76},"452","34","288","52",[59,566,569],{"x":567,"y":568,"style":543,"fill":126,"textAnchor":65},"596","56","value(key) — no type",[59,571,573],{"x":567,"y":572,"style":120,"fill":98,"textAnchor":65},"76","returns the string, which is truthy",[52,575],{"x":561,"y":576,"width":563,"height":564,"rx":523,"fill":74,"stroke":75,"style":76},"96",[59,578,580],{"x":567,"y":579,"style":543,"fill":75,"textAnchor":65},"118","value(key, False, type=bool)",[59,582,584],{"x":567,"y":583,"style":120,"fill":98,"textAnchor":65},"138","returns False, as written",[196,586],{"x1":587,"y1":588,"x2":589,"y2":588,"stroke":98,"style":590},"200","90","230","stroke-width:2;marker-end:url(#boolArrow)",[196,592],{"x1":593,"y1":594,"x2":595,"y2":95,"stroke":126,"style":590},"416","80","446",[196,597],{"x1":593,"y1":598,"x2":595,"y2":579,"stroke":75,"style":590},"100",[52,600],{"x":601,"y":602,"width":603,"height":85,"rx":523,"fill":90,"stroke":91,"style":92},"120","176","520",[59,605,607],{"x":61,"y":606,"style":120,"fill":98,"textAnchor":65},"204","Reported as \"the checkbox will not stay off\" — and never reproducible on your machine",[14,609,610],{},"Deciding whether a key exists at all is a separate question from its value:",[185,612,614],{"className":187,"code":613,"language":189,"meta":190,"style":190},"if not settings.contains(f\"{PREFIX}\u002Foutput_folder\"):\n    settings.setValue(f\"{PREFIX}\u002Foutput_folder\", default_export_folder())\n",[17,615,616,638],{"__ignoreMap":190},[194,617,618,621,624,627,629,631,633,635],{"class":196,"line":197},[194,619,620],{"class":200},"if",[194,622,623],{"class":200}," not",[194,625,626],{"class":204}," settings.contains(",[194,628,258],{"class":200},[194,630,261],{"class":231},[194,632,264],{"class":224},[194,634,267],{"class":231},[194,636,637],{"class":204},"):\n",[194,639,640,643,645,647,649,651],{"class":196,"line":214},[194,641,642],{"class":204},"    settings.setValue(",[194,644,258],{"class":200},[194,646,261],{"class":231},[194,648,264],{"class":224},[194,650,267],{"class":231},[194,652,653],{"class":204},", default_export_folder())\n",[14,655,656,658,659,662],{},[164,657,487],{}," ",[17,660,661],{},"contains()"," distinguishes \"never configured\" from \"deliberately set to empty\", which matters when an empty value is a legitimate choice. This is also the correct place to seed first-run defaults — doing it once on first run means the user can later clear a value without your code helpfully putting it back.",[153,664,666],{"id":665},"group-related-keys","Group related keys",[185,668,670],{"className":187,"code":669,"language":189,"meta":190,"style":190},"settings = QgsSettings()\nsettings.beginGroup(PREFIX)\ntry:\n    settings.setValue(\"output_folder\", \"\u002Fdata\u002Fexports\")\n    settings.setValue(\"add_to_map\", True)\n    stored_keys = settings.allKeys()\nfinally:\n    settings.endGroup()\n\nprint(stored_keys)      # ['add_to_map', 'output_folder']\n",[17,671,672,680,689,697,710,723,733,740,745,749],{"__ignoreMap":190},[194,673,674,676,678],{"class":196,"line":197},[194,675,243],{"class":204},[194,677,246],{"class":200},[194,679,249],{"class":204},[194,681,682,685,687],{"class":196,"line":214},[194,683,684],{"class":204},"settings.beginGroup(",[194,686,225],{"class":224},[194,688,276],{"class":204},[194,690,691,694],{"class":196,"line":221},[194,692,693],{"class":200},"try",[194,695,696],{"class":204},":\n",[194,698,699,701,704,706,708],{"class":196,"line":235},[194,700,642],{"class":204},[194,702,703],{"class":231},"\"output_folder\"",[194,705,270],{"class":204},[194,707,273],{"class":231},[194,709,276],{"class":204},[194,711,712,714,717,719,721],{"class":196,"line":240},[194,713,642],{"class":204},[194,715,716],{"class":231},"\"add_to_map\"",[194,718,270],{"class":204},[194,720,295],{"class":224},[194,722,276],{"class":204},[194,724,725,728,730],{"class":196,"line":252},[194,726,727],{"class":204},"    stored_keys ",[194,729,246],{"class":200},[194,731,732],{"class":204}," settings.allKeys()\n",[194,734,735,738],{"class":196,"line":279},[194,736,737],{"class":200},"finally",[194,739,696],{"class":204},[194,741,742],{"class":196,"line":300},[194,743,744],{"class":204},"    settings.endGroup()\n",[194,746,747],{"class":196,"line":321},[194,748,218],{"emptyLinePlaceholder":217},[194,750,751,754,757],{"class":196,"line":342},[194,752,753],{"class":224},"print",[194,755,756],{"class":204},"(stored_keys)      ",[194,758,760],{"class":759},"sjoCn","# ['add_to_map', 'output_folder']\n",[14,762,763,765,766,768,769,771,772,775,776,779,780,783],{},[164,764,487],{}," Inside a group, keys are relative, which keeps long paths readable and makes it obvious that everything in the block belongs together. The ",[17,767,693],{},"\u002F",[17,770,737],{}," matters more than it looks: an exception between ",[17,773,774],{},"beginGroup()"," and ",[17,777,778],{},"endGroup()"," leaves the settings object with a dangling group prefix, and every later read in that object silently addresses the wrong place. ",[17,781,782],{},"allKeys()"," inside the group lists what you have stored, which is genuinely useful for an options page that needs to enumerate saved profiles.",[153,785,787],{"id":786},"store-lists-and-structured-values","Store lists and structured values",[14,789,790],{},"Simple lists round-trip directly. Anything with structure is safer as JSON.",[185,792,794],{"className":187,"code":793,"language":189,"meta":190,"style":190},"import json\n\nsettings.setValue(f\"{PREFIX}\u002Frecent_folders\", [\"\u002Fdata\u002Fa\", \"\u002Fdata\u002Fb\"])\nrecent = settings.value(f\"{PREFIX}\u002Frecent_folders\", [], type=list)\n\nprofile = {\"crs\": \"EPSG:27700\", \"buffer\": 25.0, \"fields\": [\"ref\", \"area\"]}\nsettings.setValue(f\"{PREFIX}\u002Fprofile\", json.dumps(profile))\nloaded = json.loads(settings.value(f\"{PREFIX}\u002Fprofile\", \"{}\", type=str))\n",[17,795,796,803,807,834,863,867,914,930],{"__ignoreMap":190},[194,797,798,800],{"class":196,"line":197},[194,799,208],{"class":200},[194,801,802],{"class":204}," json\n",[194,804,805],{"class":196,"line":214},[194,806,218],{"emptyLinePlaceholder":217},[194,808,809,811,813,815,817,820,823,826,828,831],{"class":196,"line":221},[194,810,255],{"class":204},[194,812,258],{"class":200},[194,814,261],{"class":231},[194,816,264],{"class":224},[194,818,819],{"class":231},"\u002Frecent_folders\"",[194,821,822],{"class":204},", [",[194,824,825],{"class":231},"\"\u002Fdata\u002Fa\"",[194,827,270],{"class":204},[194,829,830],{"class":231},"\"\u002Fdata\u002Fb\"",[194,832,833],{"class":204},"])\n",[194,835,836,839,841,843,845,847,849,851,854,856,858,861],{"class":196,"line":235},[194,837,838],{"class":204},"recent ",[194,840,246],{"class":200},[194,842,355],{"class":204},[194,844,258],{"class":200},[194,846,261],{"class":231},[194,848,264],{"class":224},[194,850,819],{"class":231},[194,852,853],{"class":204},", [], ",[194,855,374],{"class":373},[194,857,246],{"class":200},[194,859,860],{"class":224},"list",[194,862,276],{"class":204},[194,864,865],{"class":196,"line":240},[194,866,218],{"emptyLinePlaceholder":217},[194,868,869,872,874,877,880,883,886,888,891,893,895,897,900,903,906,908,911],{"class":196,"line":252},[194,870,871],{"class":204},"profile ",[194,873,246],{"class":200},[194,875,876],{"class":204}," {",[194,878,879],{"class":231},"\"crs\"",[194,881,882],{"class":204},": ",[194,884,885],{"class":231},"\"EPSG:27700\"",[194,887,270],{"class":204},[194,889,890],{"class":231},"\"buffer\"",[194,892,882],{"class":204},[194,894,316],{"class":224},[194,896,270],{"class":204},[194,898,899],{"class":231},"\"fields\"",[194,901,902],{"class":204},": [",[194,904,905],{"class":231},"\"ref\"",[194,907,270],{"class":204},[194,909,910],{"class":231},"\"area\"",[194,912,913],{"class":204},"]}\n",[194,915,916,918,920,922,924,927],{"class":196,"line":279},[194,917,255],{"class":204},[194,919,258],{"class":200},[194,921,261],{"class":231},[194,923,264],{"class":224},[194,925,926],{"class":231},"\u002Fprofile\"",[194,928,929],{"class":204},", json.dumps(profile))\n",[194,931,932,935,937,940,942,944,946,948,950,952,955,957,959,961,963,965],{"class":196,"line":300},[194,933,934],{"class":204},"loaded ",[194,936,246],{"class":200},[194,938,939],{"class":204}," json.loads(settings.value(",[194,941,258],{"class":200},[194,943,261],{"class":231},[194,945,264],{"class":224},[194,947,926],{"class":231},[194,949,270],{"class":204},[194,951,261],{"class":231},[194,953,954],{"class":224},"{}",[194,956,261],{"class":231},[194,958,270],{"class":204},[194,960,374],{"class":373},[194,962,246],{"class":200},[194,964,379],{"class":224},[194,966,967],{"class":204},"))\n",[14,969,970,972,973,976,977,979],{},[164,971,487],{}," A list of strings survives the round trip on every platform; a list containing mixed types or nested structures does not, because the backends flatten differently. Serialising to JSON makes the storage format explicit, keeps types intact, and — usefully — makes the stored value human-readable if somebody inspects the settings file. Wrapping ",[17,974,975],{},"json.loads()"," in a ",[17,978,693],{}," is worth it in shipped code: a truncated write, or a value edited by hand, otherwise raises somewhere unhelpful during startup.",[14,981,982],{},[37,983,986,989,992,995,998,1001,1005,1008,1011,1015,1020,1024,1028,1031,1034,1037,1040,1044,1048,1051,1054,1057,1060,1063,1067,1070,1073,1076],{"viewBox":984,"role":40,"ariaLabel":985,"xmlns":42},"0 0 760 250","Decision guide for choosing a settings value type, covering plain scalars, lists of strings, structured data stored as JSON and values that should not be in settings at all",[44,987,988],{},"Choosing how to store a value",[48,990,991],{},"Scalars such as strings, numbers and booleans are stored directly with a typed read. Lists of strings are stored directly and read with a list type. Nested or mixed structures are serialised to JSON and stored as a string. Secrets and large data should not be in settings at all: secrets belong in the authentication database and large data on disk.",[52,993],{"x":54,"y":54,"width":55,"height":994,"fill":57},"250",[59,996,997],{"x":61,"y":62,"style":63,"fill":64,"textAnchor":65},"Four kinds of value, three of them belong here",[52,999],{"x":1000,"y":70,"width":602,"height":112,"rx":73,"fill":103,"stroke":104,"style":76},"16",[59,1002,1004],{"x":1003,"y":80,"style":543,"fill":104,"textAnchor":65},"104","scalar",[59,1006,1007],{"x":1003,"y":598,"style":120,"fill":98,"textAnchor":65},"str, int, float, bool",[59,1009,1010],{"x":1003,"y":102,"style":120,"fill":98,"textAnchor":65},"setValue directly",[59,1012,1014],{"x":1003,"y":1013,"style":120,"fill":98,"textAnchor":65},"152","read with type=",[59,1016,1019],{"x":1003,"y":1017,"style":1018,"fill":91,"textAnchor":65},"186","text-anchor:middle;font-size:10px;font-family:sans-serif","the common case",[52,1021],{"x":606,"y":70,"width":602,"height":112,"rx":73,"fill":1022,"stroke":1023,"style":76},"#eff3ff","#2563eb",[59,1025,1027],{"x":1026,"y":80,"style":543,"fill":1023,"textAnchor":65},"292","list of strings",[59,1029,1030],{"x":1026,"y":598,"style":120,"fill":98,"textAnchor":65},"recent folders",[59,1032,1033],{"x":1026,"y":102,"style":120,"fill":98,"textAnchor":65},"selected field names",[59,1035,1036],{"x":1026,"y":1013,"style":120,"fill":98,"textAnchor":65},"read with type=list",[59,1038,1039],{"x":1026,"y":1017,"style":1018,"fill":91,"textAnchor":65},"safe if all strings",[52,1041],{"x":1042,"y":70,"width":602,"height":112,"rx":73,"fill":125,"stroke":1043,"style":76},"392","#b45309",[59,1045,1047],{"x":1046,"y":80,"style":543,"fill":1043,"textAnchor":65},"480","structured",[59,1049,1050],{"x":1046,"y":598,"style":120,"fill":98,"textAnchor":65},"nested or mixed types",[59,1052,1053],{"x":1046,"y":102,"style":120,"fill":98,"textAnchor":65},"json.dumps to a string",[59,1055,1056],{"x":1046,"y":1013,"style":120,"fill":98,"textAnchor":65},"guard the load",[59,1058,1059],{"x":1046,"y":1017,"style":1018,"fill":91,"textAnchor":65},"explicit and portable",[52,1061],{"x":1062,"y":70,"width":112,"height":112,"rx":73,"fill":57,"stroke":126,"style":76},"580",[59,1064,1066],{"x":1065,"y":80,"style":543,"fill":126,"textAnchor":65},"662","not here",[59,1068,1069],{"x":1065,"y":598,"style":120,"fill":98,"textAnchor":65},"passwords and tokens",[59,1071,1072],{"x":1065,"y":102,"style":120,"fill":98,"textAnchor":65},"cached datasets",[59,1074,1075],{"x":1065,"y":1013,"style":120,"fill":98,"textAnchor":65},"anything large",[59,1077,1078],{"x":1065,"y":1017,"style":1018,"fill":126,"textAnchor":65},"auth database or disk",[153,1080,1082],{"id":1081},"read-qgiss-own-settings","Read QGIS's own settings",[14,1084,1085],{},"Your plugin is not the only thing that has been configured. Honouring QGIS's existing preferences saves the user answering the same question twice.",[185,1087,1089],{"className":187,"code":1088,"language":189,"meta":190,"style":190},"settings = QgsSettings()\n\ndefault_crs = settings.value(\"app\u002Fprojections\u002FdefaultProjectCrs\", \"EPSG:4326\", type=str)\nlocale = settings.value(\"locale\u002FuserLocale\", \"en\", type=str)\ntimeout_ms = settings.value(\"qgis\u002FnetworkAndProxy\u002FnetworkTimeout\", 60000, type=int)\nlast_project_dir = settings.value(\"UI\u002FlastProjectDir\", \"\", type=str)\n",[17,1090,1091,1099,1103,1130,1157,1184],{"__ignoreMap":190},[194,1092,1093,1095,1097],{"class":196,"line":197},[194,1094,243],{"class":204},[194,1096,246],{"class":200},[194,1098,249],{"class":204},[194,1100,1101],{"class":196,"line":214},[194,1102,218],{"emptyLinePlaceholder":217},[194,1104,1105,1108,1110,1112,1115,1117,1120,1122,1124,1126,1128],{"class":196,"line":221},[194,1106,1107],{"class":204},"default_crs ",[194,1109,246],{"class":200},[194,1111,355],{"class":204},[194,1113,1114],{"class":231},"\"app\u002Fprojections\u002FdefaultProjectCrs\"",[194,1116,270],{"class":204},[194,1118,1119],{"class":231},"\"EPSG:4326\"",[194,1121,270],{"class":204},[194,1123,374],{"class":373},[194,1125,246],{"class":200},[194,1127,379],{"class":224},[194,1129,276],{"class":204},[194,1131,1132,1135,1137,1139,1142,1144,1147,1149,1151,1153,1155],{"class":196,"line":235},[194,1133,1134],{"class":204},"locale ",[194,1136,246],{"class":200},[194,1138,355],{"class":204},[194,1140,1141],{"class":231},"\"locale\u002FuserLocale\"",[194,1143,270],{"class":204},[194,1145,1146],{"class":231},"\"en\"",[194,1148,270],{"class":204},[194,1150,374],{"class":373},[194,1152,246],{"class":200},[194,1154,379],{"class":224},[194,1156,276],{"class":204},[194,1158,1159,1162,1164,1166,1169,1171,1174,1176,1178,1180,1182],{"class":196,"line":240},[194,1160,1161],{"class":204},"timeout_ms ",[194,1163,246],{"class":200},[194,1165,355],{"class":204},[194,1167,1168],{"class":231},"\"qgis\u002FnetworkAndProxy\u002FnetworkTimeout\"",[194,1170,270],{"class":204},[194,1172,1173],{"class":224},"60000",[194,1175,270],{"class":204},[194,1177,374],{"class":373},[194,1179,246],{"class":200},[194,1181,480],{"class":224},[194,1183,276],{"class":204},[194,1185,1186,1189,1191,1193,1196,1198,1200,1202,1204,1206,1208],{"class":196,"line":252},[194,1187,1188],{"class":204},"last_project_dir ",[194,1190,246],{"class":200},[194,1192,355],{"class":204},[194,1194,1195],{"class":231},"\"UI\u002FlastProjectDir\"",[194,1197,270],{"class":204},[194,1199,368],{"class":231},[194,1201,270],{"class":204},[194,1203,374],{"class":373},[194,1205,246],{"class":200},[194,1207,379],{"class":224},[194,1209,276],{"class":204},[14,1211,1212,1214,1215,1218],{},[164,1213,487],{}," These are QGIS's own keys, readable by anybody. Using ",[17,1216,1217],{},"UI\u002FlastProjectDir"," as the starting folder for your file dialog is a small touch that makes a plugin feel native, because it opens where the user last worked rather than in their home directory. The network timeout is worth respecting in any plugin that fetches something. Read these keys, do not write them: changing another component's configuration behind the user's back is how plugins get uninstalled.",[153,1220,1222],{"id":1221},"clean-up-on-uninstall","Clean up on uninstall",[185,1224,1226],{"className":187,"code":1225,"language":189,"meta":190,"style":190},"def remove_all_settings():\n    settings = QgsSettings()\n    settings.remove(PREFIX)          # removes the whole subtree\n",[17,1227,1228,1240,1249],{"__ignoreMap":190},[194,1229,1230,1233,1237],{"class":196,"line":197},[194,1231,1232],{"class":200},"def",[194,1234,1236],{"class":1235},"svObZ"," remove_all_settings",[194,1238,1239],{"class":204},"():\n",[194,1241,1242,1245,1247],{"class":196,"line":214},[194,1243,1244],{"class":204},"    settings ",[194,1246,246],{"class":200},[194,1248,249],{"class":204},[194,1250,1251,1254,1256,1259],{"class":196,"line":221},[194,1252,1253],{"class":204},"    settings.remove(",[194,1255,225],{"class":224},[194,1257,1258],{"class":204},")          ",[194,1260,1261],{"class":759},"# removes the whole subtree\n",[14,1263,1264,658,1266,1269,1270,1273],{},[164,1265,487],{},[17,1267,1268],{},"remove()"," on a group deletes it and everything beneath it in one call — the payoff for having namespaced properly in the first place. QGIS does not call anything on your plugin when it is uninstalled, so this belongs on a visible \"reset settings\" action in your options page rather than in ",[17,1271,1272],{},"unload()",", which runs on every ordinary reload and would wipe preferences constantly. A reset action is also what you will ask a user to press when diagnosing a problem that turns out to be a stale value.",[153,1275,1277],{"id":1276},"decide-what-deserves-to-be-a-setting-at-all","Decide what deserves to be a setting at all",[14,1279,1280],{},"The technical side of storing values is easy; the judgement about which values to store is where plugins go wrong in both directions.",[14,1282,1283],{},"A value earns a setting when the right answer genuinely differs between users and does not change between runs — an organisation's server address, a preferred output format, whether results are added to the map automatically. Those are real preferences: asked once, answered once, never thought about again.",[14,1285,1286],{},"A value does not deserve a setting when your plugin could work it out. A default output folder can come from the project folder; a default coordinate system can come from the project's; a default field can come from the layer's first suitable one. Every option you add is a question put to every user of the plugin, forever, and a plugin with thirty settings is usually one that could not decide anything for itself.",[14,1288,1289,1290,1293,1294,1297],{},"Two intermediate cases are worth naming. ",[164,1291,1292],{},"Values that change per run"," — the input layer, the output name, the buffer distance for this particular job — belong in the dialog, with the last value remembered as a convenience rather than presented as a preference. ",[164,1295,1296],{},"Values that belong to the map"," — which layer is authoritative, what parameters produced the current output — belong in the project, so a colleague opening it gets them too.",[14,1299,1300],{},"The test that resolves nearly every case is to ask what should happen when the user opens a different project tomorrow on a different machine. A preference should follow the person. A project fact should follow the file. Anything that should follow neither was working state, and should not have been persisted at all.",[153,1302,1304],{"id":1303},"qgis-version-compatibility","QGIS version compatibility",[1306,1307,1308,1324],"table",{},[1309,1310,1311],"thead",{},[1312,1313,1314,1318,1321],"tr",{},[1315,1316,1317],"th",{},"QGIS version",[1315,1319,1320],{},"Python",[1315,1322,1323],{},"Notes",[1325,1326,1327,1344,1354,1365],"tbody",{},[1312,1328,1329,1333,1336],{},[1330,1331,1332],"td",{},"3.22 LTR",[1330,1334,1335],{},"3.9",[1330,1337,1338,1340,1341,1343],{},[17,1339,19],{}," with groups, typed reads and ",[17,1342,1268],{}," as described.",[1312,1345,1346,1349,1351],{},[1330,1347,1348],{},"3.28 LTR",[1330,1350,1335],{},[1330,1352,1353],{},"Identical.",[1312,1355,1356,1359,1362],{},[1330,1357,1358],{},"3.34 LTR",[1330,1360,1361],{},"3.12",[1330,1363,1364],{},"Baseline for this page.",[1312,1366,1367,1370,1372],{},[1330,1368,1369],{},"3.40 \u002F 3.44",[1330,1371,1361],{},[1330,1373,1374,1375,1378],{},"Adds the typed settings-entry classes (",[17,1376,1377],{},"QgsSettingsEntryString"," and friends), which declare a key, its type and default once and are worth adopting in new code.",[14,1380,1381,1384,1385,1387],{},[17,1382,1383],{},"QSettings"," from PyQt still works, but bypasses QGIS's defaults and its profile handling — always prefer ",[17,1386,19],{},", which is a drop-in replacement.",[153,1389,1391],{"id":1390},"troubleshooting","Troubleshooting",[158,1393,1394,1404,1416,1422,1428,1437],{},[161,1395,1396,1399,1400,1403],{},[164,1397,1398],{},"A boolean is always true."," The value came back as a string. Add ",[17,1401,1402],{},"type=bool"," to the read.",[161,1405,1406,1409,1410,1412,1413,1415],{},[164,1407,1408],{},"A setting resets every restart."," It was written to a ",[17,1411,19],{}," object inside a group that was never closed, or the plugin writes a default over it on startup. Use ",[17,1414,661],{}," before seeding defaults.",[161,1417,1418,1421],{},[164,1419,1420],{},"Values leak between plugins."," Keys are not namespaced. Prefix every key with your plugin's folder name.",[161,1423,1424,1427],{},[164,1425,1426],{},"A list comes back as a string."," It contained non-string items. Store it as JSON instead.",[161,1429,1430,1433,1434,179],{},[164,1431,1432],{},"Settings vanish after switching profiles."," They are per profile by design. That is also the easiest way to test a clean first run: launch with ",[17,1435,1436],{},"--profile test",[161,1438,1439,1442,1443,1446,1447,1450],{},[164,1440,1441],{},"Nothing persists in a headless script."," Settings are written on destruction or on ",[17,1444,1445],{},"sync()",". Call ",[17,1448,1449],{},"settings.sync()"," before a standalone script exits.",[153,1452,1454],{"id":1453},"conclusion","Conclusion",[14,1456,1457,1458,1460,1461,1463],{},"Namespace every key under your plugin's folder name, always read with both a default and a ",[17,1459,491],{},", group related keys and close the group in a ",[17,1462,737],{},", and serialise anything structured to JSON. Read QGIS's own settings to inherit sensible defaults, keep secrets in the authentication database instead, and offer a reset action that removes your whole subtree in one call.",[153,1465,1467],{"id":1466},"frequently-asked-questions","Frequently Asked Questions",[14,1469,1470,1473],{},[164,1471,1472],{},"Where are the values actually stored?","\nIn the platform's standard location — the registry on Windows, an INI file in the active user profile elsewhere. Treat it as opaque; the file layout is not an API.",[14,1475,1476,1479,1480,1482],{},[164,1477,1478],{},"Can I use QSettings instead?","\nIt works, but it misses QGIS's defaults and profile awareness. ",[17,1481,19],{}," has the same interface, so there is no reason to.",[14,1484,1485,1488,1489,1493],{},[164,1486,1487],{},"How do I store a password?","\nDo not. Create an authentication configuration and store its id, which is what ",[29,1490,1492],{"href":1491},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fconnect-to-postgis-database-pyqgis\u002F","Connect to a PostGIS Database in PyQGIS"," uses for exactly this reason.",[14,1495,1496,1499],{},[164,1497,1498],{},"Are settings shared between QGIS profiles?","\nNo. Each profile has its own settings, plugins and authentication database, which is what makes profiles useful for testing and for separating work contexts.",[14,1501,1502,1505],{},[164,1503,1504],{},"Should the plugin write settings on every change or on close?","\nWrite on change for anything the user would be annoyed to lose in a crash, and on close for high-frequency values such as window geometry.",[153,1507,1509],{"id":1508},"related","Related",[158,1511,1512,1517,1523,1529,1535],{},[161,1513,1514,1516],{},[29,1515,32],{"href":31}," — the guide this recipe belongs to",[161,1518,1519],{},[29,1520,1522],{"href":1521},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings\u002F","Add a Plugin Options Page to QGIS Settings",[161,1524,1525],{},[29,1526,1528],{"href":1527},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fremember-last-used-folder-in-plugin-pyqgis\u002F","Remember the Last Used Folder in a Plugin",[161,1530,1531],{},[29,1532,1534],{"href":1533},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate and Structure",[161,1536,1537],{},[29,1538,1540],{"href":1539},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002F","Working with QGIS Projects in PyQGIS",[1542,1543,1544],"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":190,"searchDepth":214,"depth":214,"links":1546},[1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558],{"id":155,"depth":214,"text":156},{"id":182,"depth":214,"text":183},{"id":665,"depth":214,"text":666},{"id":786,"depth":214,"text":787},{"id":1081,"depth":214,"text":1082},{"id":1221,"depth":214,"text":1222},{"id":1276,"depth":214,"text":1277},{"id":1303,"depth":214,"text":1304},{"id":1390,"depth":214,"text":1391},{"id":1453,"depth":214,"text":1454},{"id":1466,"depth":214,"text":1467},{"id":1508,"depth":214,"text":1509},"Persist plugin preferences correctly — namespaced keys, typed reads with defaults, groups, lists and JSON values, reading QGIS's own settings, and cleaning up when the plugin is removed.","md",{"slug":12,"type":1562,"breadcrumb":1563,"datePublished":1564,"dateModified":1564},"article","Store Settings","2026-08-15","\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fstore-plugin-settings-with-qgssettings",{"title":5,"description":1559},"qgis-plugin-development\u002Fplugin-settings-and-localization\u002Fstore-plugin-settings-with-qgssettings\u002Findex","MZF5SwC6U1bWC0s-bB7G1OJivtnEshWUCd3FW7Vw5Q4",1786789584641]