[{"data":1,"prerenderedAt":1273},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-and-save-scripts-in-qgis-python-editor":3},{"id":4,"title":5,"body":6,"description":1262,"extension":1263,"meta":1264,"navigation":255,"path":1269,"seo":1270,"stem":1271,"__hash__":1272},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-and-save-scripts-in-qgis-python-editor\u002Findex.md","Run and Save Scripts in the QGIS Python Editor",{"type":7,"value":8,"toc":1249},"minimark",[9,13,17,26,178,183,200,204,216,498,516,520,523,556,583,587,590,657,673,780,784,791,866,879,972,976,984,995,1001,1007,1010,1014,1082,1093,1097,1149,1153,1162,1166,1172,1188,1194,1208,1214,1218,1245],[10,11,5],"h1",{"id":12},"run-and-save-scripts-in-the-qgis-python-editor",[14,15,16],"p",{},"The console is where PyQGIS starts and a poor place for it to stay. As soon as a task is more than three lines, retyping it after every mistake becomes the work, and the useful version of yesterday's script is somewhere in the scrollback. The editor built into the console panel fixes that for very little effort: a file you can save, re-run with one key, and keep.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console Basics",". It covers the editor itself, the import-caching trap that makes edits appear to have no effect, putting your own modules on the path, running a script at QGIS startup, and the point at which an external editor becomes the better tool.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,98,102,106,115,119,124,127,130,133,136,139,143,148,152,155,158,161,164,167,174],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 268","img","Comparison of three ways to run PyQGIS code, the console for one-liners, the built-in editor for saved scripts, and an external editor for anything larger, with the trade-offs of each","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three places to write PyQGIS, and when each stops being right",[40,41,42],"desc",{},"The console suits single expressions and quick inspection but loses everything on restart. The built-in editor suits saved scripts that are run repeatedly inside QGIS, with syntax highlighting and one-key execution. An external editor suits plugins and anything under version control, with real debugging and completion, at the cost of needing the environment configured.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","268","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"editorArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#59645f",[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","Grow out of each one deliberately",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"16","48","232","176","10","#fdf2e2","#b45309","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"132","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","the console",[68,93,97],{"x":88,"y":94,"style":95,"fill":96,"textAnchor":74},"100","text-anchor:middle;font-size:11px;font-family:sans-serif","#2f3b35","one expression at a time",[68,99,101],{"x":88,"y":100,"style":95,"fill":96,"textAnchor":74},"122","instant feedback",[68,103,105],{"x":88,"y":104,"style":95,"fill":96,"textAnchor":74},"144","history lost on restart",[44,107],{"x":108,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":84,"style":114},"36","168","192","40","6","#fffdf7","stroke-width:1.5",[68,116,118],{"x":88,"y":117,"style":95,"fill":96,"textAnchor":74},"193","outgrown at three lines",[44,120],{"x":121,"y":79,"width":80,"height":81,"rx":82,"fill":122,"stroke":123,"style":85},"264","#eef7f4","#0f766e",[68,125,126],{"x":70,"y":89,"style":90,"fill":123,"textAnchor":74},"the built-in editor",[68,128,129],{"x":70,"y":94,"style":95,"fill":96,"textAnchor":74},"saved files, run with one key",[68,131,132],{"x":70,"y":100,"style":95,"fill":96,"textAnchor":74},"runs inside the live QGIS",[68,134,135],{"x":70,"y":104,"style":95,"fill":96,"textAnchor":74},"no debugger, basic completion",[44,137],{"x":138,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":123,"style":114},"284",[68,140,142],{"x":70,"y":117,"style":141,"fill":123,"textAnchor":74},"text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","the sweet spot for scripts",[44,144],{"x":145,"y":79,"width":80,"height":81,"rx":82,"fill":146,"stroke":147,"style":85},"512","#eff3ff","#2563eb",[68,149,151],{"x":150,"y":89,"style":90,"fill":147,"textAnchor":74},"628","an external editor",[68,153,154],{"x":150,"y":94,"style":95,"fill":96,"textAnchor":74},"version control, refactoring",[68,156,157],{"x":150,"y":100,"style":95,"fill":96,"textAnchor":74},"real breakpoint debugging",[68,159,160],{"x":150,"y":104,"style":95,"fill":96,"textAnchor":74},"needs the environment set up",[44,162],{"x":163,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":147,"style":114},"532",[68,165,166],{"x":150,"y":117,"style":141,"fill":147,"textAnchor":74},"plugins and real projects",[168,169],"line",{"x1":170,"y1":171,"x2":172,"y2":171,"stroke":66,"style":173},"248","136","258","stroke-width:2;marker-end:url(#editorArrow)",[168,175],{"x1":176,"y1":171,"x2":177,"y2":171,"stroke":66,"style":173},"496","506",[179,180,182],"h2",{"id":181},"prerequisites","Prerequisites",[184,185,186,194,197],"ul",{},[187,188,189,193],"li",{},[190,191,192],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[187,195,196],{},"The Python console open — Plugins → Python Console, or Ctrl+Alt+P.",[187,198,199],{},"A folder you are happy to keep scripts in; anywhere writable will do.",[179,201,203],{"id":202},"open-the-editor-and-run-a-script","Open the editor and run a script",[14,205,206,207,211,212,215],{},"The editor is the second button on the console toolbar — \"Show Editor\" — and it opens a tabbed pane beside the console output. Write a file, save it, and press Ctrl+Shift+E to run it. The script executes in the same interpreter as the console, so ",[208,209,210],"code",{},"iface",", ",[208,213,214],{},"QgsProject"," and anything you have already imported are available, and anything the script defines stays available in the console afterwards.",[217,218,223],"pre",{"className":219,"code":220,"language":221,"meta":222,"style":222},"language-python shiki shiki-themes github-dark","\"\"\"Summarise parcel areas by ward — run with Ctrl+Shift+E.\"\"\"\nfrom qgis.core import QgsProject\n\ndef summarise(layer_name=\"Parcels\", field=\"ward\"):\n    layers = QgsProject.instance().mapLayersByName(layer_name)\n    if not layers:\n        raise RuntimeError(f\"no layer called {layer_name}\")\n\n    totals = {}\n    for feature in layers[0].getFeatures():\n        totals[feature[field]] = totals.get(feature[field], 0) + feature.geometry().area()\n    return totals\n\n\nif __name__ == \"__console__\":\n    for ward, area in sorted(summarise().items()):\n        print(f\"{ward}: {area \u002F 10000:.1f} ha\")\n","python","",[208,224,225,233,250,257,287,298,310,344,349,360,380,402,411,416,421,439,455],{"__ignoreMap":222},[226,227,229],"span",{"class":168,"line":228},1,[226,230,232],{"class":231},"sU2Wk","\"\"\"Summarise parcel areas by ward — run with Ctrl+Shift+E.\"\"\"\n",[226,234,236,240,244,247],{"class":168,"line":235},2,[226,237,239],{"class":238},"snl16","from",[226,241,243],{"class":242},"s95oV"," qgis.core ",[226,245,246],{"class":238},"import",[226,248,249],{"class":242}," QgsProject\n",[226,251,253],{"class":168,"line":252},3,[226,254,256],{"emptyLinePlaceholder":255},true,"\n",[226,258,260,263,267,270,273,276,279,281,284],{"class":168,"line":259},4,[226,261,262],{"class":238},"def",[226,264,266],{"class":265},"svObZ"," summarise",[226,268,269],{"class":242},"(layer_name",[226,271,272],{"class":238},"=",[226,274,275],{"class":231},"\"Parcels\"",[226,277,278],{"class":242},", field",[226,280,272],{"class":238},[226,282,283],{"class":231},"\"ward\"",[226,285,286],{"class":242},"):\n",[226,288,290,293,295],{"class":168,"line":289},5,[226,291,292],{"class":242},"    layers ",[226,294,272],{"class":238},[226,296,297],{"class":242}," QgsProject.instance().mapLayersByName(layer_name)\n",[226,299,301,304,307],{"class":168,"line":300},6,[226,302,303],{"class":238},"    if",[226,305,306],{"class":238}," not",[226,308,309],{"class":242}," layers:\n",[226,311,313,316,320,323,326,329,332,335,338,341],{"class":168,"line":312},7,[226,314,315],{"class":238},"        raise",[226,317,319],{"class":318},"sDLfK"," RuntimeError",[226,321,322],{"class":242},"(",[226,324,325],{"class":238},"f",[226,327,328],{"class":231},"\"no layer called ",[226,330,331],{"class":318},"{",[226,333,334],{"class":242},"layer_name",[226,336,337],{"class":318},"}",[226,339,340],{"class":231},"\"",[226,342,343],{"class":242},")\n",[226,345,347],{"class":168,"line":346},8,[226,348,256],{"emptyLinePlaceholder":255},[226,350,352,355,357],{"class":168,"line":351},9,[226,353,354],{"class":242},"    totals ",[226,356,272],{"class":238},[226,358,359],{"class":242}," {}\n",[226,361,363,366,369,372,375,377],{"class":168,"line":362},10,[226,364,365],{"class":238},"    for",[226,367,368],{"class":242}," feature ",[226,370,371],{"class":238},"in",[226,373,374],{"class":242}," layers[",[226,376,46],{"class":318},[226,378,379],{"class":242},"].getFeatures():\n",[226,381,383,386,388,391,393,396,399],{"class":168,"line":382},11,[226,384,385],{"class":242},"        totals[feature[field]] ",[226,387,272],{"class":238},[226,389,390],{"class":242}," totals.get(feature[field], ",[226,392,46],{"class":318},[226,394,395],{"class":242},") ",[226,397,398],{"class":238},"+",[226,400,401],{"class":242}," feature.geometry().area()\n",[226,403,405,408],{"class":168,"line":404},12,[226,406,407],{"class":238},"    return",[226,409,410],{"class":242}," totals\n",[226,412,414],{"class":168,"line":413},13,[226,415,256],{"emptyLinePlaceholder":255},[226,417,419],{"class":168,"line":418},14,[226,420,256],{"emptyLinePlaceholder":255},[226,422,424,427,430,433,436],{"class":168,"line":423},15,[226,425,426],{"class":238},"if",[226,428,429],{"class":318}," __name__",[226,431,432],{"class":238}," ==",[226,434,435],{"class":231}," \"__console__\"",[226,437,438],{"class":242},":\n",[226,440,442,444,447,449,452],{"class":168,"line":441},16,[226,443,365],{"class":238},[226,445,446],{"class":242}," ward, area ",[226,448,371],{"class":238},[226,450,451],{"class":318}," sorted",[226,453,454],{"class":242},"(summarise().items()):\n",[226,456,458,461,463,465,467,469,472,474,477,479,482,485,488,491,493,496],{"class":168,"line":457},17,[226,459,460],{"class":318},"        print",[226,462,322],{"class":242},[226,464,325],{"class":238},[226,466,340],{"class":231},[226,468,331],{"class":318},[226,470,471],{"class":242},"ward",[226,473,337],{"class":318},[226,475,476],{"class":231},": ",[226,478,331],{"class":318},[226,480,481],{"class":242},"area ",[226,483,484],{"class":238},"\u002F",[226,486,487],{"class":318}," 10000",[226,489,490],{"class":238},":.1f",[226,492,337],{"class":318},[226,494,495],{"class":231}," ha\"",[226,497,343],{"class":242},[14,499,500,503,504,507,508,511,512,515],{},[190,501,502],{},"Breakdown:"," Wrapping the work in a function and calling it under a guard means the file is useful in two ways: run it and it prints a summary, or import it and call ",[208,505,506],{},"summarise()"," with different arguments. The guard value is ",[208,509,510],{},"\"__console__\""," rather than ",[208,513,514],{},"\"__main__\""," — that is the module name the QGIS console gives to the script it runs, which is a genuine QGIS-specific detail that catches people out. Keeping the parameters as arguments with defaults rather than constants at the top means the console can experiment without editing the file.",[179,517,519],{"id":518},"beware-the-import-cache","Beware the import cache",[14,521,522],{},"The trap that costs everybody an hour once: a script imported as a module is cached, so editing the file and re-running the importer changes nothing.",[217,524,526],{"className":219,"code":525,"language":221,"meta":222,"style":222},"import importlib\nimport my_tools\n\nimportlib.reload(my_tools)\nmy_tools.run()\n",[208,527,528,535,542,546,551],{"__ignoreMap":222},[226,529,530,532],{"class":168,"line":228},[226,531,246],{"class":238},[226,533,534],{"class":242}," importlib\n",[226,536,537,539],{"class":168,"line":235},[226,538,246],{"class":238},[226,540,541],{"class":242}," my_tools\n",[226,543,544],{"class":168,"line":252},[226,545,256],{"emptyLinePlaceholder":255},[226,547,548],{"class":168,"line":259},[226,549,550],{"class":242},"importlib.reload(my_tools)\n",[226,552,553],{"class":168,"line":289},[226,554,555],{"class":242},"my_tools.run()\n",[14,557,558,560,561,564,565,568,569,572,573,577,578,582],{},[190,559,502],{}," Python caches modules in ",[208,562,563],{},"sys.modules"," on first import, and a second ",[208,566,567],{},"import my_tools"," is a no-op. ",[208,570,571],{},"importlib.reload()"," re-executes the file, which picks up your edits — but only for that module, not for modules it imports, so a package of several files may need reloading in dependency order. This is the same problem plugins have, and the same reason the Plugin Reloader tool exists, as described in ",[21,574,576],{"href":575},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Freload-qgis-plugin-without-restart\u002F","Reload a QGIS Plugin Without Restarting",". Scripts ",[579,580,581],"em",{},"run"," from the editor with Ctrl+Shift+E are executed fresh every time and are not affected; only imports are.",[179,584,586],{"id":585},"put-your-own-modules-on-the-path","Put your own modules on the path",[14,588,589],{},"Once a few scripts share helper functions, put them in a folder QGIS can import from.",[217,591,593],{"className":219,"code":592,"language":221,"meta":222,"style":222},"import sys\n\nfolder = \"\u002Fhome\u002Fana\u002Fpyqgis_tools\"\nif folder not in sys.path:\n    sys.path.append(folder)\n\nfrom geometry_helpers import densify        # your own module\n",[208,594,595,602,606,616,632,637,641],{"__ignoreMap":222},[226,596,597,599],{"class":168,"line":228},[226,598,246],{"class":238},[226,600,601],{"class":242}," sys\n",[226,603,604],{"class":168,"line":235},[226,605,256],{"emptyLinePlaceholder":255},[226,607,608,611,613],{"class":168,"line":252},[226,609,610],{"class":242},"folder ",[226,612,272],{"class":238},[226,614,615],{"class":231}," \"\u002Fhome\u002Fana\u002Fpyqgis_tools\"\n",[226,617,618,620,623,626,629],{"class":168,"line":259},[226,619,426],{"class":238},[226,621,622],{"class":242}," folder ",[226,624,625],{"class":238},"not",[226,627,628],{"class":238}," in",[226,630,631],{"class":242}," sys.path:\n",[226,633,634],{"class":168,"line":289},[226,635,636],{"class":242},"    sys.path.append(folder)\n",[226,638,639],{"class":168,"line":300},[226,640,256],{"emptyLinePlaceholder":255},[226,642,643,645,648,650,653],{"class":168,"line":312},[226,644,239],{"class":238},[226,646,647],{"class":242}," geometry_helpers ",[226,649,246],{"class":238},[226,651,652],{"class":242}," densify        ",[226,654,656],{"class":655},"sjoCn","# your own module\n",[14,658,659,661,662,665,666,669,670,672],{},[190,660,502],{}," Appending to ",[208,663,664],{},"sys.path"," at the top of a script works immediately and lasts for the session. For something permanent, the same two lines belong in the startup script described below, or the folder can be added to the ",[208,667,668],{},"PYTHONPATH"," environment variable before QGIS launches. Guard the append with the membership test or the path accumulates duplicates every time the script runs, which is harmless but makes ",[208,671,664],{}," unreadable when you are debugging an import problem.",[14,674,675],{},[29,676,679,682,685,688,695,698,704,709,713,717,721,724,728,732,735,740,744,748,752,756,758,761,764,766,769,772,776,778],{"viewBox":677,"role":32,"ariaLabel":678,"xmlns":34},"0 0 760 250","Diagram of how QGIS finds a Python module, checking already imported modules first, then the script folder, then the user path additions, then the QGIS and system libraries",[36,680,681],{},"Where an import actually looks",[40,683,684],{},"An import statement first checks the modules already loaded in memory, which is why editing a file changes nothing until it is reloaded. Only if the module is not already loaded does the search continue through the paths, in order: the current script folder, any folders added to the path, the QGIS Python libraries, and finally the system site packages.",[44,686],{"x":46,"y":46,"width":47,"height":687,"fill":49},"250",[51,689,690],{},[54,691,693],{"id":692,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"pathArrow",[63,694],{"d":65,"fill":96},[68,696,697],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"The first stop explains the edits that seem to do nothing",[44,699],{"x":700,"y":701,"width":702,"height":703,"rx":58,"fill":83,"stroke":84,"style":85},"20","56","200","72",[68,705,708],{"x":706,"y":707,"style":141,"fill":84,"textAnchor":74},"120","84","already imported?",[68,710,712],{"x":706,"y":711,"style":95,"fill":96,"textAnchor":74},"106","sys.modules cache",[44,714],{"x":121,"y":701,"width":715,"height":703,"rx":58,"fill":122,"stroke":123,"style":716},"196","stroke-width:2",[68,718,720],{"x":719,"y":707,"style":141,"fill":123,"textAnchor":74},"362","the script folder",[68,722,723],{"x":719,"y":711,"style":95,"fill":96,"textAnchor":74},"and your path additions",[44,725],{"x":726,"y":701,"width":727,"height":703,"rx":58,"fill":146,"stroke":147,"style":716},"504","236",[68,729,731],{"x":730,"y":707,"style":141,"fill":147,"textAnchor":74},"622","QGIS and system libraries",[68,733,734],{"x":730,"y":711,"style":95,"fill":96,"textAnchor":74},"qgis, PyQt, site packages",[168,736],{"x1":737,"y1":738,"x2":172,"y2":738,"stroke":96,"style":739},"220","92","stroke-width:2;marker-end:url(#pathArrow)",[168,741],{"x1":742,"y1":738,"x2":743,"y2":738,"stroke":96,"style":739},"460","498",[44,745],{"x":700,"y":746,"width":702,"height":747,"rx":58,"fill":113,"stroke":84,"style":716},"160","66",[68,749,751],{"x":706,"y":750,"style":141,"fill":84,"textAnchor":74},"186","found here — no reread",[68,753,755],{"x":706,"y":754,"style":95,"fill":96,"textAnchor":74},"208","importlib.reload() forces one",[44,757],{"x":121,"y":746,"width":715,"height":747,"rx":58,"fill":113,"stroke":123,"style":716},[68,759,760],{"x":719,"y":750,"style":141,"fill":123,"textAnchor":74},"file read and executed",[68,762,763],{"x":719,"y":754,"style":95,"fill":96,"textAnchor":74},"then cached for next time",[44,765],{"x":726,"y":746,"width":727,"height":747,"rx":58,"fill":113,"stroke":147,"style":716},[68,767,768],{"x":730,"y":750,"style":141,"fill":147,"textAnchor":74},"not found anywhere",[68,770,771],{"x":730,"y":754,"style":95,"fill":96,"textAnchor":74},"ImportError names the module",[168,773],{"x1":706,"y1":774,"x2":706,"y2":775,"stroke":96,"style":739},"128","156",[168,777],{"x1":719,"y1":774,"x2":719,"y2":775,"stroke":96,"style":739},[168,779],{"x1":730,"y1":774,"x2":730,"y2":775,"stroke":96,"style":739},[179,781,783],{"id":782},"run-code-at-qgis-startup","Run code at QGIS startup",[14,785,786,787,790],{},"QGIS runs a Python file called ",[208,788,789],{},"startup.py"," in the active profile folder every time it launches — the right place for path additions, small conveniences and anything you want available in every session.",[217,792,794],{"className":219,"code":793,"language":221,"meta":222,"style":222},"# ~\u002F.local\u002Fshare\u002FQGIS\u002FQGIS3\u002Fprofiles\u002Fdefault\u002Fpython\u002Fstartup.py\nimport sys\n\nfor folder in (\"\u002Fhome\u002Fana\u002Fpyqgis_tools\", \"\u002Fhome\u002Fana\u002Fshared_gis_helpers\"):\n    if folder not in sys.path:\n        sys.path.append(folder)\n\nprint(\"PyQGIS tools ready\")\n",[208,795,796,801,807,811,833,845,850,854],{"__ignoreMap":222},[226,797,798],{"class":168,"line":228},[226,799,800],{"class":655},"# ~\u002F.local\u002Fshare\u002FQGIS\u002FQGIS3\u002Fprofiles\u002Fdefault\u002Fpython\u002Fstartup.py\n",[226,802,803,805],{"class":168,"line":235},[226,804,246],{"class":238},[226,806,601],{"class":242},[226,808,809],{"class":168,"line":252},[226,810,256],{"emptyLinePlaceholder":255},[226,812,813,816,818,820,823,826,828,831],{"class":168,"line":259},[226,814,815],{"class":238},"for",[226,817,622],{"class":242},[226,819,371],{"class":238},[226,821,822],{"class":242}," (",[226,824,825],{"class":231},"\"\u002Fhome\u002Fana\u002Fpyqgis_tools\"",[226,827,211],{"class":242},[226,829,830],{"class":231},"\"\u002Fhome\u002Fana\u002Fshared_gis_helpers\"",[226,832,286],{"class":242},[226,834,835,837,839,841,843],{"class":168,"line":289},[226,836,303],{"class":238},[226,838,622],{"class":242},[226,840,625],{"class":238},[226,842,628],{"class":238},[226,844,631],{"class":242},[226,846,847],{"class":168,"line":300},[226,848,849],{"class":242},"        sys.path.append(folder)\n",[226,851,852],{"class":168,"line":312},[226,853,256],{"emptyLinePlaceholder":255},[226,855,856,859,861,864],{"class":168,"line":346},[226,857,858],{"class":318},"print",[226,860,322],{"class":242},[226,862,863],{"class":231},"\"PyQGIS tools ready\"",[226,865,343],{"class":242},[14,867,868,870,871,874,875,878],{},[190,869,502],{}," The path differs by platform — on Windows it is under ",[208,872,873],{},"AppData\u002FRoaming\u002FQGIS\u002FQGIS3\u002Fprofiles",", on macOS under ",[208,876,877],{},"Library\u002FApplication Support"," — but the profile folder is always reachable from Settings → User Profiles → Open Active Profile Folder. Keep this file small and fast: it runs before the interface appears, and an exception here can prevent QGIS from starting cleanly. It is also profile-specific, which makes it a good way to keep an experimental setup separate from the profile you do real work in.",[14,880,881],{},[29,882,885,888,891,894,901,904,908,912,916,920,924,927,931,934,937,941,944,948,952,955,961,965,968],{"viewBox":883,"role":32,"ariaLabel":884,"xmlns":34},"0 0 760 240","Diagram of a QGIS user profile folder showing where the startup script, plugins, settings and the authentication database live",[36,886,887],{},"What a user profile contains",[40,889,890],{},"A profile folder holds the startup script run at launch, the installed plugins, the settings file, and the authentication database. Because each profile has its own copy of all four, switching profiles gives a clean environment for testing without disturbing the one used for real work.",[44,892],{"x":46,"y":46,"width":47,"height":893,"fill":49},"240",[51,895,896],{},[54,897,899],{"id":898,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"profArrow",[63,900],{"d":65,"fill":123},[68,902,903],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Everything below belongs to one profile, not to QGIS",[44,905],{"x":170,"y":906,"width":121,"height":907,"rx":82,"fill":122,"stroke":123,"style":85},"50","52",[68,909,911],{"x":70,"y":910,"style":90,"fill":123,"textAnchor":74},"82","profiles\u002Fdefault\u002F",[44,913],{"x":78,"y":914,"width":915,"height":703,"rx":58,"fill":113,"stroke":66,"style":716},"140","172",[68,917,919],{"x":918,"y":109,"style":141,"fill":73,"textAnchor":74},"102","python\u002Fstartup.py",[68,921,923],{"x":918,"y":922,"style":95,"fill":96,"textAnchor":74},"190","runs at every launch",[44,925],{"x":926,"y":914,"width":915,"height":703,"rx":58,"fill":113,"stroke":66,"style":716},"204",[68,928,930],{"x":929,"y":109,"style":141,"fill":73,"textAnchor":74},"290","python\u002Fplugins\u002F",[68,932,933],{"x":929,"y":922,"style":95,"fill":96,"textAnchor":74},"what is installed",[44,935],{"x":936,"y":914,"width":915,"height":703,"rx":58,"fill":113,"stroke":66,"style":716},"392",[68,938,940],{"x":939,"y":109,"style":141,"fill":73,"textAnchor":74},"478","QGIS3.ini",[68,942,943],{"x":939,"y":922,"style":95,"fill":96,"textAnchor":74},"every setting",[44,945],{"x":946,"y":914,"width":947,"height":703,"rx":58,"fill":113,"stroke":66,"style":716},"580","164",[68,949,951],{"x":950,"y":109,"style":141,"fill":73,"textAnchor":74},"662","qgis-auth.db",[68,953,954],{"x":950,"y":922,"style":95,"fill":96,"textAnchor":74},"stored credentials",[168,956],{"x1":957,"y1":918,"x2":958,"y2":959,"stroke":123,"style":960},"330","130","134","stroke-width:2;marker-end:url(#profArrow)",[168,962],{"x1":963,"y1":918,"x2":964,"y2":959,"stroke":123,"style":960},"360","300",[168,966],{"x1":967,"y1":918,"x2":742,"y2":959,"stroke":123,"style":960},"400",[168,969],{"x1":970,"y1":918,"x2":971,"y2":959,"stroke":123,"style":960},"430","640",[179,973,975],{"id":974},"know-when-to-move-to-a-real-editor","Know when to move to a real editor",[14,977,978,979,983],{},"The built-in editor is deliberately modest. Three signs mean it is time to move on, all covered in ",[21,980,982],{"href":981},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002F","Setting Up PyCharm for QGIS",":",[14,985,986,989,990,994],{},[190,987,988],{},"You want breakpoints."," Print statements stop scaling somewhere around the third nested function. A real debugger attached to the running QGIS — the workflow in ",[21,991,993],{"href":992},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fdebug-qgis-plugin-with-debugpy\u002F","Debug a QGIS Plugin with debugpy"," — pays for its setup in one session.",[14,996,997,1000],{},[190,998,999],{},"The code is more than one file."," Multi-module projects need proper navigation, refactoring and an import graph you can see. Reloading three modules by hand in the right order is a signal, not a workflow.",[14,1002,1003,1006],{},[190,1004,1005],{},"It belongs in version control."," Anything shared, scheduled or shipped should be in a repository with tests, which means an editor that understands the project layout.",[14,1008,1009],{},"None of this makes the built-in editor obsolete. It remains the fastest way to run a fifty-line script against the project currently open in front of you, which is a large share of all PyQGIS ever written.",[179,1011,1013],{"id":1012},"qgis-version-compatibility","QGIS version compatibility",[1015,1016,1017,1033],"table",{},[1018,1019,1020],"thead",{},[1021,1022,1023,1027,1030],"tr",{},[1024,1025,1026],"th",{},"QGIS version",[1024,1028,1029],{},"Python",[1024,1031,1032],{},"Notes",[1034,1035,1036,1051,1061,1072],"tbody",{},[1021,1037,1038,1042,1045],{},[1039,1040,1041],"td",{},"3.22 LTR",[1039,1043,1044],{},"3.9",[1039,1046,1047,1048,1050],{},"Editor, Ctrl+Shift+E and ",[208,1049,789],{}," all present.",[1021,1052,1053,1056,1058],{},[1039,1054,1055],{},"3.28 LTR",[1039,1057,1044],{},[1039,1059,1060],{},"Identical.",[1021,1062,1063,1066,1069],{},[1039,1064,1065],{},"3.34 LTR",[1039,1067,1068],{},"3.12",[1039,1070,1071],{},"Baseline for this page.",[1021,1073,1074,1077,1079],{},[1039,1075,1076],{},"3.40 \u002F 3.44",[1039,1078,1068],{},[1039,1080,1081],{},"Editor gained better completion and an improved find dialog; behaviour is otherwise the same.",[14,1083,1084,1085,1088,1089,1092],{},"The console's script module name has been ",[208,1086,1087],{},"__console__"," throughout the 3.x series, but do not rely on it in code that must also run standalone — check for both it and ",[208,1090,1091],{},"__main__",".",[179,1094,1096],{"id":1095},"troubleshooting","Troubleshooting",[184,1098,1099,1108,1123,1129,1135,1143],{},[187,1100,1101,1104,1105,1107],{},[190,1102,1103],{},"Edits to an imported module have no effect."," Module caching. Use ",[208,1106,571],{},", or run the file from the editor rather than importing it.",[187,1109,1110,1116,1117,1119,1120,1122],{},[190,1111,1112,1115],{},[208,1113,1114],{},"ImportError"," for your own module."," The folder is not on ",[208,1118,664],{},", or the file name does not match the import name. Print ",[208,1121,664],{}," to check.",[187,1124,1125,1128],{},[190,1126,1127],{},"The script runs but nothing prints."," Output goes to the console pane above the editor, which may be scrolled away or collapsed.",[187,1130,1131,1134],{},[190,1132,1133],{},"A syntax error points at the wrong line."," Mixed tabs and spaces. The editor can show whitespace; turn it on once and the problem is obvious.",[187,1136,1137,1142],{},[190,1138,1139,1141],{},[208,1140,789],{}," seems to be ignored."," It is in the wrong profile folder. Open the active profile folder from the menu rather than guessing the path.",[187,1144,1145,1148],{},[190,1146,1147],{},"The script works once and fails on re-run."," It mutates state left over from the previous run — an open edit session, a layer already added. Make scripts idempotent.",[179,1150,1152],{"id":1151},"conclusion","Conclusion",[14,1154,1155,1156,1158,1159,1161],{},"Use the console for one-liners and the built-in editor the moment a task is worth keeping: save the file, run it with Ctrl+Shift+E, and wrap the work in functions so the console can call them with different arguments. Remember that imports are cached and edits need a reload, add your own folder to ",[208,1157,664],{}," for shared helpers, and use ",[208,1160,789],{}," for anything that should be ready in every session.",[179,1163,1165],{"id":1164},"frequently-asked-questions","Frequently Asked Questions",[14,1167,1168,1171],{},[190,1169,1170],{},"Does the editor run scripts in the same interpreter as the console?","\nYes. Everything the script defines is available in the console afterwards, and everything already imported is available to the script.",[14,1173,1174,1184,1185,1187],{},[190,1175,1176,1177,1180,1181,1183],{},"Why is ",[208,1178,1179],{},"__name__"," not ",[208,1182,1091],{},"?","\nThe console runs scripts under the name ",[208,1186,1087],{},". Check for that value, or for both, if the file should also run standalone.",[14,1189,1190,1193],{},[190,1191,1192],{},"Where are console scripts saved by default?","\nWherever you choose; QGIS offers a default folder in the profile directory but does not require it. Keeping scripts with the project they belong to is usually more useful.",[14,1195,1196,1199,1200,1204,1205,1207],{},[190,1197,1198],{},"Can I run a script without opening QGIS?","\nYes, with a standalone setup — see ",[21,1201,1203],{"href":1202},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","Running Python Scripts Outside QGIS Desktop",". Anything using ",[208,1206,210],{}," will need rewriting first.",[14,1209,1210,1213],{},[190,1211,1212],{},"How do I share a script with a colleague?","\nA file plus a note about which layers it expects. If it is used often enough to be worth documenting properly, it is probably worth being a Processing script or a small plugin instead.",[179,1215,1217],{"id":1216},"related","Related",[184,1219,1220,1225,1231,1235,1241],{},[187,1221,1222,1224],{},[21,1223,24],{"href":23}," — the guide this recipe belongs to",[187,1226,1227],{},[21,1228,1230],{"href":1229},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis\u002F","Access the Active Layer and Selection in PyQGIS",[187,1232,1233],{},[21,1234,982],{"href":981},[187,1236,1237],{},[21,1238,1240],{"href":1239},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","Debugging PyQGIS Scripts",[187,1242,1243],{},[21,1244,1203],{"href":1202},[1246,1247,1248],"style",{},"html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .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}",{"title":222,"searchDepth":235,"depth":235,"links":1250},[1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261],{"id":181,"depth":235,"text":182},{"id":202,"depth":235,"text":203},{"id":518,"depth":235,"text":519},{"id":585,"depth":235,"text":586},{"id":782,"depth":235,"text":783},{"id":974,"depth":235,"text":975},{"id":1012,"depth":235,"text":1013},{"id":1095,"depth":235,"text":1096},{"id":1151,"depth":235,"text":1152},{"id":1164,"depth":235,"text":1165},{"id":1216,"depth":235,"text":1217},"Move from one-line console experiments to saved, re-runnable scripts — the built-in editor, module reloading traps, startup scripts, script folders on the path, and when to graduate to a real IDE.","md",{"slug":1265,"type":1266,"breadcrumb":1267,"datePublished":1268,"dateModified":1268},"run-and-save-scripts-in-qgis-python-editor","article","Run & Save Scripts","2026-08-15","\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-and-save-scripts-in-qgis-python-editor",{"title":5,"description":1262},"pyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-and-save-scripts-in-qgis-python-editor\u002Findex","jQzu0fJDFs-p-g-0316To_g3mreGUwjJWLZ_yeMH3IY",1786789584629]