[{"data":1,"prerenderedAt":1670},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fadd-features-to-vector-layer-pyqgis":3},{"id":4,"title":5,"body":6,"description":1659,"extension":1660,"meta":1661,"navigation":250,"path":1666,"seo":1667,"stem":1668,"__hash__":1669},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fadd-features-to-vector-layer-pyqgis\u002Findex.md","Add Features to a Vector Layer in PyQGIS",{"type":7,"value":8,"toc":1645},"minimark",[9,13,17,26,178,183,207,211,218,395,418,422,425,534,557,641,645,648,790,823,827,838,1015,1032,1036,1039,1098,1290,1303,1307,1314,1457,1473,1485,1489,1515,1519,1562,1566,1572,1576,1582,1590,1599,1608,1612,1641],[10,11,5],"h1",{"id":12},"add-features-to-a-vector-layer-in-pyqgis",[14,15,16],"p",{},"Adding a feature looks like a one-liner, and in the simplest case it is. In practice the questions start immediately: should the script write through the data provider or the layer's edit buffer? Why did the primary key come back as NULL? Why does the form's default value not appear? Why does adding ten thousand features take a minute? Each of these has a clear answer once you see the two paths a new feature can take into a layer.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002F","Features, Geometries & Memory Layers",". It builds features correctly, chooses between the provider and the edit buffer, applies the layer's default values and constraints, recovers the ids the data source assigns, and keeps bulk inserts fast.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,90,95,103,107,114,119,123,127,132,136,139,142,146,149,153,157,161,164,172],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 300","img","A new feature reaching a data source either directly through the data provider or via the layer's edit buffer with undo and a commit step","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Two paths into a layer",[40,41,42],"desc",{},"A new QgsFeature can reach the data source in two ways. Through layer.dataProvider().addFeatures it is written immediately, with no undo, no edit mode and no signals to the attribute table beyond a reload. Through layer.startEditing, layer.addFeature and commitChanges it is held in the edit buffer, can be undone, fires featureAdded signals, and is written in one transaction on commit. Both end in the same data source.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","300","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"addPathsArrow","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-family:sans-serif;font-weight:bold","#17211d","middle","Provider for scripts, edit buffer for users",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"290","48","180","52","#fffdf7","#59645f","stroke-width:2",[68,86,89],{"x":70,"y":87,"style":88,"fill":73,"textAnchor":74},"67.805","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","new QgsFeature",[68,91,94],{"x":70,"y":92,"style":93,"fill":83,"textAnchor":74},"87.755","text-anchor:middle;font-size:10.5px;font-family:sans-serif","fields + geometry",[96,97],"line",{"x1":98,"y1":99,"x2":100,"y2":101,"stroke":66,"style":102},"330","100","190","134","stroke-width:1.8;marker-end:url(#addPathsArrow)",[96,104],{"x1":105,"y1":99,"x2":106,"y2":101,"stroke":66,"style":102},"430","570",[44,108],{"x":109,"y":110,"width":98,"height":111,"rx":58,"fill":112,"stroke":113,"style":84},"24","138","72","#fdf2e2","#b45309",[68,115,118],{"x":116,"y":117,"style":88,"fill":113,"textAnchor":74},"189","157.83","dataProvider().addFeatures()",[68,120,122],{"x":116,"y":121,"style":93,"fill":83,"textAnchor":74},"177.78","written now · no undo",[68,124,126],{"x":116,"y":125,"style":93,"fill":83,"textAnchor":74},"197.73","no edit mode needed",[44,128],{"x":129,"y":110,"width":98,"height":111,"rx":58,"fill":130,"stroke":131,"style":84},"406","#eff3ff","#2563eb",[68,133,135],{"x":134,"y":117,"style":88,"fill":131,"textAnchor":74},"571","startEditing → addFeature",[68,137,138],{"x":134,"y":121,"style":93,"fill":83,"textAnchor":74},"edit buffer · undo stack",[68,140,141],{"x":134,"y":125,"style":93,"fill":83,"textAnchor":74},"featureAdded signals",[96,143],{"x1":106,"y1":144,"x2":106,"y2":145,"stroke":66,"style":102},"210","228",[44,147],{"x":129,"y":148,"width":98,"height":79,"rx":58,"fill":130,"stroke":131,"style":84},"232",[68,150,152],{"x":134,"y":151,"style":88,"fill":131,"textAnchor":74},"249.805","commitChanges()",[68,154,156],{"x":134,"y":155,"style":93,"fill":83,"textAnchor":74},"269.755","one transaction",[96,158],{"x1":100,"y1":144,"x2":159,"y2":160,"stroke":66,"style":102},"246","240",[96,162],{"x1":129,"y1":163,"x2":70,"y2":163,"stroke":66,"style":102},"256",[44,165],{"x":166,"y":167,"width":168,"height":169,"rx":58,"fill":170,"stroke":171,"style":84},"250","236","130","40","#e8efe6","#15803d",[68,173,177],{"x":174,"y":175,"style":88,"fill":176,"textAnchor":74},"315","259.78","#166534","data source",[179,180,182],"h2",{"id":181},"prerequisites","Prerequisites",[184,185,186,190],"ul",{},[187,188,189],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[187,191,192,193,197,198,202,203,206],{},"A layer to add to: a GeoPackage, a PostGIS table or a ",[21,194,196],{"href":195},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fcreate-memory-layer-pyqgis\u002F","memory layer",". Check ",[199,200,201],"code",{},"layer.dataProvider().capabilities()"," includes ",[199,204,205],{},"AddFeatures","; read-only sources such as CSV files do not.",[179,208,210],{"id":209},"build-a-feature-that-matches-the-layer","Build a feature that matches the layer",[14,212,213,214,217],{},"A ",[199,215,216],{},"QgsFeature"," is a geometry plus an ordered list of attribute values. The order and count must match the layer's fields exactly, which is why a feature should always be created from those fields rather than empty.",[219,220,225],"pre",{"className":221,"code":222,"language":223,"meta":224,"style":224},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsFeature, QgsGeometry, QgsPointXY\n\ntrees = QgsProject.instance().mapLayersByName(\"street_trees\")[0]\nprint(trees.fields().names())   # ['fid', 'species', 'girth_cm', 'planted', 'notes']\n\nf = QgsFeature(trees.fields())\nf[\"species\"] = \"Tilia cordata\"\nf[\"girth_cm\"] = 118\nf[\"planted\"] = \"1987-04-12\"\nf.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(571244.3, 5934019.8)))\n\nprint(f.attributes())           # [NULL, 'Tilia cordata', 118, '1987-04-12', NULL]\nprint(f.isValid(), f.hasGeometry())\n","python","",[199,226,227,245,252,277,290,295,306,323,338,353,371,376,387],{"__ignoreMap":224},[228,229,231,235,239,242],"span",{"class":96,"line":230},1,[228,232,234],{"class":233},"snl16","from",[228,236,238],{"class":237},"s95oV"," qgis.core ",[228,240,241],{"class":233},"import",[228,243,244],{"class":237}," QgsProject, QgsFeature, QgsGeometry, QgsPointXY\n",[228,246,248],{"class":96,"line":247},2,[228,249,251],{"emptyLinePlaceholder":250},true,"\n",[228,253,255,258,261,264,268,271,274],{"class":96,"line":254},3,[228,256,257],{"class":237},"trees ",[228,259,260],{"class":233},"=",[228,262,263],{"class":237}," QgsProject.instance().mapLayersByName(",[228,265,267],{"class":266},"sU2Wk","\"street_trees\"",[228,269,270],{"class":237},")[",[228,272,46],{"class":273},"sDLfK",[228,275,276],{"class":237},"]\n",[228,278,280,283,286],{"class":96,"line":279},4,[228,281,282],{"class":273},"print",[228,284,285],{"class":237},"(trees.fields().names())   ",[228,287,289],{"class":288},"sjoCn","# ['fid', 'species', 'girth_cm', 'planted', 'notes']\n",[228,291,293],{"class":96,"line":292},5,[228,294,251],{"emptyLinePlaceholder":250},[228,296,298,301,303],{"class":96,"line":297},6,[228,299,300],{"class":237},"f ",[228,302,260],{"class":233},[228,304,305],{"class":237}," QgsFeature(trees.fields())\n",[228,307,309,312,315,318,320],{"class":96,"line":308},7,[228,310,311],{"class":237},"f[",[228,313,314],{"class":266},"\"species\"",[228,316,317],{"class":237},"] ",[228,319,260],{"class":233},[228,321,322],{"class":266}," \"Tilia cordata\"\n",[228,324,326,328,331,333,335],{"class":96,"line":325},8,[228,327,311],{"class":237},[228,329,330],{"class":266},"\"girth_cm\"",[228,332,317],{"class":237},[228,334,260],{"class":233},[228,336,337],{"class":273}," 118\n",[228,339,341,343,346,348,350],{"class":96,"line":340},9,[228,342,311],{"class":237},[228,344,345],{"class":266},"\"planted\"",[228,347,317],{"class":237},[228,349,260],{"class":233},[228,351,352],{"class":266}," \"1987-04-12\"\n",[228,354,356,359,362,365,368],{"class":96,"line":355},10,[228,357,358],{"class":237},"f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(",[228,360,361],{"class":273},"571244.3",[228,363,364],{"class":237},", ",[228,366,367],{"class":273},"5934019.8",[228,369,370],{"class":237},")))\n",[228,372,374],{"class":96,"line":373},11,[228,375,251],{"emptyLinePlaceholder":250},[228,377,379,381,384],{"class":96,"line":378},12,[228,380,282],{"class":273},[228,382,383],{"class":237},"(f.attributes())           ",[228,385,386],{"class":288},"# [NULL, 'Tilia cordata', 118, '1987-04-12', NULL]\n",[228,388,390,392],{"class":96,"line":389},13,[228,391,282],{"class":273},[228,393,394],{"class":237},"(f.isValid(), f.hasGeometry())\n",[14,396,397,401,402,405,406,409,410,413,414,417],{},[398,399,400],"strong",{},"Breakdown:"," ",[199,403,404],{},"QgsFeature(fields)"," initialises one NULL slot per field, so assigning by name works and any field you leave alone stays NULL rather than shifting the others along. Setting attributes by name is clearer than ",[199,407,408],{},"setAttributes([...])"," with a positional list, and it survives a field being added to the layer later. The ",[199,411,412],{},"fid"," primary key is left NULL deliberately: GeoPackage and PostGIS assign it on insert. A date can be passed as an ISO string; the provider converts it to the field type, though passing a ",[199,415,416],{},"QDate"," avoids any ambiguity.",[179,419,421],{"id":420},"write-through-the-data-provider","Write through the data provider",[14,423,424],{},"For a script that loads data — an import, a nightly sync, the output of an analysis — writing straight through the provider is the simplest and fastest route. There is no edit session, nothing to commit and nothing to roll back.",[219,426,428],{"className":221,"code":427,"language":223,"meta":224,"style":224},"provider = trees.dataProvider()\nok, added = provider.addFeatures([f])\nif not ok:\n    raise RuntimeError(f\"insert failed: {provider.lastError()}\")\n\nnew_fid = added[0].id()\nprint(\"inserted feature id\", new_fid)\ntrees.updateExtents()\ntrees.triggerRepaint()\n",[199,429,430,440,450,461,493,497,512,524,529],{"__ignoreMap":224},[228,431,432,435,437],{"class":96,"line":230},[228,433,434],{"class":237},"provider ",[228,436,260],{"class":233},[228,438,439],{"class":237}," trees.dataProvider()\n",[228,441,442,445,447],{"class":96,"line":247},[228,443,444],{"class":237},"ok, added ",[228,446,260],{"class":233},[228,448,449],{"class":237}," provider.addFeatures([f])\n",[228,451,452,455,458],{"class":96,"line":254},[228,453,454],{"class":233},"if",[228,456,457],{"class":233}," not",[228,459,460],{"class":237}," ok:\n",[228,462,463,466,469,472,475,478,481,484,487,490],{"class":96,"line":279},[228,464,465],{"class":233},"    raise",[228,467,468],{"class":273}," RuntimeError",[228,470,471],{"class":237},"(",[228,473,474],{"class":233},"f",[228,476,477],{"class":266},"\"insert failed: ",[228,479,480],{"class":273},"{",[228,482,483],{"class":237},"provider.lastError()",[228,485,486],{"class":273},"}",[228,488,489],{"class":266},"\"",[228,491,492],{"class":237},")\n",[228,494,495],{"class":96,"line":292},[228,496,251],{"emptyLinePlaceholder":250},[228,498,499,502,504,507,509],{"class":96,"line":297},[228,500,501],{"class":237},"new_fid ",[228,503,260],{"class":233},[228,505,506],{"class":237}," added[",[228,508,46],{"class":273},[228,510,511],{"class":237},"].id()\n",[228,513,514,516,518,521],{"class":96,"line":308},[228,515,282],{"class":273},[228,517,471],{"class":237},[228,519,520],{"class":266},"\"inserted feature id\"",[228,522,523],{"class":237},", new_fid)\n",[228,525,526],{"class":96,"line":325},[228,527,528],{"class":237},"trees.updateExtents()\n",[228,530,531],{"class":96,"line":340},[228,532,533],{"class":237},"trees.triggerRepaint()\n",[14,535,536,401,538,541,542,544,545,548,549,552,553,556],{},[398,537,400],{},[199,539,540],{},"addFeatures"," returns a pair: a success flag and the list of features as stored, with ids assigned by the data source. That second value is how you learn the new primary key — the ",[199,543,474],{}," you passed in still has its old, invalid id. ",[199,546,547],{},"lastError()"," carries the provider's message, typically a constraint violation from the database. Because the layer itself did not perform the edit, call ",[199,550,551],{},"updateExtents()"," so the cached extent includes the new feature and ",[199,554,555],{},"triggerRepaint()"," so an open map shows it.",[14,558,559],{},[29,560,563,566,569,572,579,582,587,592,597,601,607,613,615,618,621,625,628,632,635,638],{"viewBox":561,"role":32,"ariaLabel":562,"xmlns":34},"0 0 760 230","The returned feature list carries the primary key assigned by the data source, while the original Python feature keeps its old invalid id",[36,564,565],{},"The id you passed is not the id you get",[40,567,568],{},"Before insert, the feature has an invalid id and a NULL fid attribute. The data provider writes it, and the data source assigns a primary key, for example 4812. The returned list from addFeatures carries a copy with id 4812; the original Python object still has the invalid id. Code that needs the new key must read it from the returned features.",[44,570],{"x":46,"y":46,"width":47,"height":571,"fill":49},"230",[51,573,574],{},[54,575,577],{"id":576,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"addIdsArrow",[63,578],{"d":65,"fill":66},[68,580,581],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Read the new key from the return value",[44,583],{"x":109,"y":584,"width":585,"height":586,"rx":58,"fill":82,"stroke":83,"style":84},"70","200","96",[68,588,591],{"x":589,"y":590,"style":88,"fill":73,"textAnchor":74},"124","101.83","your feature",[68,593,596],{"x":589,"y":594,"style":595,"fill":66,"textAnchor":74},"121.78","text-anchor:middle;font-size:10.0px;font-family:monospace","f.id() → -9223…",[68,598,600],{"x":589,"y":599,"style":595,"fill":66,"textAnchor":74},"141.73","fid = NULL",[96,602],{"x1":603,"y1":604,"x2":605,"y2":604,"stroke":66,"style":606},"224","118","280","stroke-width:1.8;marker-end:url(#addIdsArrow)",[44,608],{"x":609,"y":584,"width":610,"height":586,"rx":58,"fill":611,"stroke":612,"style":84},"288","184","#eef7f4","#0f766e",[68,614,177],{"x":70,"y":590,"style":88,"fill":612,"textAnchor":74},[68,616,617],{"x":70,"y":594,"style":93,"fill":66,"textAnchor":74},"assigns key",[68,619,620],{"x":70,"y":599,"style":93,"fill":83,"textAnchor":74},"autoincrement fid",[96,622],{"x1":623,"y1":604,"x2":624,"y2":604,"stroke":66,"style":606},"472","528",[44,626],{"x":627,"y":584,"width":585,"height":586,"rx":58,"fill":170,"stroke":171,"style":84},"536",[68,629,631],{"x":630,"y":590,"style":88,"fill":176,"textAnchor":74},"636","added[0]",[68,633,634],{"x":630,"y":594,"style":595,"fill":66,"textAnchor":74},"id() → 4812",[68,636,637],{"x":630,"y":599,"style":595,"fill":66,"textAnchor":74},"fid = 4812",[68,639,640],{"x":70,"y":585,"style":93,"fill":83,"textAnchor":74},"f itself is unchanged — use the returned copy",[179,642,644],{"id":643},"use-the-edit-buffer-when-edits-must-be-undoable","Use the edit buffer when edits must be undoable",[14,646,647],{},"Inside a plugin or a console session where a person is working, an addition should behave like an addition made with the digitizing tools: visible in the attribute table at once, undoable with Ctrl+Z, and written only when the user saves. That is the layer's edit buffer.",[219,649,651],{"className":221,"code":650,"language":223,"meta":224,"style":224},"from qgis.core import edit\n\nwith edit(trees):\n    for x, y, species in [(571250.0, 5934025.0, \"Acer platanoides\"),\n                          (571262.5, 5934031.2, \"Quercus robur\")]:\n        g = QgsFeature(trees.fields())\n        g[\"species\"] = species\n        g.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))\n        if not trees.addFeature(g):\n            raise RuntimeError(\"addFeature refused the feature\")\n\nprint(trees.featureCount())\n",[199,652,653,664,668,676,706,727,736,750,755,765,779,783],{"__ignoreMap":224},[228,654,655,657,659,661],{"class":96,"line":230},[228,656,234],{"class":233},[228,658,238],{"class":237},[228,660,241],{"class":233},[228,662,663],{"class":237}," edit\n",[228,665,666],{"class":96,"line":247},[228,667,251],{"emptyLinePlaceholder":250},[228,669,670,673],{"class":96,"line":254},[228,671,672],{"class":233},"with",[228,674,675],{"class":237}," edit(trees):\n",[228,677,678,681,684,687,690,693,695,698,700,703],{"class":96,"line":279},[228,679,680],{"class":233},"    for",[228,682,683],{"class":237}," x, y, species ",[228,685,686],{"class":233},"in",[228,688,689],{"class":237}," [(",[228,691,692],{"class":273},"571250.0",[228,694,364],{"class":237},[228,696,697],{"class":273},"5934025.0",[228,699,364],{"class":237},[228,701,702],{"class":266},"\"Acer platanoides\"",[228,704,705],{"class":237},"),\n",[228,707,708,711,714,716,719,721,724],{"class":96,"line":292},[228,709,710],{"class":237},"                          (",[228,712,713],{"class":273},"571262.5",[228,715,364],{"class":237},[228,717,718],{"class":273},"5934031.2",[228,720,364],{"class":237},[228,722,723],{"class":266},"\"Quercus robur\"",[228,725,726],{"class":237},")]:\n",[228,728,729,732,734],{"class":96,"line":297},[228,730,731],{"class":237},"        g ",[228,733,260],{"class":233},[228,735,305],{"class":237},[228,737,738,741,743,745,747],{"class":96,"line":308},[228,739,740],{"class":237},"        g[",[228,742,314],{"class":266},[228,744,317],{"class":237},[228,746,260],{"class":233},[228,748,749],{"class":237}," species\n",[228,751,752],{"class":96,"line":325},[228,753,754],{"class":237},"        g.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))\n",[228,756,757,760,762],{"class":96,"line":340},[228,758,759],{"class":233},"        if",[228,761,457],{"class":233},[228,763,764],{"class":237}," trees.addFeature(g):\n",[228,766,767,770,772,774,777],{"class":96,"line":355},[228,768,769],{"class":233},"            raise",[228,771,468],{"class":273},[228,773,471],{"class":237},[228,775,776],{"class":266},"\"addFeature refused the feature\"",[228,778,492],{"class":237},[228,780,781],{"class":96,"line":373},[228,782,251],{"emptyLinePlaceholder":250},[228,784,785,787],{"class":96,"line":378},[228,786,282],{"class":273},[228,788,789],{"class":237},"(trees.featureCount())\n",[14,791,792,794,795,798,799,802,803,805,806,809,810,813,814,817,818,822],{},[398,793,400],{}," The ",[199,796,797],{},"edit()"," context manager calls ",[199,800,801],{},"startEditing()",", then ",[199,804,152],{}," when the block ends, or ",[199,807,808],{},"rollBack()"," if an exception escapes — so a failure halfway through leaves the data source untouched. Inside the block, new features have temporary negative ids until commit. If you want the user to review and save instead, call ",[199,811,812],{},"trees.startEditing()"," and ",[199,815,816],{},"trees.addFeature(g)"," without committing; the layer stays in edit mode with the additions in its undo stack. ",[21,819,821],{"href":820},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fundo-edits-with-edit-buffer-pyqgis\u002F","Undoing edits with the edit buffer"," covers commit errors and the undo stack in detail.",[179,824,826],{"id":825},"apply-default-values-and-constraints","Apply default values and constraints",[14,828,829,830,833,834,837],{},"Layers can define default values — an expression such as ",[199,831,832],{},"now()"," or ",[199,835,836],{},"@user_account_name",", or a database sequence — and constraints such as \"not null\" or \"unique\". The digitizing tools apply them automatically; a script must ask for them.",[219,839,841],{"className":221,"code":840,"language":223,"meta":224,"style":224},"from qgis.core import QgsVectorLayerUtils, QgsExpressionContextUtils\n\ncontext = trees.createExpressionContext()\nattrs = {trees.fields().indexOf(\"species\"): \"Betula pendula\",\n         trees.fields().indexOf(\"girth_cm\"): 64}\ngeom = QgsGeometry.fromPointXY(QgsPointXY(571270.0, 5934040.0))\n\nnew = QgsVectorLayerUtils.createFeature(trees, geom, attrs, context)\nprint(new.attributes())   # defaults filled in, e.g. planted = today's date\n\nfor i in range(trees.fields().count()):\n    ok, errors = QgsVectorLayerUtils.validateAttribute(trees, new, i)\n    if not ok:\n        print(trees.fields()[i].name(), errors)\n\nwith edit(trees):\n    trees.addFeature(new)\n",[199,842,843,854,858,868,889,904,925,929,939,949,953,969,979,988,997,1002,1009],{"__ignoreMap":224},[228,844,845,847,849,851],{"class":96,"line":230},[228,846,234],{"class":233},[228,848,238],{"class":237},[228,850,241],{"class":233},[228,852,853],{"class":237}," QgsVectorLayerUtils, QgsExpressionContextUtils\n",[228,855,856],{"class":96,"line":247},[228,857,251],{"emptyLinePlaceholder":250},[228,859,860,863,865],{"class":96,"line":254},[228,861,862],{"class":237},"context ",[228,864,260],{"class":233},[228,866,867],{"class":237}," trees.createExpressionContext()\n",[228,869,870,873,875,878,880,883,886],{"class":96,"line":279},[228,871,872],{"class":237},"attrs ",[228,874,260],{"class":233},[228,876,877],{"class":237}," {trees.fields().indexOf(",[228,879,314],{"class":266},[228,881,882],{"class":237},"): ",[228,884,885],{"class":266},"\"Betula pendula\"",[228,887,888],{"class":237},",\n",[228,890,891,894,896,898,901],{"class":96,"line":292},[228,892,893],{"class":237},"         trees.fields().indexOf(",[228,895,330],{"class":266},[228,897,882],{"class":237},[228,899,900],{"class":273},"64",[228,902,903],{"class":237},"}\n",[228,905,906,909,911,914,917,919,922],{"class":96,"line":297},[228,907,908],{"class":237},"geom ",[228,910,260],{"class":233},[228,912,913],{"class":237}," QgsGeometry.fromPointXY(QgsPointXY(",[228,915,916],{"class":273},"571270.0",[228,918,364],{"class":237},[228,920,921],{"class":273},"5934040.0",[228,923,924],{"class":237},"))\n",[228,926,927],{"class":96,"line":308},[228,928,251],{"emptyLinePlaceholder":250},[228,930,931,934,936],{"class":96,"line":325},[228,932,933],{"class":237},"new ",[228,935,260],{"class":233},[228,937,938],{"class":237}," QgsVectorLayerUtils.createFeature(trees, geom, attrs, context)\n",[228,940,941,943,946],{"class":96,"line":340},[228,942,282],{"class":273},[228,944,945],{"class":237},"(new.attributes())   ",[228,947,948],{"class":288},"# defaults filled in, e.g. planted = today's date\n",[228,950,951],{"class":96,"line":355},[228,952,251],{"emptyLinePlaceholder":250},[228,954,955,958,961,963,966],{"class":96,"line":373},[228,956,957],{"class":233},"for",[228,959,960],{"class":237}," i ",[228,962,686],{"class":233},[228,964,965],{"class":273}," range",[228,967,968],{"class":237},"(trees.fields().count()):\n",[228,970,971,974,976],{"class":96,"line":378},[228,972,973],{"class":237},"    ok, errors ",[228,975,260],{"class":233},[228,977,978],{"class":237}," QgsVectorLayerUtils.validateAttribute(trees, new, i)\n",[228,980,981,984,986],{"class":96,"line":389},[228,982,983],{"class":233},"    if",[228,985,457],{"class":233},[228,987,460],{"class":237},[228,989,991,994],{"class":96,"line":990},14,[228,992,993],{"class":273},"        print",[228,995,996],{"class":237},"(trees.fields()[i].name(), errors)\n",[228,998,1000],{"class":96,"line":999},15,[228,1001,251],{"emptyLinePlaceholder":250},[228,1003,1005,1007],{"class":96,"line":1004},16,[228,1006,672],{"class":233},[228,1008,675],{"class":237},[228,1010,1012],{"class":96,"line":1011},17,[228,1013,1014],{"class":237},"    trees.addFeature(new)\n",[14,1016,1017,401,1019,1022,1023,1026,1027,1031],{},[398,1018,400],{},[199,1020,1021],{},"QgsVectorLayerUtils.createFeature"," builds a feature the same way the attribute form does: it evaluates default value expressions, takes provider defaults such as sequences, and then overlays the attributes you pass by field index. ",[199,1024,1025],{},"validateAttribute"," checks each field against its constraints, returning the messages a form would show. Doing this in a script that feeds a carefully configured layer keeps scripted edits consistent with hand-made ones — see ",[21,1028,1030],{"href":1029},"\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fset-default-values-and-field-constraints-pyqgis\u002F","setting default values and field constraints"," for how those rules are defined.",[179,1033,1035],{"id":1034},"keep-bulk-inserts-fast","Keep bulk inserts fast",[14,1037,1038],{},"Adding features one at a time is the main cause of slow scripts. Every provider call is a round trip — a transaction on GeoPackage, a network request to PostGIS — and every edit-buffer addition emits signals the attribute table listens to.",[14,1040,1041],{},[29,1042,1045,1048,1051,1053,1056,1062,1070,1074,1077,1081,1085,1090,1094],{"viewBox":1043,"role":32,"ariaLabel":1044,"xmlns":34},"0 0 760 250","Relative cost of inserting 50,000 features one per call, in batches of 5,000, and in a single call",[36,1046,1047],{},"Batching changes the cost per feature",[40,1049,1050],{},"Bars compare relative time to insert 50,000 points into a GeoPackage. One addFeatures call per feature is slowest, with one transaction each. One call per batch of 5,000 features is far faster. A single call with all features is fastest but holds everything in memory at once. Batches of a few thousand balance memory and speed.",[44,1052],{"x":46,"y":46,"width":47,"height":166,"fill":49},[68,1054,1055],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Same 50,000 points, three ways",[68,1057,1061],{"x":169,"y":1058,"style":1059,"fill":66,"textAnchor":1060},"82","text-anchor:start;font-size:11.0px;font-family:sans-serif","start","one call per feature",[44,1063],{"x":1064,"y":1065,"width":1066,"height":109,"rx":1067,"fill":1068,"stroke":1068,"style":1069},"220","66","500","4","#b91c1c","stroke-width:1.5",[68,1071,1073],{"x":169,"y":1072,"style":1059,"fill":66,"textAnchor":1060},"132","batches of 5,000",[44,1075],{"x":1064,"y":1076,"width":900,"height":109,"rx":1067,"fill":113,"stroke":113,"style":1069},"116",[68,1078,1080],{"x":169,"y":1079,"style":1059,"fill":66,"textAnchor":1060},"182","one call, all features",[44,1082],{"x":1064,"y":1083,"width":1084,"height":109,"rx":1067,"fill":171,"stroke":171,"style":1069},"166","44",[68,1086,1089],{"x":48,"y":1087,"style":1088,"fill":83,"textAnchor":1060},"133","text-anchor:start;font-size:10.5px;font-family:sans-serif","fast, bounded memory",[68,1091,1093],{"x":605,"y":1092,"style":1088,"fill":83,"textAnchor":1060},"183","fastest, all in RAM",[68,1095,1097],{"x":70,"y":145,"style":1096,"fill":83,"textAnchor":74},"text-anchor:middle;font-size:10.0px;font-family:sans-serif","illustrative proportions — measure with your own data source",[219,1099,1101],{"className":221,"code":1100,"language":223,"meta":224,"style":224},"BATCH = 5000\nprovider = trees.dataProvider()\nbatch, total = [], 0\n\nfor rec in read_source_records():          # your generator of dicts\n    feat = QgsFeature(trees.fields())\n    feat[\"species\"] = rec[\"species\"]\n    feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(rec[\"x\"], rec[\"y\"])))\n    batch.append(feat)\n    if len(batch) == BATCH:\n        provider.addFeatures(batch)\n        total += len(batch)\n        batch.clear()\n\nif batch:\n    provider.addFeatures(batch)\n    total += len(batch)\ntrees.updateExtents()\nprint(total, \"features inserted\")\n",[199,1102,1103,1114,1122,1135,1139,1154,1163,1181,1198,1203,1222,1227,1240,1245,1249,1256,1261,1272,1277],{"__ignoreMap":224},[228,1104,1105,1108,1111],{"class":96,"line":230},[228,1106,1107],{"class":273},"BATCH",[228,1109,1110],{"class":233}," =",[228,1112,1113],{"class":273}," 5000\n",[228,1115,1116,1118,1120],{"class":96,"line":247},[228,1117,434],{"class":237},[228,1119,260],{"class":233},[228,1121,439],{"class":237},[228,1123,1124,1127,1129,1132],{"class":96,"line":254},[228,1125,1126],{"class":237},"batch, total ",[228,1128,260],{"class":233},[228,1130,1131],{"class":237}," [], ",[228,1133,1134],{"class":273},"0\n",[228,1136,1137],{"class":96,"line":279},[228,1138,251],{"emptyLinePlaceholder":250},[228,1140,1141,1143,1146,1148,1151],{"class":96,"line":292},[228,1142,957],{"class":233},[228,1144,1145],{"class":237}," rec ",[228,1147,686],{"class":233},[228,1149,1150],{"class":237}," read_source_records():          ",[228,1152,1153],{"class":288},"# your generator of dicts\n",[228,1155,1156,1159,1161],{"class":96,"line":297},[228,1157,1158],{"class":237},"    feat ",[228,1160,260],{"class":233},[228,1162,305],{"class":237},[228,1164,1165,1168,1170,1172,1174,1177,1179],{"class":96,"line":308},[228,1166,1167],{"class":237},"    feat[",[228,1169,314],{"class":266},[228,1171,317],{"class":237},[228,1173,260],{"class":233},[228,1175,1176],{"class":237}," rec[",[228,1178,314],{"class":266},[228,1180,276],{"class":237},[228,1182,1183,1186,1189,1192,1195],{"class":96,"line":325},[228,1184,1185],{"class":237},"    feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(rec[",[228,1187,1188],{"class":266},"\"x\"",[228,1190,1191],{"class":237},"], rec[",[228,1193,1194],{"class":266},"\"y\"",[228,1196,1197],{"class":237},"])))\n",[228,1199,1200],{"class":96,"line":340},[228,1201,1202],{"class":237},"    batch.append(feat)\n",[228,1204,1205,1207,1210,1213,1216,1219],{"class":96,"line":355},[228,1206,983],{"class":233},[228,1208,1209],{"class":273}," len",[228,1211,1212],{"class":237},"(batch) ",[228,1214,1215],{"class":233},"==",[228,1217,1218],{"class":273}," BATCH",[228,1220,1221],{"class":237},":\n",[228,1223,1224],{"class":96,"line":373},[228,1225,1226],{"class":237},"        provider.addFeatures(batch)\n",[228,1228,1229,1232,1235,1237],{"class":96,"line":378},[228,1230,1231],{"class":237},"        total ",[228,1233,1234],{"class":233},"+=",[228,1236,1209],{"class":273},[228,1238,1239],{"class":237},"(batch)\n",[228,1241,1242],{"class":96,"line":389},[228,1243,1244],{"class":237},"        batch.clear()\n",[228,1246,1247],{"class":96,"line":990},[228,1248,251],{"emptyLinePlaceholder":250},[228,1250,1251,1253],{"class":96,"line":999},[228,1252,454],{"class":233},[228,1254,1255],{"class":237}," batch:\n",[228,1257,1258],{"class":96,"line":1004},[228,1259,1260],{"class":237},"    provider.addFeatures(batch)\n",[228,1262,1263,1266,1268,1270],{"class":96,"line":1011},[228,1264,1265],{"class":237},"    total ",[228,1267,1234],{"class":233},[228,1269,1209],{"class":273},[228,1271,1239],{"class":237},[228,1273,1275],{"class":96,"line":1274},18,[228,1276,528],{"class":237},[228,1278,1280,1282,1285,1288],{"class":96,"line":1279},19,[228,1281,282],{"class":273},[228,1283,1284],{"class":237},"(total, ",[228,1286,1287],{"class":266},"\"features inserted\"",[228,1289,492],{"class":237},[14,1291,1292,1294,1295,1297,1298,1302],{},[398,1293,400],{}," Batching bounds memory use — only five thousand features exist at once — while cutting provider calls by the same factor. On GeoPackage each ",[199,1296,540],{}," call is a single SQLite transaction, which is where most of the speed-up comes from. Use the provider rather than the edit buffer for bulk loads: the buffer keeps an undo entry for every feature, which grows large and offers nothing for an automated import. The ",[21,1299,1301],{"href":1300},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fspeed-up-feature-iteration-with-qgsfeaturerequest-pyqgis\u002F","feature-iteration performance guide"," covers the read side of the same problem.",[179,1304,1306],{"id":1305},"copy-features-from-a-layer-with-a-different-schema","Copy features from a layer with a different schema",[14,1308,1309,1310,1313],{},"A frequent variant is appending features from one layer into another whose fields do not match: an inspection export with extra columns going into the master table, or a survey layer whose field order differs from the target. Adding the source features directly would misalign every attribute. ",[199,1311,1312],{},"QgsVectorLayerUtils.makeFeaturesCompatible"," remaps them by field name and converts geometry where it can.",[219,1315,1317],{"className":221,"code":1316,"language":223,"meta":224,"style":224},"from qgis.core import QgsVectorLayerUtils, QgsFeatureRequest\n\nsurvey = QgsProject.instance().mapLayersByName(\"tree_survey_2026\")[0]\nprint(\"survey:\", survey.fields().names())\nprint(\"target:\", trees.fields().names())\n\nsource = list(survey.getFeatures(QgsFeatureRequest().setFilterExpression('\"status\" = \\'new\\'')))\ncompatible = QgsVectorLayerUtils.makeFeaturesCompatible(source, trees)\n\nwith edit(trees):\n    ok = trees.addFeatures(compatible)\nprint(ok, len(compatible), \"survey records appended\")\n",[199,1318,1319,1330,1334,1352,1364,1376,1380,1409,1419,1423,1429,1439],{"__ignoreMap":224},[228,1320,1321,1323,1325,1327],{"class":96,"line":230},[228,1322,234],{"class":233},[228,1324,238],{"class":237},[228,1326,241],{"class":233},[228,1328,1329],{"class":237}," QgsVectorLayerUtils, QgsFeatureRequest\n",[228,1331,1332],{"class":96,"line":247},[228,1333,251],{"emptyLinePlaceholder":250},[228,1335,1336,1339,1341,1343,1346,1348,1350],{"class":96,"line":254},[228,1337,1338],{"class":237},"survey ",[228,1340,260],{"class":233},[228,1342,263],{"class":237},[228,1344,1345],{"class":266},"\"tree_survey_2026\"",[228,1347,270],{"class":237},[228,1349,46],{"class":273},[228,1351,276],{"class":237},[228,1353,1354,1356,1358,1361],{"class":96,"line":279},[228,1355,282],{"class":273},[228,1357,471],{"class":237},[228,1359,1360],{"class":266},"\"survey:\"",[228,1362,1363],{"class":237},", survey.fields().names())\n",[228,1365,1366,1368,1370,1373],{"class":96,"line":292},[228,1367,282],{"class":273},[228,1369,471],{"class":237},[228,1371,1372],{"class":266},"\"target:\"",[228,1374,1375],{"class":237},", trees.fields().names())\n",[228,1377,1378],{"class":96,"line":297},[228,1379,251],{"emptyLinePlaceholder":250},[228,1381,1382,1385,1387,1390,1393,1396,1399,1402,1404,1407],{"class":96,"line":308},[228,1383,1384],{"class":237},"source ",[228,1386,260],{"class":233},[228,1388,1389],{"class":273}," list",[228,1391,1392],{"class":237},"(survey.getFeatures(QgsFeatureRequest().setFilterExpression(",[228,1394,1395],{"class":266},"'\"status\" = ",[228,1397,1398],{"class":273},"\\'",[228,1400,1401],{"class":266},"new",[228,1403,1398],{"class":273},[228,1405,1406],{"class":266},"'",[228,1408,370],{"class":237},[228,1410,1411,1414,1416],{"class":96,"line":325},[228,1412,1413],{"class":237},"compatible ",[228,1415,260],{"class":233},[228,1417,1418],{"class":237}," QgsVectorLayerUtils.makeFeaturesCompatible(source, trees)\n",[228,1420,1421],{"class":96,"line":340},[228,1422,251],{"emptyLinePlaceholder":250},[228,1424,1425,1427],{"class":96,"line":355},[228,1426,672],{"class":233},[228,1428,675],{"class":237},[228,1430,1431,1434,1436],{"class":96,"line":373},[228,1432,1433],{"class":237},"    ok ",[228,1435,260],{"class":233},[228,1437,1438],{"class":237}," trees.addFeatures(compatible)\n",[228,1440,1441,1443,1446,1449,1452,1455],{"class":96,"line":378},[228,1442,282],{"class":273},[228,1444,1445],{"class":237},"(ok, ",[228,1447,1448],{"class":273},"len",[228,1450,1451],{"class":237},"(compatible), ",[228,1453,1454],{"class":266},"\"survey records appended\"",[228,1456,492],{"class":237},[14,1458,1459,401,1461,1464,1465,1468,1469,1472],{},[398,1460,400],{},[199,1462,1463],{},"makeFeaturesCompatible"," builds a new feature for each source feature using the target's fields: attributes with matching names are copied across, fields the target lacks are dropped, and target fields with no source column are left NULL so that defaults or later updates can fill them. It also converts geometry between single and multipart and drops or adds Z and M to match the target layer, which removes the most common reason an append is rejected. Values are still converted by the provider on insert, so a text column holding ",[199,1466,1467],{},"\"12\""," lands in an integer field as 12, but a text column holding ",[199,1470,1471],{},"\"twelve\""," becomes NULL — check counts of NULLs after a first run against an unfamiliar source.",[14,1474,1475,1476,1479,1480,1484],{},"When the target is a database table and both layers already sit in the same database, an ",[199,1477,1478],{},"INSERT … SELECT"," statement is faster still; ",[21,1481,1483],{"href":1482},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fappend-features-to-postgis-table-pyqgis\u002F","appending features to a PostGIS table"," compares the two approaches.",[179,1486,1488],{"id":1487},"qgis-version-compatibility","QGIS version compatibility",[14,1490,1491,1493,1494,1496,1497,1499,1500,1503,1504,1507,1508,1511,1512,1514],{},[199,1492,540],{},", the ",[199,1495,797],{}," context manager and ",[199,1498,1021],{}," are stable from QGIS 3.0 to QGIS 4. On QGIS 4, provider capability flags are reached as ",[199,1501,1502],{},"Qgis.VectorProviderCapability.AddFeatures","; on 3.x, ",[199,1505,1506],{},"QgsVectorDataProvider.AddFeatures",". The ",[199,1509,1510],{},"QgsFeatureSink.FastInsert"," flag, which skips per-feature checks on some providers, can be passed as a second argument to ",[199,1513,540],{}," from 3.4 onwards.",[179,1516,1518],{"id":1517},"troubleshooting","Troubleshooting",[184,1520,1521,1529,1542,1551],{},[187,1522,1523,1528],{},[398,1524,1525,1527],{},[199,1526,540],{}," returns True but nothing appears."," The extent was not updated, the layer has a subset filter that excludes the new features, or the map was not repainted.",[187,1530,1531,1534,1535,1538,1539,1541],{},[398,1532,1533],{},"Attributes are shifted by one."," The feature was built with ",[199,1536,1537],{},"setAttributes"," and a list that omitted the ",[199,1540,412],{}," column.",[187,1543,1544,1547,1548,1550],{},[398,1545,1546],{},"\"Could not add feature\" on PostGIS."," A NOT NULL column without a default was left NULL; read ",[199,1549,483],{},".",[187,1552,1553,1556,1557,1559,1560,1550],{},[398,1554,1555],{},"Edits are lost."," The layer was left in edit mode and QGIS closed without saving; use ",[199,1558,797],{}," or call ",[199,1561,152],{},[179,1563,1565],{"id":1564},"conclusion","Conclusion",[14,1567,1568,1569,1571],{},"Create features from the layer's fields, write through the provider for scripts and through the edit buffer when a person should be able to undo, read new ids from the returned features, use ",[199,1570,1021],{}," to honour defaults and constraints, and add in batches of a few thousand.",[179,1573,1575],{"id":1574},"frequently-asked-questions","Frequently Asked Questions",[14,1577,1578,1581],{},[398,1579,1580],{},"Can I add a feature without geometry?","\nYes, to any layer. The feature appears in the attribute table but not on the map; on a geometryless table it is the only option.",[14,1583,1584,1587,1588,1550],{},[398,1585,1586],{},"Why does my new feature have a negative id?","\nIt is still in the edit buffer. Ids become permanent after ",[199,1589,152],{},[14,1591,1592,1595,1596,1598],{},[398,1593,1594],{},"How do I copy features between layers with different fields?","\nUse ",[199,1597,1312],{},", or map attributes by name into new features built from the target's fields.",[14,1600,1601,1607],{},[398,1602,1603,1604,1606],{},"Does ",[199,1605,540],{}," reproject geometry?","\nNo. Transform the geometry to the layer's CRS first.",[179,1609,1611],{"id":1610},"related","Related",[184,1613,1614,1619,1624,1630,1636],{},[187,1615,1616,1618],{},[21,1617,24],{"href":23}," — the guide this recipe belongs to",[187,1620,1621],{},[21,1622,1623],{"href":820},"Undo Edits with the Edit Buffer in PyQGIS",[187,1625,1626],{},[21,1627,1629],{"href":1628},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis\u002F","Read Feature Attributes and Geometry in PyQGIS",[187,1631,1632],{},[21,1633,1635],{"href":1634},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fedit-features-with-transactions-pyqgis\u002F","Edit Features with Transactions in PyQGIS",[187,1637,1638],{},[21,1639,1640],{"href":1482},"Append Features to a PostGIS Table in PyQGIS",[1642,1643,1644],"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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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":224,"searchDepth":247,"depth":247,"links":1646},[1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658],{"id":181,"depth":247,"text":182},{"id":209,"depth":247,"text":210},{"id":420,"depth":247,"text":421},{"id":643,"depth":247,"text":644},{"id":825,"depth":247,"text":826},{"id":1034,"depth":247,"text":1035},{"id":1305,"depth":247,"text":1306},{"id":1487,"depth":247,"text":1488},{"id":1517,"depth":247,"text":1518},{"id":1564,"depth":247,"text":1565},{"id":1574,"depth":247,"text":1575},{"id":1610,"depth":247,"text":1611},"Create QgsFeature objects with attributes and geometry and add them to a vector layer — through the data provider for scripts or the edit buffer for undoable edits — with defaults, constraints, returned feature ids and bulk performance.","md",{"slug":1662,"type":1663,"breadcrumb":1664,"datePublished":1665,"dateModified":1665},"add-features-to-vector-layer-pyqgis","article","Add Features to a Vector Layer","2026-10-02","\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fadd-features-to-vector-layer-pyqgis",{"title":5,"description":1659},"pyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fadd-features-to-vector-layer-pyqgis\u002Findex","V8ozz2z_1Kb1v0kOoY4FE42C_kNHQFlMkLRuRjhx0j8",1790966252922]