[{"data":1,"prerenderedAt":1602},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fremember-last-used-folder-in-plugin-pyqgis":3},{"id":4,"title":5,"body":6,"description":1591,"extension":1592,"meta":1593,"navigation":269,"path":1598,"seo":1599,"stem":1600,"__hash__":1601},"docs\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fremember-last-used-folder-in-plugin-pyqgis\u002Findex.md","Remember the Last Used Folder in a Plugin",{"type":7,"value":8,"toc":1577},"minimark",[9,13,17,26,184,189,211,215,460,479,483,486,648,665,669,672,881,886,995,999,1002,1090,1107,1195,1199,1202,1313,1333,1337,1340,1346,1352,1358,1361,1365,1435,1441,1445,1494,1498,1504,1508,1514,1520,1530,1536,1542,1546,1573],[10,11,5],"h1",{"id":12},"remember-the-last-used-folder-in-a-plugin",[14,15,16],"p",{},"The difference between a plugin people tolerate and one they like is often this small: the file dialog opens in the folder they were working in yesterday, not in their home directory. It costs about six lines. Skipping it costs every user several seconds and a small irritation on every single run, which is a surprisingly effective way to make an otherwise good tool feel unfinished.",[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 storing and restoring a last-used path, keeping a short recent list, choosing sensible fallbacks when the stored path no longer exists, and doing the same for window size and position.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,97,101,105,110,113,116,119,122,127,131,134,137,140,149,153,156,160,163,167,174,178],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 262","img","Comparison of a file dialog opening in the home directory every time against one that opens in the last used folder, with the fallback chain shown when the stored folder no longer exists","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Where the dialog opens, and what happens when the folder is gone",[40,41,42],"desc",{},"Without a stored path, every run starts in the user's home directory and they navigate the same five folders again. With a stored path, the dialog opens where they left off. When the stored folder no longer exists, a fallback chain tries the project folder, then the QGIS last used directory, then the home directory, rather than failing or opening somewhere arbitrary.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","262","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"lastFolderArrow","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","Six lines the user notices every day",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"20","48","216","106","10","#fdf2e2","#b45309","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"128","74","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","no stored path",[68,93,96],{"x":88,"y":94,"style":95,"fill":66,"textAnchor":74},"98","text-anchor:middle;font-size:11px;font-family:sans-serif","opens in home",[68,98,100],{"x":88,"y":99,"style":95,"fill":66,"textAnchor":74},"118","five clicks to the project",[68,102,104],{"x":88,"y":103,"style":95,"fill":66,"textAnchor":74},"140","every single run",[44,106],{"x":107,"y":79,"width":80,"height":81,"rx":82,"fill":108,"stroke":109,"style":85},"272","#edf8e9","#15803d",[68,111,112],{"x":70,"y":89,"style":90,"fill":109,"textAnchor":74},"stored path",[68,114,115],{"x":70,"y":94,"style":95,"fill":66,"textAnchor":74},"opens where they left off",[68,117,118],{"x":70,"y":99,"style":95,"fill":66,"textAnchor":74},"zero clicks",[68,120,121],{"x":70,"y":103,"style":95,"fill":66,"textAnchor":74},"the common case",[44,123],{"x":124,"y":79,"width":80,"height":81,"rx":82,"fill":125,"stroke":126,"style":85},"524","#eff3ff","#2563eb",[68,128,130],{"x":129,"y":89,"style":90,"fill":126,"textAnchor":74},"632","stored path is gone",[68,132,133],{"x":129,"y":94,"style":95,"fill":66,"textAnchor":74},"a share unmounted",[68,135,136],{"x":129,"y":99,"style":95,"fill":66,"textAnchor":74},"a folder renamed",[68,138,139],{"x":129,"y":103,"style":95,"fill":66,"textAnchor":74},"fall back, do not fail",[44,141],{"x":142,"y":143,"width":144,"height":145,"rx":58,"fill":146,"stroke":147,"style":148},"130","188","150","46","#fffdf7","#0f766e","stroke-width:2",[68,150,152],{"x":151,"y":80,"style":95,"fill":66,"textAnchor":74},"205","project folder",[44,154],{"x":155,"y":143,"width":144,"height":145,"rx":58,"fill":146,"stroke":147,"style":148},"304",[68,157,159],{"x":158,"y":80,"style":95,"fill":66,"textAnchor":74},"379","QGIS last directory",[44,161],{"x":162,"y":143,"width":144,"height":145,"rx":58,"fill":146,"stroke":147,"style":148},"478",[68,164,166],{"x":165,"y":80,"style":95,"fill":66,"textAnchor":74},"553","home directory",[168,169],"line",{"x1":170,"y1":171,"x2":172,"y2":171,"stroke":66,"style":173},"280","211","298","stroke-width:2;marker-end:url(#lastFolderArrow)",[168,175],{"x1":176,"y1":171,"x2":177,"y2":171,"stroke":66,"style":173},"454","472",[168,179],{"x1":129,"y1":180,"x2":181,"y2":182,"stroke":126,"style":183},"154","240","184","stroke-width:2;stroke-dasharray:5 4;marker-end:url(#lastFolderArrow)",[185,186,188],"h2",{"id":187},"prerequisites","Prerequisites",[190,191,192,200,203],"ul",{},[193,194,195,199],"li",{},[196,197,198],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[193,201,202],{},"A plugin with a file or folder dialog somewhere.",[193,204,205,206,210],{},"The settings conventions from ",[21,207,209],{"href":208},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fstore-plugin-settings-with-qgssettings\u002F","Store Plugin Settings with QgsSettings",".",[185,212,214],{"id":213},"store-and-restore-the-folder","Store and restore the folder",[216,217,222],"pre",{"className":218,"code":219,"language":220,"meta":221,"style":221},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.PyQt.QtWidgets import QFileDialog\nfrom qgis.core import QgsSettings\n\nPREFIX = \"parcel_tools\"\n\n\ndef choose_output_file(parent):\n    settings = QgsSettings()\n    start_dir = settings.value(f\"{PREFIX}\u002Flast_folder\", \"\", type=str)\n\n    path, _ = QFileDialog.getSaveFileName(\n        parent,\n        \"Save parcel summary\",\n        os.path.join(start_dir, \"parcel_summary.gpkg\"),\n        \"GeoPackage (*.gpkg)\",\n    )\n\n    if path:\n        settings.setValue(f\"{PREFIX}\u002Flast_folder\", os.path.dirname(path))\n    return path\n","python","",[223,224,225,237,251,264,271,285,290,295,308,320,363,368,379,385,394,406,414,420,425,434,451],"code",{"__ignoreMap":221},[226,227,229,233],"span",{"class":168,"line":228},1,[226,230,232],{"class":231},"snl16","import",[226,234,236],{"class":235},"s95oV"," os\n",[226,238,240,243,246,248],{"class":168,"line":239},2,[226,241,242],{"class":231},"from",[226,244,245],{"class":235}," qgis.PyQt.QtWidgets ",[226,247,232],{"class":231},[226,249,250],{"class":235}," QFileDialog\n",[226,252,254,256,259,261],{"class":168,"line":253},3,[226,255,242],{"class":231},[226,257,258],{"class":235}," qgis.core ",[226,260,232],{"class":231},[226,262,263],{"class":235}," QgsSettings\n",[226,265,267],{"class":168,"line":266},4,[226,268,270],{"emptyLinePlaceholder":269},true,"\n",[226,272,274,278,281],{"class":168,"line":273},5,[226,275,277],{"class":276},"sDLfK","PREFIX",[226,279,280],{"class":231}," =",[226,282,284],{"class":283},"sU2Wk"," \"parcel_tools\"\n",[226,286,288],{"class":168,"line":287},6,[226,289,270],{"emptyLinePlaceholder":269},[226,291,293],{"class":168,"line":292},7,[226,294,270],{"emptyLinePlaceholder":269},[226,296,298,301,305],{"class":168,"line":297},8,[226,299,300],{"class":231},"def",[226,302,304],{"class":303},"svObZ"," choose_output_file",[226,306,307],{"class":235},"(parent):\n",[226,309,311,314,317],{"class":168,"line":310},9,[226,312,313],{"class":235},"    settings ",[226,315,316],{"class":231},"=",[226,318,319],{"class":235}," QgsSettings()\n",[226,321,323,326,328,331,334,337,340,343,346,349,351,355,357,360],{"class":168,"line":322},10,[226,324,325],{"class":235},"    start_dir ",[226,327,316],{"class":231},[226,329,330],{"class":235}," settings.value(",[226,332,333],{"class":231},"f",[226,335,336],{"class":283},"\"",[226,338,339],{"class":276},"{PREFIX}",[226,341,342],{"class":283},"\u002Flast_folder\"",[226,344,345],{"class":235},", ",[226,347,348],{"class":283},"\"\"",[226,350,345],{"class":235},[226,352,354],{"class":353},"s9osk","type",[226,356,316],{"class":231},[226,358,359],{"class":276},"str",[226,361,362],{"class":235},")\n",[226,364,366],{"class":168,"line":365},11,[226,367,270],{"emptyLinePlaceholder":269},[226,369,371,374,376],{"class":168,"line":370},12,[226,372,373],{"class":235},"    path, _ ",[226,375,316],{"class":231},[226,377,378],{"class":235}," QFileDialog.getSaveFileName(\n",[226,380,382],{"class":168,"line":381},13,[226,383,384],{"class":235},"        parent,\n",[226,386,388,391],{"class":168,"line":387},14,[226,389,390],{"class":283},"        \"Save parcel summary\"",[226,392,393],{"class":235},",\n",[226,395,397,400,403],{"class":168,"line":396},15,[226,398,399],{"class":235},"        os.path.join(start_dir, ",[226,401,402],{"class":283},"\"parcel_summary.gpkg\"",[226,404,405],{"class":235},"),\n",[226,407,409,412],{"class":168,"line":408},16,[226,410,411],{"class":283},"        \"GeoPackage (*.gpkg)\"",[226,413,393],{"class":235},[226,415,417],{"class":168,"line":416},17,[226,418,419],{"class":235},"    )\n",[226,421,423],{"class":168,"line":422},18,[226,424,270],{"emptyLinePlaceholder":269},[226,426,428,431],{"class":168,"line":427},19,[226,429,430],{"class":231},"    if",[226,432,433],{"class":235}," path:\n",[226,435,437,440,442,444,446,448],{"class":168,"line":436},20,[226,438,439],{"class":235},"        settings.setValue(",[226,441,333],{"class":231},[226,443,336],{"class":283},[226,445,339],{"class":276},[226,447,342],{"class":283},[226,449,450],{"class":235},", os.path.dirname(path))\n",[226,452,454,457],{"class":168,"line":453},21,[226,455,456],{"class":231},"    return",[226,458,459],{"class":235}," path\n",[14,461,462,465,466,470,471,474,475,478],{},[196,463,464],{},"Breakdown:"," The stored value is the ",[467,468,469],"em",{},"folder",", not the file, because a suggested file name should usually be a sensible default rather than whatever the user last called something. Joining the folder with a default file name gives both at once: the dialog opens in the right place with a name already filled in. Writing the setting only when a path came back means a cancelled dialog leaves the previous value alone. Note that an empty ",[223,472,473],{},"start_dir"," is harmless — ",[223,476,477],{},"QFileDialog"," falls back to its own default — so no special case is needed for a first run.",[185,480,482],{"id":481},"fall-back-when-the-folder-is-gone","Fall back when the folder is gone",[14,484,485],{},"Stored paths go stale: network shares unmount, projects get archived, an external drive is not plugged in. A dialog that opens at a non-existent path behaves inconsistently across platforms, so check first.",[216,487,489],{"className":218,"code":488,"language":220,"meta":221,"style":221},"from qgis.core import QgsProject\n\n\ndef resolve_start_dir():\n    settings = QgsSettings()\n    candidates = [\n        settings.value(f\"{PREFIX}\u002Flast_folder\", \"\", type=str),\n        QgsProject.instance().homePath(),\n        settings.value(\"UI\u002FlastProjectDir\", \"\", type=str),\n        os.path.expanduser(\"~\"),\n    ]\n    for candidate in candidates:\n        if candidate and os.path.isdir(candidate):\n            return candidate\n    return \"\"\n",[223,490,491,502,506,510,520,528,538,565,570,591,601,606,620,633,641],{"__ignoreMap":221},[226,492,493,495,497,499],{"class":168,"line":228},[226,494,242],{"class":231},[226,496,258],{"class":235},[226,498,232],{"class":231},[226,500,501],{"class":235}," QgsProject\n",[226,503,504],{"class":168,"line":239},[226,505,270],{"emptyLinePlaceholder":269},[226,507,508],{"class":168,"line":253},[226,509,270],{"emptyLinePlaceholder":269},[226,511,512,514,517],{"class":168,"line":266},[226,513,300],{"class":231},[226,515,516],{"class":303}," resolve_start_dir",[226,518,519],{"class":235},"():\n",[226,521,522,524,526],{"class":168,"line":273},[226,523,313],{"class":235},[226,525,316],{"class":231},[226,527,319],{"class":235},[226,529,530,533,535],{"class":168,"line":287},[226,531,532],{"class":235},"    candidates ",[226,534,316],{"class":231},[226,536,537],{"class":235}," [\n",[226,539,540,543,545,547,549,551,553,555,557,559,561,563],{"class":168,"line":292},[226,541,542],{"class":235},"        settings.value(",[226,544,333],{"class":231},[226,546,336],{"class":283},[226,548,339],{"class":276},[226,550,342],{"class":283},[226,552,345],{"class":235},[226,554,348],{"class":283},[226,556,345],{"class":235},[226,558,354],{"class":353},[226,560,316],{"class":231},[226,562,359],{"class":276},[226,564,405],{"class":235},[226,566,567],{"class":168,"line":297},[226,568,569],{"class":235},"        QgsProject.instance().homePath(),\n",[226,571,572,574,577,579,581,583,585,587,589],{"class":168,"line":310},[226,573,542],{"class":235},[226,575,576],{"class":283},"\"UI\u002FlastProjectDir\"",[226,578,345],{"class":235},[226,580,348],{"class":283},[226,582,345],{"class":235},[226,584,354],{"class":353},[226,586,316],{"class":231},[226,588,359],{"class":276},[226,590,405],{"class":235},[226,592,593,596,599],{"class":168,"line":322},[226,594,595],{"class":235},"        os.path.expanduser(",[226,597,598],{"class":283},"\"~\"",[226,600,405],{"class":235},[226,602,603],{"class":168,"line":365},[226,604,605],{"class":235},"    ]\n",[226,607,608,611,614,617],{"class":168,"line":370},[226,609,610],{"class":231},"    for",[226,612,613],{"class":235}," candidate ",[226,615,616],{"class":231},"in",[226,618,619],{"class":235}," candidates:\n",[226,621,622,625,627,630],{"class":168,"line":381},[226,623,624],{"class":231},"        if",[226,626,613],{"class":235},[226,628,629],{"class":231},"and",[226,631,632],{"class":235}," os.path.isdir(candidate):\n",[226,634,635,638],{"class":168,"line":387},[226,636,637],{"class":231},"            return",[226,639,640],{"class":235}," candidate\n",[226,642,643,645],{"class":168,"line":396},[226,644,456],{"class":231},[226,646,647],{"class":283}," \"\"\n",[14,649,650,652,653,656,657,660,661,210],{},[196,651,464],{}," The chain runs from most specific to most general: what this plugin last used, then the folder of the open project, then wherever QGIS itself last opened something, then home. Each is checked with ",[223,654,655],{},"isdir()"," rather than assumed, which is what makes an unmounted share degrade into a mild inconvenience instead of an error dialog. Reading ",[223,658,659],{},"UI\u002FlastProjectDir"," — QGIS's own key — is the small touch that makes a plugin feel native, because it lands where the user was working even on a first run. The project home path is covered in ",[21,662,664],{"href":663},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002F","Working with QGIS Projects in PyQGIS",[185,666,668],{"id":667},"keep-a-short-recent-list","Keep a short recent list",[14,670,671],{},"For a plugin used across several projects, one remembered folder is not enough. A recent list of four or five, offered in a combo box, covers the way people actually work.",[216,673,675],{"className":218,"code":674,"language":220,"meta":221,"style":221},"MAX_RECENT = 5\n\n\ndef remember_folder(folder):\n    settings = QgsSettings()\n    recent = settings.value(f\"{PREFIX}\u002Frecent_folders\", [], type=list)\n\n    recent = [f for f in recent if f != folder]\n    recent.insert(0, folder)\n    settings.setValue(f\"{PREFIX}\u002Frecent_folders\", recent[:MAX_RECENT])\n\n\ndef recent_folders():\n    settings = QgsSettings()\n    stored = settings.value(f\"{PREFIX}\u002Frecent_folders\", [], type=list)\n    return [f for f in stored if os.path.isdir(f)]\n",[223,676,677,687,691,695,705,713,743,747,778,788,809,813,817,826,834,861],{"__ignoreMap":221},[226,678,679,682,684],{"class":168,"line":228},[226,680,681],{"class":276},"MAX_RECENT",[226,683,280],{"class":231},[226,685,686],{"class":276}," 5\n",[226,688,689],{"class":168,"line":239},[226,690,270],{"emptyLinePlaceholder":269},[226,692,693],{"class":168,"line":253},[226,694,270],{"emptyLinePlaceholder":269},[226,696,697,699,702],{"class":168,"line":266},[226,698,300],{"class":231},[226,700,701],{"class":303}," remember_folder",[226,703,704],{"class":235},"(folder):\n",[226,706,707,709,711],{"class":168,"line":273},[226,708,313],{"class":235},[226,710,316],{"class":231},[226,712,319],{"class":235},[226,714,715,718,720,722,724,726,728,731,734,736,738,741],{"class":168,"line":287},[226,716,717],{"class":235},"    recent ",[226,719,316],{"class":231},[226,721,330],{"class":235},[226,723,333],{"class":231},[226,725,336],{"class":283},[226,727,339],{"class":276},[226,729,730],{"class":283},"\u002Frecent_folders\"",[226,732,733],{"class":235},", [], ",[226,735,354],{"class":353},[226,737,316],{"class":231},[226,739,740],{"class":276},"list",[226,742,362],{"class":235},[226,744,745],{"class":168,"line":292},[226,746,270],{"emptyLinePlaceholder":269},[226,748,749,751,753,756,759,762,764,767,770,772,775],{"class":168,"line":297},[226,750,717],{"class":235},[226,752,316],{"class":231},[226,754,755],{"class":235}," [f ",[226,757,758],{"class":231},"for",[226,760,761],{"class":235}," f ",[226,763,616],{"class":231},[226,765,766],{"class":235}," recent ",[226,768,769],{"class":231},"if",[226,771,761],{"class":235},[226,773,774],{"class":231},"!=",[226,776,777],{"class":235}," folder]\n",[226,779,780,783,785],{"class":168,"line":310},[226,781,782],{"class":235},"    recent.insert(",[226,784,46],{"class":276},[226,786,787],{"class":235},", folder)\n",[226,789,790,793,795,797,799,801,804,806],{"class":168,"line":322},[226,791,792],{"class":235},"    settings.setValue(",[226,794,333],{"class":231},[226,796,336],{"class":283},[226,798,339],{"class":276},[226,800,730],{"class":283},[226,802,803],{"class":235},", recent[:",[226,805,681],{"class":276},[226,807,808],{"class":235},"])\n",[226,810,811],{"class":168,"line":365},[226,812,270],{"emptyLinePlaceholder":269},[226,814,815],{"class":168,"line":370},[226,816,270],{"emptyLinePlaceholder":269},[226,818,819,821,824],{"class":168,"line":381},[226,820,300],{"class":231},[226,822,823],{"class":303}," recent_folders",[226,825,519],{"class":235},[226,827,828,830,832],{"class":168,"line":387},[226,829,313],{"class":235},[226,831,316],{"class":231},[226,833,319],{"class":235},[226,835,836,839,841,843,845,847,849,851,853,855,857,859],{"class":168,"line":396},[226,837,838],{"class":235},"    stored ",[226,840,316],{"class":231},[226,842,330],{"class":235},[226,844,333],{"class":231},[226,846,336],{"class":283},[226,848,339],{"class":276},[226,850,730],{"class":283},[226,852,733],{"class":235},[226,854,354],{"class":353},[226,856,316],{"class":231},[226,858,740],{"class":276},[226,860,362],{"class":235},[226,862,863,865,867,869,871,873,876,878],{"class":168,"line":408},[226,864,456],{"class":231},[226,866,755],{"class":235},[226,868,758],{"class":231},[226,870,761],{"class":235},[226,872,616],{"class":231},[226,874,875],{"class":235}," stored ",[226,877,769],{"class":231},[226,879,880],{"class":235}," os.path.isdir(f)]\n",[14,882,883,885],{},[196,884,464],{}," Removing the folder before inserting it at the front keeps the list ordered by recency without duplicates, which is the behaviour every \"recent files\" menu has and users expect without thinking about it. Truncating on write rather than on read keeps the stored value bounded. Filtering on read rather than on write is deliberate: a folder on a share that is temporarily unmounted should disappear from today's list and come back tomorrow, not be permanently forgotten because it was unavailable once.",[14,887,888],{},[29,889,892,895,898,901,908,911,916,922,928,931,935,939,944,947,951,954,958,963,968,972,975,979,981,983,985,987,989,991,993],{"viewBox":890,"role":32,"ariaLabel":891,"xmlns":34},"0 0 760 246","Diagram of a recent folders list showing a newly used folder moving to the front, an existing duplicate being removed, and the oldest entry falling off the end of a five item list",[36,893,894],{},"How the recent list changes when a folder is used again",[40,896,897],{},"Before the update the list holds five folders in order of recency. The user works in a folder that is already third in the list. After the update that folder is first, the entries above it have shifted down, no duplicate exists, and the list is still five items long because nothing new was added.",[44,899],{"x":46,"y":46,"width":47,"height":900,"fill":49},"246",[51,902,903],{},[54,904,906],{"id":905,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"recentArrow",[63,907],{"d":65,"fill":147},[68,909,910],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Remove, then insert at the front",[68,912,915],{"x":144,"y":913,"style":90,"fill":914,"textAnchor":74},"60","#59645f","before",[44,917],{"x":918,"y":919,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},"40","72","220","stroke-width:1.5",[68,923,927],{"x":924,"y":925,"style":926,"fill":66},"54","91","font-size:11px;font-family:sans-serif","\u002Fdata\u002Friverside",[44,929],{"x":918,"y":930,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},"104",[68,932,934],{"x":924,"y":933,"style":926,"fill":66},"123","\u002Fdata\u002Fcoastal",[44,936],{"x":918,"y":937,"width":920,"height":71,"rx":59,"fill":938,"stroke":147,"style":85},"136","#eef7f4",[68,940,943],{"x":924,"y":941,"style":942,"fill":147},"155","font-size:11px;font-weight:bold;font-family:sans-serif","\u002Fdata\u002Fupland — used now",[44,945],{"x":918,"y":946,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},"168",[68,948,950],{"x":924,"y":949,"style":926,"fill":66},"187","\u002Fdata\u002Farchive",[44,952],{"x":918,"y":953,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},"200",[68,955,957],{"x":924,"y":956,"style":926,"fill":66},"219","\u002Fdata\u002Fscratch",[168,959],{"x1":170,"y1":144,"x2":960,"y2":961,"stroke":147,"style":962},"470","90","stroke-width:2;marker-end:url(#recentArrow)",[68,964,967],{"x":70,"y":965,"style":966,"fill":147,"textAnchor":74},"112","text-anchor:middle;font-size:10px;font-family:sans-serif","moves to the front",[68,969,971],{"x":970,"y":913,"style":90,"fill":914,"textAnchor":74},"610","after",[44,973],{"x":974,"y":919,"width":920,"height":71,"rx":59,"fill":938,"stroke":147,"style":85},"500",[68,976,978],{"x":977,"y":925,"style":942,"fill":147},"514","\u002Fdata\u002Fupland",[44,980],{"x":974,"y":930,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},[68,982,927],{"x":977,"y":933,"style":926,"fill":66},[44,984],{"x":974,"y":937,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},[68,986,934],{"x":977,"y":941,"style":926,"fill":66},[44,988],{"x":974,"y":946,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},[68,990,950],{"x":977,"y":949,"style":926,"fill":66},[44,992],{"x":974,"y":953,"width":920,"height":71,"rx":59,"fill":146,"stroke":914,"style":921},[68,994,957],{"x":977,"y":956,"style":926,"fill":66},[185,996,998],{"id":997},"let-qgsfilewidget-do-it-for-you","Let QgsFileWidget do it for you",[14,1000,1001],{},"For a path field inside a dialog, QGIS's own widget already handles the browse button, the storage mode and validation — and it accepts a settings key for the default root.",[216,1003,1005],{"className":218,"code":1004,"language":220,"meta":221,"style":221},"from qgis.gui import QgsFileWidget\n\nself.output_widget = QgsFileWidget()\nself.output_widget.setStorageMode(QgsFileWidget.SaveFile)\nself.output_widget.setFilter(\"GeoPackage (*.gpkg)\")\nself.output_widget.setDefaultRoot(resolve_start_dir())\nself.output_widget.fileChanged.connect(\n    lambda path: remember_folder(os.path.dirname(path)) if path else None)\n",[223,1006,1007,1019,1023,1036,1043,1055,1062,1069],{"__ignoreMap":221},[226,1008,1009,1011,1014,1016],{"class":168,"line":228},[226,1010,242],{"class":231},[226,1012,1013],{"class":235}," qgis.gui ",[226,1015,232],{"class":231},[226,1017,1018],{"class":235}," QgsFileWidget\n",[226,1020,1021],{"class":168,"line":239},[226,1022,270],{"emptyLinePlaceholder":269},[226,1024,1025,1028,1031,1033],{"class":168,"line":253},[226,1026,1027],{"class":276},"self",[226,1029,1030],{"class":235},".output_widget ",[226,1032,316],{"class":231},[226,1034,1035],{"class":235}," QgsFileWidget()\n",[226,1037,1038,1040],{"class":168,"line":266},[226,1039,1027],{"class":276},[226,1041,1042],{"class":235},".output_widget.setStorageMode(QgsFileWidget.SaveFile)\n",[226,1044,1045,1047,1050,1053],{"class":168,"line":273},[226,1046,1027],{"class":276},[226,1048,1049],{"class":235},".output_widget.setFilter(",[226,1051,1052],{"class":283},"\"GeoPackage (*.gpkg)\"",[226,1054,362],{"class":235},[226,1056,1057,1059],{"class":168,"line":287},[226,1058,1027],{"class":276},[226,1060,1061],{"class":235},".output_widget.setDefaultRoot(resolve_start_dir())\n",[226,1063,1064,1066],{"class":168,"line":292},[226,1065,1027],{"class":276},[226,1067,1068],{"class":235},".output_widget.fileChanged.connect(\n",[226,1070,1071,1074,1077,1079,1082,1085,1088],{"class":168,"line":297},[226,1072,1073],{"class":231},"    lambda",[226,1075,1076],{"class":235}," path: remember_folder(os.path.dirname(path)) ",[226,1078,769],{"class":231},[226,1080,1081],{"class":235}," path ",[226,1083,1084],{"class":231},"else",[226,1086,1087],{"class":276}," None",[226,1089,362],{"class":235},[14,1091,1092,1094,1095,1098,1099,1102,1103,1106],{},[196,1093,464],{}," ",[223,1096,1097],{},"QgsFileWidget"," gives a line edit, a browse button and consistent behaviour with the rest of QGIS for four lines, and its storage modes cover files, existing files, directories and multiple files. ",[223,1100,1101],{},"setDefaultRoot()"," is where the resolved start directory goes. Connecting ",[223,1104,1105],{},"fileChanged"," means the recent list updates as soon as a path is chosen rather than only when the dialog is accepted, which is usually what you want — the user has expressed the intent by then. Using QGIS's widget also means a translated interface gets a translated browse button without any work on your part.",[14,1108,1109],{},[29,1110,1113,1116,1119,1122,1125,1130,1135,1138,1141,1144,1148,1151,1154,1157,1160,1163,1166,1169,1172,1176,1180,1183,1186,1189,1192],{"viewBox":1111,"role":32,"ariaLabel":1112,"xmlns":34},"0 0 760 244","Diagram classifying remembered values into those safe to restore silently, those to restore but show clearly, and those that should never be reapplied without confirmation",[36,1114,1115],{},"Which remembered values are safe to reapply",[40,1117,1118],{},"Convenience values such as the output folder, window size and a checkbox state can be restored silently because a wrong guess costs a click. Context values such as the last selected layer should be restored only when they still exist, and shown plainly. Values that change what happens to data, such as a destination table or an overwrite flag, must never be reapplied without the user seeing them.",[44,1120],{"x":46,"y":46,"width":47,"height":1121,"fill":49},"244",[68,1123,1124],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Memory should remove decisions, not make them for you",[44,1126],{"x":1127,"y":1128,"width":1129,"height":946,"rx":82,"fill":108,"stroke":109,"style":85},"16","52","232",[68,1131,1134],{"x":1132,"y":1133,"style":90,"fill":109,"textAnchor":74},"132","78","restore silently",[68,1136,1137],{"x":1132,"y":81,"style":95,"fill":66,"textAnchor":74},"output folder",[68,1139,1140],{"x":1132,"y":88,"style":95,"fill":66,"textAnchor":74},"window size and position",[68,1142,1143],{"x":1132,"y":144,"style":95,"fill":66,"textAnchor":74},"add result to map",[68,1145,1147],{"x":1132,"y":1146,"style":95,"fill":66,"textAnchor":74},"172","output format",[68,1149,1150],{"x":1132,"y":953,"style":966,"fill":109,"textAnchor":74},"a wrong guess costs one click",[44,1152],{"x":1153,"y":1128,"width":1129,"height":946,"rx":82,"fill":125,"stroke":126,"style":85},"264",[68,1155,1156],{"x":70,"y":1133,"style":90,"fill":126,"textAnchor":74},"restore, but show",[68,1158,1159],{"x":70,"y":81,"style":95,"fill":66,"textAnchor":74},"last selected layer",[68,1161,1162],{"x":70,"y":88,"style":95,"fill":66,"textAnchor":74},"last chosen field",[68,1164,1165],{"x":70,"y":144,"style":95,"fill":66,"textAnchor":74},"a saved profile name",[68,1167,1168],{"x":70,"y":1146,"style":95,"fill":66,"textAnchor":74},"only if it still exists",[68,1170,1171],{"x":70,"y":953,"style":966,"fill":126,"textAnchor":74},"fall back to nothing, not to anything",[44,1173],{"x":1174,"y":1128,"width":1129,"height":946,"rx":82,"fill":83,"stroke":1175,"style":85},"512","#b91c1c",[68,1177,1179],{"x":1178,"y":1133,"style":90,"fill":1175,"textAnchor":74},"628","never reapply quietly",[68,1181,1182],{"x":1178,"y":81,"style":95,"fill":66,"textAnchor":74},"destination table",[68,1184,1185],{"x":1178,"y":88,"style":95,"fill":66,"textAnchor":74},"overwrite flag",[68,1187,1188],{"x":1178,"y":144,"style":95,"fill":66,"textAnchor":74},"buffer distance",[68,1190,1191],{"x":1178,"y":1146,"style":95,"fill":66,"textAnchor":74},"anything that changes data",[68,1193,1194],{"x":1178,"y":953,"style":966,"fill":1175,"textAnchor":74},"show it, and let them confirm",[185,1196,1198],{"id":1197},"remember-dialog-size-too","Remember dialog size too",[14,1200,1201],{},"The same mechanism restores window geometry, which users notice on small screens and multi-monitor setups.",[216,1203,1205],{"className":218,"code":1204,"language":220,"meta":221,"style":221},"    def showEvent(self, event):\n        super().showEvent(event)\n        geometry = QgsSettings().value(f\"{PREFIX}\u002Fdialog_geometry\")\n        if geometry is not None:\n            self.restoreGeometry(geometry)\n\n    def closeEvent(self, event):\n        QgsSettings().setValue(f\"{PREFIX}\u002Fdialog_geometry\", self.saveGeometry())\n        super().closeEvent(event)\n",[223,1206,1207,1218,1226,1247,1265,1273,1277,1286,1306],{"__ignoreMap":221},[226,1208,1209,1212,1215],{"class":168,"line":228},[226,1210,1211],{"class":231},"    def",[226,1213,1214],{"class":303}," showEvent",[226,1216,1217],{"class":235},"(self, event):\n",[226,1219,1220,1223],{"class":168,"line":239},[226,1221,1222],{"class":276},"        super",[226,1224,1225],{"class":235},"().showEvent(event)\n",[226,1227,1228,1231,1233,1236,1238,1240,1242,1245],{"class":168,"line":253},[226,1229,1230],{"class":235},"        geometry ",[226,1232,316],{"class":231},[226,1234,1235],{"class":235}," QgsSettings().value(",[226,1237,333],{"class":231},[226,1239,336],{"class":283},[226,1241,339],{"class":276},[226,1243,1244],{"class":283},"\u002Fdialog_geometry\"",[226,1246,362],{"class":235},[226,1248,1249,1251,1254,1257,1260,1262],{"class":168,"line":266},[226,1250,624],{"class":231},[226,1252,1253],{"class":235}," geometry ",[226,1255,1256],{"class":231},"is",[226,1258,1259],{"class":231}," not",[226,1261,1087],{"class":276},[226,1263,1264],{"class":235},":\n",[226,1266,1267,1270],{"class":168,"line":273},[226,1268,1269],{"class":276},"            self",[226,1271,1272],{"class":235},".restoreGeometry(geometry)\n",[226,1274,1275],{"class":168,"line":287},[226,1276,270],{"emptyLinePlaceholder":269},[226,1278,1279,1281,1284],{"class":168,"line":292},[226,1280,1211],{"class":231},[226,1282,1283],{"class":303}," closeEvent",[226,1285,1217],{"class":235},[226,1287,1288,1291,1293,1295,1297,1299,1301,1303],{"class":168,"line":297},[226,1289,1290],{"class":235},"        QgsSettings().setValue(",[226,1292,333],{"class":231},[226,1294,336],{"class":283},[226,1296,339],{"class":276},[226,1298,1244],{"class":283},[226,1300,345],{"class":235},[226,1302,1027],{"class":276},[226,1304,1305],{"class":235},".saveGeometry())\n",[226,1307,1308,1310],{"class":168,"line":310},[226,1309,1222],{"class":276},[226,1311,1312],{"class":235},"().closeEvent(event)\n",[14,1314,1315,1094,1317,1320,1321,1324,1325,1328,1329,1332],{},[196,1316,464],{},[223,1318,1319],{},"saveGeometry()"," returns an opaque byte array holding size, position and screen — pass it straight back to ",[223,1322,1323],{},"restoreGeometry()"," and never try to interpret it. Restoring in ",[223,1326,1327],{},"showEvent"," rather than the constructor means it also applies when a reused dialog is shown again. One caveat worth knowing: a geometry saved on a second monitor can place the dialog off-screen when that monitor is gone; recent Qt versions handle this, but a defensive ",[223,1330,1331],{},"if not self.geometry().intersects(screen_rect)"," check is cheap insurance if your users dock and undock laptops all day.",[185,1334,1336],{"id":1335},"remember-more-than-the-folder","Remember more than the folder",[14,1338,1339],{},"Once the mechanism is in place, the same few lines cover everything else the user sets identically on every run. The judgement is about which values are safe to restore silently.",[14,1341,1342,1345],{},[196,1343,1344],{},"Safe to restore without comment:"," the output folder, the window size, an expanded or collapsed section, the last chosen output format, the state of a checkbox such as \"add result to map\". These change the convenience of the run, not its meaning, and a wrong guess costs a click.",[14,1347,1348,1351],{},[196,1349,1350],{},"Restore, but show clearly:"," the last selected layer or field. Restoring a layer name is helpful when the same project is open and confusing when it is not, so match on the layer still existing in the current project and fall back to no selection rather than to something arbitrary.",[14,1353,1354,1357],{},[196,1355,1356],{},"Do not restore silently:"," anything that changes what the operation does to data. A buffer distance, a destination table, an overwrite flag — restoring these means a user who ran a job with unusual settings last month gets them again today without noticing. Show the value in the dialog where it can be read, and never apply it to a run the user did not confirm.",[14,1359,1360],{},"The general shape is that memory should reduce the number of decisions, not make them on the user's behalf. A dialog that opens pre-filled with what you did last time, showing every value plainly, is helpful. One that quietly reuses a destructive setting is a bug waiting for a bad week.",[185,1362,1364],{"id":1363},"qgis-version-compatibility","QGIS version compatibility",[1366,1367,1368,1384],"table",{},[1369,1370,1371],"thead",{},[1372,1373,1374,1378,1381],"tr",{},[1375,1376,1377],"th",{},"QGIS version",[1375,1379,1380],{},"Python",[1375,1382,1383],{},"Notes",[1385,1386,1387,1404,1414,1425],"tbody",{},[1372,1388,1389,1393,1396],{},[1390,1391,1392],"td",{},"3.22 LTR",[1390,1394,1395],{},"3.9",[1390,1397,1398,345,1401,1403],{},[223,1399,1400],{},"QgsSettings",[223,1402,1097],{}," and geometry save and restore all present.",[1372,1405,1406,1409,1411],{},[1390,1407,1408],{},"3.28 LTR",[1390,1410,1395],{},[1390,1412,1413],{},"Identical.",[1372,1415,1416,1419,1422],{},[1390,1417,1418],{},"3.34 LTR",[1390,1420,1421],{},"3.12",[1390,1423,1424],{},"Baseline for this page.",[1372,1426,1427,1430,1432],{},[1390,1428,1429],{},"3.40 \u002F 3.44",[1390,1431,1421],{},[1390,1433,1434],{},"Identical; newer releases improve off-screen geometry handling in Qt.",[14,1436,1437,1438,1440],{},"Avoid ",[223,1439,477],{}," static methods with the native dialog disabled unless you have a reason — the native dialog is what users recognise, and it already remembers its own state per application on some platforms.",[185,1442,1444],{"id":1443},"troubleshooting","Troubleshooting",[190,1446,1447,1453,1459,1468,1478,1484],{},[193,1448,1449,1452],{},[196,1450,1451],{},"The dialog still opens in home."," The stored value is empty, or it was written after the dialog rather than after a successful selection. Print it once to confirm what is stored.",[193,1454,1455,1458],{},[196,1456,1457],{},"A stale path causes an error dialog."," No existence check. Use the fallback chain.",[193,1460,1461,1464,1465,1467],{},[196,1462,1463],{},"The recent list grows forever."," Truncation happens on read rather than write, or ",[223,1466,681],{}," is applied to the wrong list.",[193,1469,1470,1473,1474,1477],{},[196,1471,1472],{},"The list comes back as a string."," It contained non-string entries, or ",[223,1475,1476],{},"type=list"," was omitted on the read.",[193,1479,1480,1483],{},[196,1481,1482],{},"Restored geometry puts the dialog off-screen."," A monitor that no longer exists. Validate against the available screen geometry before restoring.",[193,1485,1486,1489,1490,1493],{},[196,1487,1488],{},"Values are lost when QGIS is killed."," Settings are flushed on destruction; call ",[223,1491,1492],{},"sync()"," after writing anything you cannot afford to lose.",[185,1495,1497],{"id":1496},"conclusion","Conclusion",[14,1499,1500,1501,1503],{},"Store the folder rather than the file, write it only after a successful selection, and resolve the starting directory through a fallback chain that ends somewhere that always exists. A five-item recent list covers people who move between projects, ",[223,1502,1097],{}," removes most of the boilerplate, and the same settings pattern restores dialog geometry. None of it is difficult, and together it is most of what makes a plugin feel considered.",[185,1505,1507],{"id":1506},"frequently-asked-questions","Frequently Asked Questions",[14,1509,1510,1513],{},[196,1511,1512],{},"Should the last folder be a setting or a project entry?","\nA setting. It belongs to the person and their machine, not to the map — storing a local path in the project file breaks for every colleague who opens it.",[14,1515,1516,1519],{},[196,1517,1518],{},"Can I use QGIS's own last-used directory instead of my own key?","\nAs a fallback, yes, and it is a good first-run default. As the only mechanism, no: other tools overwrite it, so your plugin would open wherever the last unrelated action happened to be.",[14,1521,1522,1525,1526,1529],{},[196,1523,1524],{},"How do I offer the recent list in the interface?","\nAn editable combo box populated from ",[223,1527,1528],{},"recent_folders()",", with a browse button beside it. The user gets one click for the common case and full freedom otherwise.",[14,1531,1532,1535],{},[196,1533,1534],{},"Does this work in a Processing algorithm?","\nProcessing manages its own parameter defaults and history, so let it. This pattern is for a plugin's own dialogs.",[14,1537,1538,1541],{},[196,1539,1540],{},"Is it worth remembering other choices too?","\nYes — the last selected layer, the last output format, the state of a checkbox. Anything the user sets identically every run is a candidate, provided the default remains sensible when it is missing.",[185,1543,1545],{"id":1544},"related","Related",[190,1547,1548,1553,1557,1563,1569],{},[193,1549,1550,1552],{},[21,1551,24],{"href":23}," — the guide this recipe belongs to",[193,1554,1555],{},[21,1556,209],{"href":208},[193,1558,1559],{},[21,1560,1562],{"href":1561},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings\u002F","Add a Plugin Options Page to QGIS Settings",[193,1564,1565],{},[21,1566,1568],{"href":1567},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","Add a Custom Dock Widget in PyQGIS",[193,1570,1571],{},[21,1572,664],{"href":663},[1574,1575,1576],"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":221,"searchDepth":239,"depth":239,"links":1578},[1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590],{"id":187,"depth":239,"text":188},{"id":213,"depth":239,"text":214},{"id":481,"depth":239,"text":482},{"id":667,"depth":239,"text":668},{"id":997,"depth":239,"text":998},{"id":1197,"depth":239,"text":1198},{"id":1335,"depth":239,"text":1336},{"id":1363,"depth":239,"text":1364},{"id":1443,"depth":239,"text":1444},{"id":1496,"depth":239,"text":1497},{"id":1506,"depth":239,"text":1507},{"id":1544,"depth":239,"text":1545},"Make file dialogs open where the user last worked — persist and restore paths with QgsSettings, keep a recent-folders list, fall back sensibly when a path disappears, and restore dialog geometry too.","md",{"slug":1594,"type":1595,"breadcrumb":1596,"datePublished":1597,"dateModified":1597},"remember-last-used-folder-in-plugin-pyqgis","article","Remember Last Folder","2026-08-15","\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fremember-last-used-folder-in-plugin-pyqgis",{"title":5,"description":1591},"qgis-plugin-development\u002Fplugin-settings-and-localization\u002Fremember-last-used-folder-in-plugin-pyqgis\u002Findex","ZLy3TuCo9FxC9OQRNEanoZoiMR0ddKLH4UIStPxhGBw",1786789584641]