[{"data":1,"prerenderedAt":1425},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis":3},{"id":4,"title":5,"body":6,"description":1414,"extension":1415,"meta":1416,"navigation":235,"path":1421,"seo":1422,"stem":1423,"__hash__":1424},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis\u002Findex.md","Access the Active Layer and Selection in PyQGIS",{"type":7,"value":8,"toc":1402},"minimark",[9,13,22,31,174,179,199,203,387,412,420,454,467,471,605,620,623,669,679,773,777,850,886,889,904,916,1010,1014,1020,1140,1156,1160,1225,1231,1235,1287,1291,1301,1305,1311,1317,1323,1340,1349,1359,1369,1373,1398],[10,11,5],"h1",{"id":12},"access-the-active-layer-and-selection-in-pyqgis",[14,15,16,17,21],"p",{},"Interactive PyQGIS almost always starts the same way: the user has clicked a layer, maybe selected a few features, and wants a script to act on exactly that. Three or four calls cover it — and each has a failure mode that produces a confusing error rather than a helpful one, because \"no layer is active\" and \"the active layer is a raster\" both arrive as an ",[18,19,20],"code",{},"AttributeError"," several lines later.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console Basics",". It covers reading the active layer, working with the current selection, changing the selection from code, the checks that turn cryptic errors into clear ones, and how to keep the same logic usable outside the console.",[14,32,33],{},[34,35,40,44,48,55,72,81,91,97,106,113,116,120,123,127,132,137,141,144,148,150,153,155,158,161,168],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 274","img","Diagram of what the interface object exposes, showing the active layer, the selected layers in the panel, the selected features within a layer and the map canvas, and which of these exist outside QGIS desktop","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"What the interface knows about the user's current state",[45,46,47],"desc",{},"The interface object exposes the active layer, the layers currently highlighted in the panel, and the map canvas. Separately, each vector layer holds its own set of selected feature identifiers. None of the interface state exists in a standalone script, while the layer selection is a property of the layer itself and does exist wherever the layer does.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","274","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"activeArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#59645f",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Selection lives on the layer; active lives on the interface",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":87,"fill":88,"stroke":89,"style":90},"20","52","330","188","10","#fdf2e2","#b45309","stroke-width:2.5",[73,92,96],{"x":93,"y":94,"style":95,"fill":89,"textAnchor":79},"185","78","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","iface — only in QGIS desktop",[49,98],{"x":99,"y":100,"width":101,"height":102,"rx":103,"fill":104,"stroke":71,"style":105},"42","94","286","32","6","#fffdf7","stroke-width:1.5",[73,107,112],{"x":108,"y":109,"style":110,"fill":111},"58","115","font-size:11px;font-family:sans-serif","#2f3b35","activeLayer() — the highlighted one",[49,114],{"x":99,"y":115,"width":101,"height":102,"rx":103,"fill":104,"stroke":71,"style":105},"132",[73,117,119],{"x":108,"y":118,"style":110,"fill":111},"153","layerTreeView().selectedLayers()",[49,121],{"x":99,"y":122,"width":101,"height":102,"rx":103,"fill":104,"stroke":71,"style":105},"170",[73,124,126],{"x":108,"y":125,"style":110,"fill":111},"191","mapCanvas() — extent and scale",[73,128,131],{"x":93,"y":129,"style":130,"fill":89,"textAnchor":79},"224","text-anchor:middle;font-size:11px;font-family:sans-serif","None of this exists in a scheduled job",[49,133],{"x":134,"y":84,"width":85,"height":86,"rx":87,"fill":135,"stroke":136,"style":90},"410","#eef7f4","#0f766e",[73,138,140],{"x":139,"y":94,"style":95,"fill":136,"textAnchor":79},"575","the layer itself — everywhere",[49,142],{"x":143,"y":100,"width":101,"height":102,"rx":103,"fill":104,"stroke":71,"style":105},"432",[73,145,147],{"x":146,"y":109,"style":110,"fill":111},"448","selectedFeatureCount()",[49,149],{"x":143,"y":115,"width":101,"height":102,"rx":103,"fill":104,"stroke":71,"style":105},[73,151,152],{"x":146,"y":118,"style":110,"fill":111},"selectedFeatures() and their ids",[49,154],{"x":143,"y":122,"width":101,"height":102,"rx":103,"fill":104,"stroke":71,"style":105},[73,156,157],{"x":146,"y":125,"style":110,"fill":111},"selectByExpression, removeSelection",[73,159,160],{"x":139,"y":129,"style":130,"fill":136,"textAnchor":79},"Works in the console and in a script",[162,163],"line",{"x1":164,"y1":165,"x2":166,"y2":165,"stroke":71,"style":167},"350","110","404","stroke-width:2;marker-end:url(#activeArrow)",[73,169,173],{"x":170,"y":171,"style":172,"fill":71,"textAnchor":79},"377","102","text-anchor:middle;font-size:10px;font-family:sans-serif","gives",[175,176,178],"h2",{"id":177},"prerequisites","Prerequisites",[180,181,182,190,193],"ul",{},[183,184,185,189],"li",{},[186,187,188],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer, with the Python console open.",[183,191,192],{},"A project with at least one vector layer, and a few features selected to experiment with.",[183,194,195,198],{},[18,196,197],{},"iface"," is already available in the console; nothing needs importing for it.",[175,200,202],{"id":201},"get-the-active-layer-safely","Get the active layer safely",[204,205,210],"pre",{"className":206,"code":207,"language":208,"meta":209,"style":209},"language-python shiki shiki-themes github-dark","from qgis.core import QgsVectorLayer, QgsWkbTypes\n\nlayer = iface.activeLayer()\n\nif layer is None:\n    raise RuntimeError(\"No layer is active — click one in the Layers panel\")\nif not isinstance(layer, QgsVectorLayer):\n    raise RuntimeError(f\"{layer.name()} is not a vector layer\")\nif layer.geometryType() != QgsWkbTypes.PolygonGeometry:\n    raise RuntimeError(f\"{layer.name()} is not polygons\")\n\nprint(layer.name(), layer.featureCount(), layer.crs().authid())\n","python","",[18,211,212,230,237,249,254,273,292,306,335,349,373,378],{"__ignoreMap":209},[213,214,216,220,224,227],"span",{"class":162,"line":215},1,[213,217,219],{"class":218},"snl16","from",[213,221,223],{"class":222},"s95oV"," qgis.core ",[213,225,226],{"class":218},"import",[213,228,229],{"class":222}," QgsVectorLayer, QgsWkbTypes\n",[213,231,233],{"class":162,"line":232},2,[213,234,236],{"emptyLinePlaceholder":235},true,"\n",[213,238,240,243,246],{"class":162,"line":239},3,[213,241,242],{"class":222},"layer ",[213,244,245],{"class":218},"=",[213,247,248],{"class":222}," iface.activeLayer()\n",[213,250,252],{"class":162,"line":251},4,[213,253,236],{"emptyLinePlaceholder":235},[213,255,257,260,263,266,270],{"class":162,"line":256},5,[213,258,259],{"class":218},"if",[213,261,262],{"class":222}," layer ",[213,264,265],{"class":218},"is",[213,267,269],{"class":268},"sDLfK"," None",[213,271,272],{"class":222},":\n",[213,274,276,279,282,285,289],{"class":162,"line":275},6,[213,277,278],{"class":218},"    raise",[213,280,281],{"class":268}," RuntimeError",[213,283,284],{"class":222},"(",[213,286,288],{"class":287},"sU2Wk","\"No layer is active — click one in the Layers panel\"",[213,290,291],{"class":222},")\n",[213,293,295,297,300,303],{"class":162,"line":294},7,[213,296,259],{"class":218},[213,298,299],{"class":218}," not",[213,301,302],{"class":268}," isinstance",[213,304,305],{"class":222},"(layer, QgsVectorLayer):\n",[213,307,309,311,313,315,318,321,324,327,330,333],{"class":162,"line":308},8,[213,310,278],{"class":218},[213,312,281],{"class":268},[213,314,284],{"class":222},[213,316,317],{"class":218},"f",[213,319,320],{"class":287},"\"",[213,322,323],{"class":268},"{",[213,325,326],{"class":222},"layer.name()",[213,328,329],{"class":268},"}",[213,331,332],{"class":287}," is not a vector layer\"",[213,334,291],{"class":222},[213,336,338,340,343,346],{"class":162,"line":337},9,[213,339,259],{"class":218},[213,341,342],{"class":222}," layer.geometryType() ",[213,344,345],{"class":218},"!=",[213,347,348],{"class":222}," QgsWkbTypes.PolygonGeometry:\n",[213,350,352,354,356,358,360,362,364,366,368,371],{"class":162,"line":351},10,[213,353,278],{"class":218},[213,355,281],{"class":268},[213,357,284],{"class":222},[213,359,317],{"class":218},[213,361,320],{"class":287},[213,363,323],{"class":268},[213,365,326],{"class":222},[213,367,329],{"class":268},[213,369,370],{"class":287}," is not polygons\"",[213,372,291],{"class":222},[213,374,376],{"class":162,"line":375},11,[213,377,236],{"emptyLinePlaceholder":235},[213,379,381,384],{"class":162,"line":380},12,[213,382,383],{"class":268},"print",[213,385,386],{"class":222},"(layer.name(), layer.featureCount(), layer.crs().authid())\n",[14,388,389,392,393,396,397,400,401,403,404,407,408,411],{},[186,390,391],{},"Breakdown:"," Three checks, three clear messages, and every later line can then assume it has a polygon vector layer. Without them, ",[18,394,395],{},"layer.getFeatures()"," on ",[18,398,399],{},"None"," raises an ",[18,402,20],{}," about ",[18,405,406],{},"NoneType",", which tells the user nothing about what they should have clicked. ",[18,409,410],{},"isinstance()"," rather than a type comparison keeps the check working for subclasses. The geometry check matters more than it looks: an algorithm expecting polygons will usually run on lines and produce silently wrong output rather than failing.",[14,413,414,415,419],{},"The ",[416,417,418],"em",{},"active"," layer is the one highlighted in the Layers panel, which is not the same as the layers that are checked, nor the same as the layers the user has selected in the panel. For the latter:",[204,421,423],{"className":206,"code":422,"language":208,"meta":209,"style":209},"selected_layers = iface.layerTreeView().selectedLayers()\nprint([lyr.name() for lyr in selected_layers])\n",[18,424,425,435],{"__ignoreMap":209},[213,426,427,430,432],{"class":162,"line":215},[213,428,429],{"class":222},"selected_layers ",[213,431,245],{"class":218},[213,433,434],{"class":222}," iface.layerTreeView().selectedLayers()\n",[213,436,437,439,442,445,448,451],{"class":162,"line":232},[213,438,383],{"class":268},[213,440,441],{"class":222},"([lyr.name() ",[213,443,444],{"class":218},"for",[213,446,447],{"class":222}," lyr ",[213,449,450],{"class":218},"in",[213,452,453],{"class":222}," selected_layers])\n",[14,455,456,458,459,462,463,466],{},[186,457,391],{}," ",[18,460,461],{},"selectedLayers()"," returns every layer highlighted in the panel, which is what you want for a tool that acts on several at once. It returns an empty list when nothing is highlighted, so a plain ",[18,464,465],{},"if not selected_layers:"," check is the whole error handling. Note that a group node being highlighted does not put its children in this list — collect those from the tree if your tool should treat a group as a selection.",[175,468,470],{"id":469},"work-with-the-selected-features","Work with the selected features",[204,472,474],{"className":206,"code":473,"language":208,"meta":209,"style":209},"count = layer.selectedFeatureCount()\nprint(f\"{count} selected\")\n\nif count == 0:\n    features = layer.getFeatures()               # act on everything\nelse:\n    features = layer.selectedFeatures()          # act on the selection\n\ntotal_area = sum(feature.geometry().area() for feature in features)\nprint(round(total_area, 2))\n",[18,475,476,486,508,512,527,541,548,560,564,587],{"__ignoreMap":209},[213,477,478,481,483],{"class":162,"line":215},[213,479,480],{"class":222},"count ",[213,482,245],{"class":218},[213,484,485],{"class":222}," layer.selectedFeatureCount()\n",[213,487,488,490,492,494,496,498,501,503,506],{"class":162,"line":232},[213,489,383],{"class":268},[213,491,284],{"class":222},[213,493,317],{"class":218},[213,495,320],{"class":287},[213,497,323],{"class":268},[213,499,500],{"class":222},"count",[213,502,329],{"class":268},[213,504,505],{"class":287}," selected\"",[213,507,291],{"class":222},[213,509,510],{"class":162,"line":239},[213,511,236],{"emptyLinePlaceholder":235},[213,513,514,516,519,522,525],{"class":162,"line":251},[213,515,259],{"class":218},[213,517,518],{"class":222}," count ",[213,520,521],{"class":218},"==",[213,523,524],{"class":268}," 0",[213,526,272],{"class":222},[213,528,529,532,534,537],{"class":162,"line":256},[213,530,531],{"class":222},"    features ",[213,533,245],{"class":218},[213,535,536],{"class":222}," layer.getFeatures()               ",[213,538,540],{"class":539},"sjoCn","# act on everything\n",[213,542,543,546],{"class":162,"line":275},[213,544,545],{"class":218},"else",[213,547,272],{"class":222},[213,549,550,552,554,557],{"class":162,"line":294},[213,551,531],{"class":222},[213,553,245],{"class":218},[213,555,556],{"class":222}," layer.selectedFeatures()          ",[213,558,559],{"class":539},"# act on the selection\n",[213,561,562],{"class":162,"line":308},[213,563,236],{"emptyLinePlaceholder":235},[213,565,566,569,571,574,577,579,582,584],{"class":162,"line":337},[213,567,568],{"class":222},"total_area ",[213,570,245],{"class":218},[213,572,573],{"class":268}," sum",[213,575,576],{"class":222},"(feature.geometry().area() ",[213,578,444],{"class":218},[213,580,581],{"class":222}," feature ",[213,583,450],{"class":218},[213,585,586],{"class":222}," features)\n",[213,588,589,591,593,596,599,602],{"class":162,"line":351},[213,590,383],{"class":268},[213,592,284],{"class":222},[213,594,595],{"class":268},"round",[213,597,598],{"class":222},"(total_area, ",[213,600,601],{"class":268},"2",[213,603,604],{"class":222},"))\n",[14,606,607,458,609,611,612,615,616,619],{},[186,608,391],{},[18,610,147],{}," is cheap and does not fetch anything, so it is the right thing to branch on; ",[18,613,614],{},"selectedFeatures()"," builds the full feature objects and is proportionally expensive on a large selection. The fall-back-to-everything pattern shown here is the convention users expect from QGIS's own tools — an empty selection means \"the whole layer\", not \"nothing\" — and matching that convention makes a script feel native. For ids alone, ",[18,617,618],{},"layer.selectedFeatureIds()"," is far cheaper and is all you need when the next step is a feature request.",[14,621,622],{},"For anything more than a few hundred features, pass the ids into a request rather than materialising them all:",[204,624,626],{"className":206,"code":625,"language":208,"meta":209,"style":209},"from qgis.core import QgsFeatureRequest\n\nrequest = QgsFeatureRequest().setFilterFids(layer.selectedFeatureIds())\nfor feature in layer.getFeatures(request):\n    ...\n",[18,627,628,639,643,653,664],{"__ignoreMap":209},[213,629,630,632,634,636],{"class":162,"line":215},[213,631,219],{"class":218},[213,633,223],{"class":222},[213,635,226],{"class":218},[213,637,638],{"class":222}," QgsFeatureRequest\n",[213,640,641],{"class":162,"line":232},[213,642,236],{"emptyLinePlaceholder":235},[213,644,645,648,650],{"class":162,"line":239},[213,646,647],{"class":222},"request ",[213,649,245],{"class":218},[213,651,652],{"class":222}," QgsFeatureRequest().setFilterFids(layer.selectedFeatureIds())\n",[213,654,655,657,659,661],{"class":162,"line":251},[213,656,444],{"class":218},[213,658,581],{"class":222},[213,660,450],{"class":218},[213,662,663],{"class":222}," layer.getFeatures(request):\n",[213,665,666],{"class":162,"line":256},[213,667,668],{"class":268},"    ...\n",[14,670,671,673,674,678],{},[186,672,391],{}," This streams the selected features one at a time instead of building a list of all of them, which matters when a user has selected fifty thousand parcels. It also composes with the other request options — an attribute subset, no geometry — described in ",[26,675,677],{"href":676},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fspeed-up-feature-iteration-with-qgsfeaturerequest-pyqgis\u002F","Speed Up Feature Iteration with QgsFeatureRequest",".",[14,680,681],{},[34,682,685,688,691,694,697,703,707,711,715,719,724,728,733,736,739,742,745,748,751,754,758,761,764,767,770],{"viewBox":683,"role":37,"ariaLabel":684,"xmlns":39},"0 0 760 246","Comparison of three ways to read a selection, showing selected features building full objects, selected feature ids being cheap, and a feature request streaming only the selected rows",[41,686,687],{},"Three ways to read a selection, by cost",[45,689,690],{},"Calling selected features builds every feature object in memory at once, which is fine for a small selection and expensive for a large one. Calling selected feature ids returns only identifiers and is always cheap. Passing those identifiers into a feature request streams the features one at a time, combining low memory use with full access to attributes and geometry.",[49,692],{"x":51,"y":51,"width":52,"height":693,"fill":54},"246",[73,695,696],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Same features, three very different memory profiles",[49,698],{"x":699,"y":700,"width":701,"height":702,"rx":87,"fill":88,"stroke":89,"style":90},"16","48","232","164",[73,704,614],{"x":115,"y":705,"style":706,"fill":89,"textAnchor":79},"74","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif",[73,708,710],{"x":115,"y":709,"style":130,"fill":111,"textAnchor":79},"100","full objects, all at once",[73,712,714],{"x":115,"y":713,"style":130,"fill":111,"textAnchor":79},"122","geometry included",[73,716,718],{"x":115,"y":717,"style":130,"fill":111,"textAnchor":79},"144","fine under a few thousand",[49,720],{"x":721,"y":702,"width":722,"height":723,"rx":103,"fill":104,"stroke":89,"style":105},"36","192","34",[73,725,727],{"x":115,"y":726,"style":130,"fill":111,"textAnchor":79},"186","memory grows with count",[49,729],{"x":730,"y":700,"width":701,"height":702,"rx":87,"fill":731,"stroke":732,"style":90},"264","#edf8e9","#15803d",[73,734,735],{"x":75,"y":705,"style":706,"fill":732,"textAnchor":79},"selectedFeatureIds()",[73,737,738],{"x":75,"y":709,"style":130,"fill":111,"textAnchor":79},"identifiers only",[73,740,741],{"x":75,"y":713,"style":130,"fill":111,"textAnchor":79},"no geometry read",[73,743,744],{"x":75,"y":717,"style":130,"fill":111,"textAnchor":79},"always cheap",[49,746],{"x":747,"y":702,"width":722,"height":723,"rx":103,"fill":104,"stroke":732,"style":105},"284",[73,749,750],{"x":75,"y":726,"style":130,"fill":111,"textAnchor":79},"use to count or to filter",[49,752],{"x":753,"y":700,"width":701,"height":702,"rx":87,"fill":135,"stroke":136,"style":90},"512",[73,755,757],{"x":756,"y":705,"style":706,"fill":136,"textAnchor":79},"628","request with those ids",[73,759,760],{"x":756,"y":709,"style":130,"fill":111,"textAnchor":79},"streamed one at a time",[73,762,763],{"x":756,"y":713,"style":130,"fill":111,"textAnchor":79},"attribute subset possible",[73,765,766],{"x":756,"y":717,"style":130,"fill":111,"textAnchor":79},"scales to any size",[49,768],{"x":769,"y":702,"width":722,"height":723,"rx":103,"fill":104,"stroke":136,"style":105},"532",[73,771,772],{"x":756,"y":726,"style":706,"fill":136,"textAnchor":79},"the default choice",[175,774,776],{"id":775},"change-the-selection-from-code","Change the selection from code",[204,778,780],{"className":206,"code":779,"language":208,"meta":209,"style":209},"layer.selectByExpression(\"\\\"risk_band\\\" = 'high' AND \\\"area_m2\\\" > 5000\")\nprint(layer.selectedFeatureCount())\n\nlayer.selectByIds([12, 47, 108], QgsVectorLayer.AddToSelection)\nlayer.removeSelection()\n",[18,781,782,812,819,823,845],{"__ignoreMap":209},[213,783,784,787,789,792,795,797,800,802,805,807,810],{"class":162,"line":215},[213,785,786],{"class":222},"layer.selectByExpression(",[213,788,320],{"class":287},[213,790,791],{"class":268},"\\\"",[213,793,794],{"class":287},"risk_band",[213,796,791],{"class":268},[213,798,799],{"class":287}," = 'high' AND ",[213,801,791],{"class":268},[213,803,804],{"class":287},"area_m2",[213,806,791],{"class":268},[213,808,809],{"class":287}," > 5000\"",[213,811,291],{"class":222},[213,813,814,816],{"class":162,"line":232},[213,815,383],{"class":268},[213,817,818],{"class":222},"(layer.selectedFeatureCount())\n",[213,820,821],{"class":162,"line":239},[213,822,236],{"emptyLinePlaceholder":235},[213,824,825,828,831,834,837,839,842],{"class":162,"line":251},[213,826,827],{"class":222},"layer.selectByIds([",[213,829,830],{"class":268},"12",[213,832,833],{"class":222},", ",[213,835,836],{"class":268},"47",[213,838,833],{"class":222},[213,840,841],{"class":268},"108",[213,843,844],{"class":222},"], QgsVectorLayer.AddToSelection)\n",[213,846,847],{"class":162,"line":256},[213,848,849],{"class":222},"layer.removeSelection()\n",[14,851,852,458,854,857,858,861,862,865,866,869,870,873,874,877,878,881,882,678],{},[186,853,391],{},[18,855,856],{},"selectByExpression()"," takes QGIS expression syntax, with field names in double quotes and string literals in single quotes — the opposite of what most people type first. ",[18,859,860],{},"selectByIds()"," takes a list of feature ids and an optional behaviour flag: ",[18,863,864],{},"SetSelection"," replaces, ",[18,867,868],{},"AddToSelection"," adds, ",[18,871,872],{},"IntersectSelection"," narrows, ",[18,875,876],{},"RemoveFromSelection"," subtracts, which together let a script build a selection in steps. ",[18,879,880],{},"removeSelection()"," clears it. Selecting from a script is often more useful than acting on the selection directly, because it leaves the user in control of what happens next — and it makes the script's effect visible on the map. The expression language itself is covered in ",[26,883,885],{"href":884},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fselect-features-by-expression-pyqgis\u002F","Select Features by Expression in PyQGIS",[14,887,888],{},"To show the user what was selected, zoom to it:",[204,890,892],{"className":206,"code":891,"language":208,"meta":209,"style":209},"iface.mapCanvas().zoomToSelected(layer)\niface.mapCanvas().refresh()\n",[18,893,894,899],{"__ignoreMap":209},[213,895,896],{"class":162,"line":215},[213,897,898],{"class":222},"iface.mapCanvas().zoomToSelected(layer)\n",[213,900,901],{"class":162,"line":232},[213,902,903],{"class":222},"iface.mapCanvas().refresh()\n",[14,905,906,458,908,911,912,915],{},[186,907,391],{},[18,909,910],{},"zoomToSelected()"," sets the canvas extent to the selection's bounding box with a small margin, and does nothing when nothing is selected — no error, no movement, which is worth knowing when a script appears to have ignored the call. The explicit ",[18,913,914],{},"refresh()"," is occasionally needed when several canvas changes are made in quick succession from a script.",[14,917,918],{},[34,919,922,925,928,931,934,939,942,949,953,957,962,965,968,971,974,977,980,983,986,990,993,996,999,1002,1007],{"viewBox":920,"role":37,"ariaLabel":921,"xmlns":39},"0 0 760 244","Diagram of the four selection behaviour flags showing how set, add, intersect and remove combine a new set of features with the existing selection",[41,923,924],{},"Building a selection in steps",[45,926,927],{},"Setting replaces the current selection with the new features. Adding combines the two. Intersecting keeps only features present in both. Removing subtracts the new features from the current selection. Chaining these lets a script build a precise selection from several simple criteria.",[49,929],{"x":51,"y":51,"width":52,"height":930,"fill":54},"244",[73,932,933],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Four flags, four ways to combine with what is already selected",[49,935],{"x":699,"y":84,"width":936,"height":937,"rx":87,"fill":135,"stroke":136,"style":938},"176","168","stroke-width:2",[73,940,864],{"x":941,"y":94,"style":706,"fill":136,"textAnchor":79},"104",[943,944],"circle",{"cx":945,"cy":946,"r":947,"fill":71,"fillOpacity":948,"stroke":71,"style":105},"84","130","30",0.18,[943,950],{"cx":951,"cy":946,"r":947,"fill":136,"fillOpacity":952,"stroke":136,"style":938},"124",0.4,[73,954,956],{"x":941,"y":955,"style":130,"fill":111,"textAnchor":79},"196","the new set only",[49,958],{"x":959,"y":84,"width":936,"height":937,"rx":87,"fill":960,"stroke":961,"style":938},"204","#eff3ff","#2563eb",[73,963,868],{"x":964,"y":94,"style":706,"fill":961,"textAnchor":79},"292",[943,966],{"cx":967,"cy":946,"r":947,"fill":961,"fillOpacity":952,"stroke":961,"style":938},"272",[943,969],{"cx":970,"cy":946,"r":947,"fill":961,"fillOpacity":952,"stroke":961,"style":938},"312",[73,972,973],{"x":964,"y":955,"style":130,"fill":111,"textAnchor":79},"both together",[49,975],{"x":976,"y":84,"width":936,"height":937,"rx":87,"fill":731,"stroke":732,"style":938},"392",[73,978,872],{"x":979,"y":94,"style":706,"fill":732,"textAnchor":79},"480",[943,981],{"cx":982,"cy":946,"r":947,"fill":71,"fillOpacity":948,"stroke":71,"style":105},"460",[943,984],{"cx":985,"cy":946,"r":947,"fill":71,"fillOpacity":948,"stroke":71,"style":105},"500",[68,987],{"d":988,"fill":732,"fillOpacity":989,"stroke":732,"style":938},"M480 104 A30 30 0 0 1 480 156 A30 30 0 0 1 480 104 Z",0.45,[73,991,992],{"x":979,"y":955,"style":130,"fill":111,"textAnchor":79},"only the overlap",[49,994],{"x":995,"y":84,"width":702,"height":937,"rx":87,"fill":88,"stroke":89,"style":938},"580",[73,997,876],{"x":998,"y":94,"style":706,"fill":89,"textAnchor":79},"662",[943,1000],{"cx":1001,"cy":946,"r":947,"fill":89,"fillOpacity":952,"stroke":89,"style":938},"642",[943,1003],{"cx":1004,"cy":946,"r":947,"fill":71,"fillOpacity":1005,"stroke":71,"style":1006},"682",0.12,"stroke-width:1.5;stroke-dasharray:4 3",[73,1008,1009],{"x":998,"y":955,"style":130,"fill":111,"textAnchor":79},"the new set taken away",[175,1011,1013],{"id":1012},"keep-the-logic-usable-outside-the-console","Keep the logic usable outside the console",[14,1015,1016,1017,1019],{},"Everything above except ",[18,1018,197],{}," works in any context, and that suggests the shape a console experiment should take before it becomes something you run regularly.",[204,1021,1023],{"className":206,"code":1022,"language":208,"meta":209,"style":209},"def summarise(layer, feature_ids=None):\n    request = QgsFeatureRequest()\n    if feature_ids:\n        request.setFilterFids(list(feature_ids))\n    return sum(f.geometry().area() for f in layer.getFeatures(request))\n\n\n# in the console\nprint(summarise(iface.activeLayer(), iface.activeLayer().selectedFeatureIds()))\n\n# in a scheduled script\nprint(summarise(QgsProject.instance().mapLayersByName(\"Parcels\")[0]))\n",[18,1024,1025,1044,1054,1062,1073,1093,1097,1101,1106,1113,1117,1122],{"__ignoreMap":209},[213,1026,1027,1030,1034,1037,1039,1041],{"class":162,"line":215},[213,1028,1029],{"class":218},"def",[213,1031,1033],{"class":1032},"svObZ"," summarise",[213,1035,1036],{"class":222},"(layer, feature_ids",[213,1038,245],{"class":218},[213,1040,399],{"class":268},[213,1042,1043],{"class":222},"):\n",[213,1045,1046,1049,1051],{"class":162,"line":232},[213,1047,1048],{"class":222},"    request ",[213,1050,245],{"class":218},[213,1052,1053],{"class":222}," QgsFeatureRequest()\n",[213,1055,1056,1059],{"class":162,"line":239},[213,1057,1058],{"class":218},"    if",[213,1060,1061],{"class":222}," feature_ids:\n",[213,1063,1064,1067,1070],{"class":162,"line":251},[213,1065,1066],{"class":222},"        request.setFilterFids(",[213,1068,1069],{"class":268},"list",[213,1071,1072],{"class":222},"(feature_ids))\n",[213,1074,1075,1078,1080,1083,1085,1088,1090],{"class":162,"line":256},[213,1076,1077],{"class":218},"    return",[213,1079,573],{"class":268},[213,1081,1082],{"class":222},"(f.geometry().area() ",[213,1084,444],{"class":218},[213,1086,1087],{"class":222}," f ",[213,1089,450],{"class":218},[213,1091,1092],{"class":222}," layer.getFeatures(request))\n",[213,1094,1095],{"class":162,"line":275},[213,1096,236],{"emptyLinePlaceholder":235},[213,1098,1099],{"class":162,"line":294},[213,1100,236],{"emptyLinePlaceholder":235},[213,1102,1103],{"class":162,"line":308},[213,1104,1105],{"class":539},"# in the console\n",[213,1107,1108,1110],{"class":162,"line":337},[213,1109,383],{"class":268},[213,1111,1112],{"class":222},"(summarise(iface.activeLayer(), iface.activeLayer().selectedFeatureIds()))\n",[213,1114,1115],{"class":162,"line":351},[213,1116,236],{"emptyLinePlaceholder":235},[213,1118,1119],{"class":162,"line":375},[213,1120,1121],{"class":539},"# in a scheduled script\n",[213,1123,1124,1126,1129,1132,1135,1137],{"class":162,"line":380},[213,1125,383],{"class":268},[213,1127,1128],{"class":222},"(summarise(QgsProject.instance().mapLayersByName(",[213,1130,1131],{"class":287},"\"Parcels\"",[213,1133,1134],{"class":222},")[",[213,1136,51],{"class":268},[213,1138,1139],{"class":222},"]))\n",[14,1141,1142,1144,1145,1147,1148,1152,1153,1155],{},[186,1143,391],{}," The function takes what it needs as arguments and never mentions ",[18,1146,197],{},", so the same code serves an interactive experiment and an unattended run. This is the small discipline that saves rewriting a script when somebody asks for it nightly, and it is the same separation described in ",[26,1149,1151],{"href":1150},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-core-gui-analysis-modules-explained\u002F","QGIS Core, GUI and Analysis Modules Explained",". Passing ",[18,1154,399],{}," for the ids meaning \"everything\" keeps the call sites short.",[175,1157,1159],{"id":1158},"qgis-version-compatibility","QGIS version compatibility",[1161,1162,1163,1179],"table",{},[1164,1165,1166],"thead",{},[1167,1168,1169,1173,1176],"tr",{},[1170,1171,1172],"th",{},"QGIS version",[1170,1174,1175],{},"Python",[1170,1177,1178],{},"Notes",[1180,1181,1182,1194,1204,1215],"tbody",{},[1167,1183,1184,1188,1191],{},[1185,1186,1187],"td",{},"3.22 LTR",[1185,1189,1190],{},"3.9",[1185,1192,1193],{},"All calls as described.",[1167,1195,1196,1199,1201],{},[1185,1197,1198],{},"3.28 LTR",[1185,1200,1190],{},[1185,1202,1203],{},"Identical.",[1167,1205,1206,1209,1212],{},[1185,1207,1208],{},"3.34 LTR",[1185,1210,1211],{},"3.12",[1185,1213,1214],{},"Baseline for this page.",[1167,1216,1217,1220,1222],{},[1185,1218,1219],{},"3.40 \u002F 3.44",[1185,1221,1211],{},[1185,1223,1224],{},"Identical; selection behaviour flags gained scoped enumeration names alongside the legacy ones.",[14,1226,1227,1230],{},[18,1228,1229],{},"layer.selectedFeaturesIterator()"," from older examples was removed in the 3.x series; use a feature request filtered by the selected ids, as above.",[175,1232,1234],{"id":1233},"troubleshooting","Troubleshooting",[180,1236,1237,1248,1257,1265,1271,1279],{},[183,1238,1239,1244,1245,1247],{},[186,1240,1241],{},[18,1242,1243],{},"AttributeError: 'NoneType' object has no attribute ..."," No layer is active. Check for ",[18,1246,399],{}," first and say so.",[183,1249,1250,1253,1254,1256],{},[186,1251,1252],{},"The script runs on the wrong layer."," The active layer is the highlighted one, which changes as the user clicks. Print ",[18,1255,326],{}," before acting.",[183,1258,1259,1264],{},[186,1260,1261,1263],{},[18,1262,856],{}," selects nothing."," Quoting: field names take double quotes, string values single. Test the same expression in the layer's filter dialog.",[183,1266,1267,1270],{},[186,1268,1269],{},"The selection looks unchanged on the map."," The canvas has not refreshed, or the selection was set on a different layer object than the one displayed.",[183,1272,1273,1278],{},[186,1274,1275,1277],{},[18,1276,614],{}," is slow."," It builds every feature. Use the ids with a request instead.",[183,1280,1281,1286],{},[186,1282,1283,1285],{},[18,1284,197],{}," is not defined."," You are not in the QGIS console. Pass the layer in explicitly.",[175,1288,1290],{"id":1289},"conclusion","Conclusion",[14,1292,1293,1294,1297,1298,1300],{},"Read the active layer with ",[18,1295,1296],{},"iface.activeLayer()"," and validate it before use — that one habit turns most confusing errors into clear messages. Branch on ",[18,1299,147],{},", treat an empty selection as \"the whole layer\" the way QGIS's own tools do, and prefer selected ids with a feature request over materialising every selected feature. Keep the actual work in a function that takes a layer and some ids, and the console experiment becomes a scheduled job without a rewrite.",[175,1302,1304],{"id":1303},"frequently-asked-questions","Frequently Asked Questions",[14,1306,1307,1310],{},[186,1308,1309],{},"What is the difference between the active layer and a selected layer?","\nThe active layer is the single one QGIS considers current; selected layers are everything highlighted in the panel. Tools acting on one use the first, tools acting on several use the second.",[14,1312,1313,1316],{},[186,1314,1315],{},"Does the selection persist when the project is saved?","\nNo. Selection is transient interface state. Store the feature ids yourself if a workflow needs to resume where it left off.",[14,1318,1319,1322],{},[186,1320,1321],{},"Can I select features on a layer that is not visible?","\nYes. Selection is a property of the layer, independent of its check state or the canvas.",[14,1324,1325,1328,1329,1332,1333,1335,1336,678],{},[186,1326,1327],{},"How do I select features that intersect another layer?","\nRun the ",[18,1330,1331],{},"native:selectbylocation"," algorithm, or build the ids with a spatial index and pass them to ",[18,1334,860],{}," — see ",[26,1337,1339],{"href":1338},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fbuild-spatial-index-pyqgis\u002F","Build a Spatial Index in PyQGIS",[14,1341,1342,1348],{},[186,1343,1344,1345,1347],{},"Why does ",[18,1346,910],{}," do nothing?","\nNothing is selected on that layer, or the layer passed is not the one with the selection. It fails silently by design.",[14,1350,1351,1354,1355,1358],{},[186,1352,1353],{},"How do I react when the user changes the selection?","\nConnect to the layer's ",[18,1356,1357],{},"selectionChanged"," signal, which passes the added and removed feature ids. It is the right hook for a dock widget that shows a live summary of what is selected, and it must be disconnected when the widget closes or the handler outlives the panel.",[14,1360,1361,1364,1365,1368],{},[186,1362,1363],{},"Can I copy the selected features to a new layer?","\nYes — run ",[18,1366,1367],{},"native:saveselectedfeatures"," through Processing, which takes the layer and writes only its selection to the output. Doing it by hand with a memory layer works too, but the algorithm handles the fields, the coordinate system and the geometry type without any of the usual mismatches.",[175,1370,1372],{"id":1371},"related","Related",[180,1374,1375,1380,1386,1390,1394],{},[183,1376,1377,1379],{},[26,1378,29],{"href":28}," — the guide this recipe belongs to",[183,1381,1382],{},[26,1383,1385],{"href":1384},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-and-save-scripts-in-qgis-python-editor\u002F","Run and Save Scripts in the QGIS Python Editor",[183,1387,1388],{},[26,1389,885],{"href":884},[183,1391,1392],{},[26,1393,677],{"href":676},[183,1395,1396],{},[26,1397,1151],{"href":1150},[1399,1400,1401],"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 .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":209,"searchDepth":232,"depth":232,"links":1403},[1404,1405,1406,1407,1408,1409,1410,1411,1412,1413],{"id":177,"depth":232,"text":178},{"id":201,"depth":232,"text":202},{"id":469,"depth":232,"text":470},{"id":775,"depth":232,"text":776},{"id":1012,"depth":232,"text":1013},{"id":1158,"depth":232,"text":1159},{"id":1233,"depth":232,"text":1234},{"id":1289,"depth":232,"text":1290},{"id":1303,"depth":232,"text":1304},{"id":1371,"depth":232,"text":1372},"Reach what the user has selected from the console — the active layer, selected features and layers, safe checks before acting, and how to turn a quick interactive script into one that also runs unattended.","md",{"slug":1417,"type":1418,"breadcrumb":1419,"datePublished":1420,"dateModified":1420},"access-active-layer-and-selection-pyqgis","article","Active Layer & Selection","2026-08-15","\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis",{"title":5,"description":1414},"pyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis\u002Findex","6K-votE7XrxBbkL8w2wtSBY0WUIh3BQdL8BdMHereLg",1786789584628]