[{"data":1,"prerenderedAt":1452},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis":3},{"id":4,"title":5,"body":6,"description":1445,"extension":1446,"meta":1447,"navigation":118,"path":1448,"seo":1449,"stem":1450,"__hash__":1451},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis\u002Findex.md","Merge Multiple Shapefiles in PyQGIS",{"type":7,"value":8,"toc":1431},"minimark",[9,13,28,35,40,60,70,74,85,268,290,296,402,411,415,425,553,576,580,590,604,607,731,750,758,762,773,870,881,894,898,908,1077,1099,1103,1110,1198,1205,1209,1216,1279,1288,1292,1344,1348,1366,1370,1382,1393,1402,1408,1412,1427],[10,11,5],"h1",{"id":12},"merge-multiple-shapefiles-in-pyqgis",[14,15,16,17,21,22,27],"p",{},"Field surveys, agency downloads, and tiled datasets often arrive as dozens of separate shapefiles that you need as a single layer before you can analyze or publish them. PyQGIS handles this with the ",[18,19,20],"code",{},"native:mergevectorlayers"," Processing algorithm, which stacks any number of input layers into one output, unions their attribute schemas, and can reproject everything to a common CRS in a single pass. This is a core ",[23,24,26],"a",{"href":25},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","Vector Data Manipulation"," task and pairs well with format conversion once the data is combined.",[14,29,30,31,34],{},"This page shows how to discover every shapefile in a folder with ",[18,32,33],{},"pathlib",", merge them safely when their schemas or projections differ, record which source file each feature came from, and write the result to a GeoPackage rather than another fragile shapefile.",[36,37,39],"h2",{"id":38},"prerequisites","Prerequisites",[41,42,43,47,50,53],"ul",{},[44,45,46],"li",{},"QGIS 3.34 LTR (bundled Python 3.12) with Processing available.",[44,48,49],{},"A folder containing the shapefiles to merge — all the same geometry type (all polygons, or all lines, or all points).",[44,51,52],{},"Read access to the source folder and write access to the output location.",[44,54,55,56,59],{},"The QGIS Python Console (",[18,57,58],{},"Plugins > Python Console",").",[14,61,62,64,65,69],{},[18,63,20],{}," only merges layers of a ",[66,67,68],"strong",{},"single"," geometry type per run. Do not mix polygons and lines in one call.",[36,71,73],{"id":72},"discover-the-shapefiles-with-pathlib","Discover the Shapefiles with pathlib",[14,75,76,77,80,81,84],{},"Hard-coding file names does not scale. Use ",[18,78,79],{},"pathlib.Path.glob"," to collect every ",[18,82,83],{},".shp"," in a directory, sorted for deterministic ordering:",[86,87,92],"pre",{"className":88,"code":89,"language":90,"meta":91,"style":91},"language-python shiki shiki-themes github-dark","from pathlib import Path\n\nsource_dir = Path(\"\u002Fdata\u002Ftiles\")\nshapefiles = sorted(source_dir.glob(\"*.shp\"))\n\nprint(f\"Found {len(shapefiles)} shapefiles\")\nfor shp in shapefiles:\n    print(\" -\", shp.name)\n\nif not shapefiles:\n    raise FileNotFoundError(f\"No shapefiles in {source_dir}\")\n","python","",[18,93,94,113,120,139,160,165,194,209,223,228,239],{"__ignoreMap":91},[95,96,99,103,107,110],"span",{"class":97,"line":98},"line",1,[95,100,102],{"class":101},"snl16","from",[95,104,106],{"class":105},"s95oV"," pathlib ",[95,108,109],{"class":101},"import",[95,111,112],{"class":105}," Path\n",[95,114,116],{"class":97,"line":115},2,[95,117,119],{"emptyLinePlaceholder":118},true,"\n",[95,121,123,126,129,132,136],{"class":97,"line":122},3,[95,124,125],{"class":105},"source_dir ",[95,127,128],{"class":101},"=",[95,130,131],{"class":105}," Path(",[95,133,135],{"class":134},"sU2Wk","\"\u002Fdata\u002Ftiles\"",[95,137,138],{"class":105},")\n",[95,140,142,145,147,151,154,157],{"class":97,"line":141},4,[95,143,144],{"class":105},"shapefiles ",[95,146,128],{"class":101},[95,148,150],{"class":149},"sDLfK"," sorted",[95,152,153],{"class":105},"(source_dir.glob(",[95,155,156],{"class":134},"\"*.shp\"",[95,158,159],{"class":105},"))\n",[95,161,163],{"class":97,"line":162},5,[95,164,119],{"emptyLinePlaceholder":118},[95,166,168,171,174,177,180,183,186,189,192],{"class":97,"line":167},6,[95,169,170],{"class":149},"print",[95,172,173],{"class":105},"(",[95,175,176],{"class":101},"f",[95,178,179],{"class":134},"\"Found ",[95,181,182],{"class":149},"{len",[95,184,185],{"class":105},"(shapefiles)",[95,187,188],{"class":149},"}",[95,190,191],{"class":134}," shapefiles\"",[95,193,138],{"class":105},[95,195,197,200,203,206],{"class":97,"line":196},7,[95,198,199],{"class":101},"for",[95,201,202],{"class":105}," shp ",[95,204,205],{"class":101},"in",[95,207,208],{"class":105}," shapefiles:\n",[95,210,212,215,217,220],{"class":97,"line":211},8,[95,213,214],{"class":149},"    print",[95,216,173],{"class":105},[95,218,219],{"class":134},"\" -\"",[95,221,222],{"class":105},", shp.name)\n",[95,224,226],{"class":97,"line":225},9,[95,227,119],{"emptyLinePlaceholder":118},[95,229,231,234,237],{"class":97,"line":230},10,[95,232,233],{"class":101},"if",[95,235,236],{"class":101}," not",[95,238,208],{"class":105},[95,240,242,245,248,250,252,255,258,261,263,266],{"class":97,"line":241},11,[95,243,244],{"class":101},"    raise",[95,246,247],{"class":149}," FileNotFoundError",[95,249,173],{"class":105},[95,251,176],{"class":101},[95,253,254],{"class":134},"\"No shapefiles in ",[95,256,257],{"class":149},"{",[95,259,260],{"class":105},"source_dir",[95,262,188],{"class":149},[95,264,265],{"class":134},"\"",[95,267,138],{"class":105},[14,269,270,273,274,277,278,281,282,285,286,289],{},[66,271,272],{},"Breakdown:"," ",[18,275,276],{},"Path.glob(\"*.shp\")"," returns a generator of ",[18,279,280],{},"Path"," objects for the top-level directory; use ",[18,283,284],{},"rglob(\"*.shp\")"," to recurse into subfolders. Sorting gives a stable feature order across runs, which matters if you later assign sequential IDs. The early ",[18,287,288],{},"FileNotFoundError"," stops a silent no-op merge.",[14,291,292,293,295],{},"Before merging, it is worth confirming every file actually loads, because one corrupt ",[18,294,83],{}," will otherwise abort the whole run:",[86,297,299],{"className":88,"code":298,"language":90,"meta":91,"style":91},"from qgis.core import QgsVectorLayer\n\nlayers = []\nfor shp in shapefiles:\n    layer = QgsVectorLayer(str(shp), shp.stem, \"ogr\")\n    if layer.isValid():\n        layers.append(layer)\n    else:\n        print(f\"Skipping invalid layer: {shp.name}\")\n",[18,300,301,313,317,327,337,358,366,371,379],{"__ignoreMap":91},[95,302,303,305,308,310],{"class":97,"line":98},[95,304,102],{"class":101},[95,306,307],{"class":105}," qgis.core ",[95,309,109],{"class":101},[95,311,312],{"class":105}," QgsVectorLayer\n",[95,314,315],{"class":97,"line":115},[95,316,119],{"emptyLinePlaceholder":118},[95,318,319,322,324],{"class":97,"line":122},[95,320,321],{"class":105},"layers ",[95,323,128],{"class":101},[95,325,326],{"class":105}," []\n",[95,328,329,331,333,335],{"class":97,"line":141},[95,330,199],{"class":101},[95,332,202],{"class":105},[95,334,205],{"class":101},[95,336,208],{"class":105},[95,338,339,342,344,347,350,353,356],{"class":97,"line":162},[95,340,341],{"class":105},"    layer ",[95,343,128],{"class":101},[95,345,346],{"class":105}," QgsVectorLayer(",[95,348,349],{"class":149},"str",[95,351,352],{"class":105},"(shp), shp.stem, ",[95,354,355],{"class":134},"\"ogr\"",[95,357,138],{"class":105},[95,359,360,363],{"class":97,"line":167},[95,361,362],{"class":101},"    if",[95,364,365],{"class":105}," layer.isValid():\n",[95,367,368],{"class":97,"line":196},[95,369,370],{"class":105},"        layers.append(layer)\n",[95,372,373,376],{"class":97,"line":211},[95,374,375],{"class":101},"    else",[95,377,378],{"class":105},":\n",[95,380,381,384,386,388,391,393,396,398,400],{"class":97,"line":225},[95,382,383],{"class":149},"        print",[95,385,173],{"class":105},[95,387,176],{"class":101},[95,389,390],{"class":134},"\"Skipping invalid layer: ",[95,392,257],{"class":149},[95,394,395],{"class":105},"shp.name",[95,397,188],{"class":149},[95,399,265],{"class":134},[95,401,138],{"class":105},[14,403,404,406,407,410],{},[66,405,272],{}," Each shapefile becomes a ",[18,408,409],{},"QgsVectorLayer"," named after the file stem (the name without extension). Validity is checked up front so the merge only receives loadable layers; the skipped ones are reported rather than crashing the job.",[36,412,414],{"id":413},"merge-into-a-single-layer","Merge into a Single Layer",[14,416,417,418,420,421,424],{},"Pass the list of valid layers to ",[18,419,20],{},". Set ",[18,422,423],{},"CRS"," to force a common projection — the algorithm reprojects any layer that does not already match:",[86,426,428],{"className":88,"code":427,"language":90,"meta":91,"style":91},"import processing\nfrom qgis.core import QgsCoordinateReferenceSystem\n\nmerged = processing.run(\"native:mergevectorlayers\", {\n    \"LAYERS\": layers,\n    \"CRS\": QgsCoordinateReferenceSystem(\"EPSG:3857\"),\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nprint(f\"Merged feature count: {merged.featureCount()}\")\nprint(\"Output CRS:\", merged.crs().authid())\n",[18,429,430,437,448,452,468,476,490,504,515,519,541],{"__ignoreMap":91},[95,431,432,434],{"class":97,"line":98},[95,433,109],{"class":101},[95,435,436],{"class":105}," processing\n",[95,438,439,441,443,445],{"class":97,"line":115},[95,440,102],{"class":101},[95,442,307],{"class":105},[95,444,109],{"class":101},[95,446,447],{"class":105}," QgsCoordinateReferenceSystem\n",[95,449,450],{"class":97,"line":122},[95,451,119],{"emptyLinePlaceholder":118},[95,453,454,457,459,462,465],{"class":97,"line":141},[95,455,456],{"class":105},"merged ",[95,458,128],{"class":101},[95,460,461],{"class":105}," processing.run(",[95,463,464],{"class":134},"\"native:mergevectorlayers\"",[95,466,467],{"class":105},", {\n",[95,469,470,473],{"class":97,"line":162},[95,471,472],{"class":134},"    \"LAYERS\"",[95,474,475],{"class":105},": layers,\n",[95,477,478,481,484,487],{"class":97,"line":167},[95,479,480],{"class":134},"    \"CRS\"",[95,482,483],{"class":105},": QgsCoordinateReferenceSystem(",[95,485,486],{"class":134},"\"EPSG:3857\"",[95,488,489],{"class":105},"),\n",[95,491,492,495,498,501],{"class":97,"line":196},[95,493,494],{"class":134},"    \"OUTPUT\"",[95,496,497],{"class":105},": ",[95,499,500],{"class":134},"\"TEMPORARY_OUTPUT\"",[95,502,503],{"class":105},",\n",[95,505,506,509,512],{"class":97,"line":211},[95,507,508],{"class":105},"})[",[95,510,511],{"class":134},"\"OUTPUT\"",[95,513,514],{"class":105},"]\n",[95,516,517],{"class":97,"line":225},[95,518,119],{"emptyLinePlaceholder":118},[95,520,521,523,525,527,530,532,535,537,539],{"class":97,"line":230},[95,522,170],{"class":149},[95,524,173],{"class":105},[95,526,176],{"class":101},[95,528,529],{"class":134},"\"Merged feature count: ",[95,531,257],{"class":149},[95,533,534],{"class":105},"merged.featureCount()",[95,536,188],{"class":149},[95,538,265],{"class":134},[95,540,138],{"class":105},[95,542,543,545,547,550],{"class":97,"line":241},[95,544,170],{"class":149},[95,546,173],{"class":105},[95,548,549],{"class":134},"\"Output CRS:\"",[95,551,552],{"class":105},", merged.crs().authid())\n",[14,554,555,273,557,560,561,563,564,566,567,571,572,575],{},[66,556,272],{},[18,558,559],{},"LAYERS"," accepts a list of ",[18,562,409],{}," objects or file-path strings. The ",[18,565,423],{}," parameter is the destination projection; supplying it guarantees a uniform output even when inputs disagree, which prevents the misalignment described in ",[23,568,570],{"href":569},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","Coordinate Reference Systems",". ",[18,573,574],{},"TEMPORARY_OUTPUT"," keeps the result in memory until you write it.",[36,577,579],{"id":578},"handle-differing-schemas","Handle Differing Schemas",[14,581,582,583,585,586,589],{},"When inputs have different field sets, ",[18,584,20],{}," builds a union of all columns: a feature simply gets ",[18,587,588],{},"NULL"," for fields it did not originally have. It also adds two helper fields automatically:",[41,591,592,598],{},[44,593,594,597],{},[18,595,596],{},"layer"," — the source layer name each feature came from.",[44,599,600,603],{},[18,601,602],{},"path"," — the full source file path.",[14,605,606],{},"These built-in fields are exactly the provenance you usually want, so explicit source tagging is often unnecessary. If you prefer a custom, cleaner source field (for example, just the tile code), add it before merging by editing each input's attributes — or compute it afterward with the field calculator algorithm:",[86,608,610],{"className":88,"code":609,"language":90,"meta":91,"style":91},"import processing\n\ntagged = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": merged,\n    \"FIELD_NAME\": \"source_file\",\n    \"FIELD_TYPE\": 2,            # 2 = string (text)\n    \"FIELD_LENGTH\": 80,\n    \"FORMULA\": \"regexp_substr(\\\"layer\\\", '([^\\\\\\\\\\\\\\\\\u002F]+)$')\",\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n",[18,611,612,618,622,636,644,656,673,685,713,723],{"__ignoreMap":91},[95,613,614,616],{"class":97,"line":98},[95,615,109],{"class":101},[95,617,436],{"class":105},[95,619,620],{"class":97,"line":115},[95,621,119],{"emptyLinePlaceholder":118},[95,623,624,627,629,631,634],{"class":97,"line":122},[95,625,626],{"class":105},"tagged ",[95,628,128],{"class":101},[95,630,461],{"class":105},[95,632,633],{"class":134},"\"native:fieldcalculator\"",[95,635,467],{"class":105},[95,637,638,641],{"class":97,"line":141},[95,639,640],{"class":134},"    \"INPUT\"",[95,642,643],{"class":105},": merged,\n",[95,645,646,649,651,654],{"class":97,"line":162},[95,647,648],{"class":134},"    \"FIELD_NAME\"",[95,650,497],{"class":105},[95,652,653],{"class":134},"\"source_file\"",[95,655,503],{"class":105},[95,657,658,661,663,666,669],{"class":97,"line":167},[95,659,660],{"class":134},"    \"FIELD_TYPE\"",[95,662,497],{"class":105},[95,664,665],{"class":149},"2",[95,667,668],{"class":105},",            ",[95,670,672],{"class":671},"sAwPA","# 2 = string (text)\n",[95,674,675,678,680,683],{"class":97,"line":196},[95,676,677],{"class":134},"    \"FIELD_LENGTH\"",[95,679,497],{"class":105},[95,681,682],{"class":149},"80",[95,684,503],{"class":105},[95,686,687,690,692,695,698,700,702,705,708,711],{"class":97,"line":211},[95,688,689],{"class":134},"    \"FORMULA\"",[95,691,497],{"class":105},[95,693,694],{"class":134},"\"regexp_substr(",[95,696,697],{"class":149},"\\\"",[95,699,596],{"class":134},[95,701,697],{"class":149},[95,703,704],{"class":134},", '([^",[95,706,707],{"class":149},"\\\\\\\\\\\\\\\\",[95,709,710],{"class":134},"\u002F]+)$')\"",[95,712,503],{"class":105},[95,714,715,717,719,721],{"class":97,"line":225},[95,716,494],{"class":134},[95,718,497],{"class":105},[95,720,500],{"class":134},[95,722,503],{"class":105},[95,724,725,727,729],{"class":97,"line":230},[95,726,508],{"class":105},[95,728,511],{"class":134},[95,730,514],{"class":105},[14,732,733,273,735,738,739,742,743,745,746,749],{},[66,734,272],{},[18,736,737],{},"native:fieldcalculator"," derives ",[18,740,741],{},"source_file"," from the auto-generated ",[18,744,596],{}," field, stripping any path so only the file name remains. ",[18,747,748],{},"FIELD_TYPE"," 2 is text; the expression uses a QGIS expression-engine regex. Because the merge already records provenance, this step is purely to present it the way you want.",[14,751,752,753,757],{},"When schemas differ in ",[754,755,756],"em",{},"type"," for the same field name (one file stores a code as text, another as an integer), the merge keeps both as separately typed columns or promotes to text. Standardize column types across inputs beforehand if downstream tools require a single typed column.",[36,759,761],{"id":760},"write-a-geopackage","Write a GeoPackage",[14,763,764,765,768,769,772],{},"Shapefiles truncate field names to 10 characters, cap at 2 GB, and split across multiple sidecar files — all reasons to write the merged result as a GeoPackage instead. Give ",[18,766,767],{},"OUTPUT"," a ",[18,770,771],{},".gpkg"," path:",[86,774,776],{"className":88,"code":775,"language":90,"meta":91,"style":91},"import processing\nfrom qgis.core import QgsCoordinateReferenceSystem\n\nprocessing.run(\"native:mergevectorlayers\", {\n    \"LAYERS\": [str(p) for p in shapefiles],\n    \"CRS\": QgsCoordinateReferenceSystem(\"EPSG:3857\"),\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fmerged_tiles.gpkg\",\n})\n\nprint(\"Wrote \u002Fdata\u002Foutput\u002Fmerged_tiles.gpkg\")\n",[18,777,778,784,794,798,807,829,839,850,855,859],{"__ignoreMap":91},[95,779,780,782],{"class":97,"line":98},[95,781,109],{"class":101},[95,783,436],{"class":105},[95,785,786,788,790,792],{"class":97,"line":115},[95,787,102],{"class":101},[95,789,307],{"class":105},[95,791,109],{"class":101},[95,793,447],{"class":105},[95,795,796],{"class":97,"line":122},[95,797,119],{"emptyLinePlaceholder":118},[95,799,800,803,805],{"class":97,"line":141},[95,801,802],{"class":105},"processing.run(",[95,804,464],{"class":134},[95,806,467],{"class":105},[95,808,809,811,814,816,819,821,824,826],{"class":97,"line":162},[95,810,472],{"class":134},[95,812,813],{"class":105},": [",[95,815,349],{"class":149},[95,817,818],{"class":105},"(p) ",[95,820,199],{"class":101},[95,822,823],{"class":105}," p ",[95,825,205],{"class":101},[95,827,828],{"class":105}," shapefiles],\n",[95,830,831,833,835,837],{"class":97,"line":167},[95,832,480],{"class":134},[95,834,483],{"class":105},[95,836,486],{"class":134},[95,838,489],{"class":105},[95,840,841,843,845,848],{"class":97,"line":196},[95,842,494],{"class":134},[95,844,497],{"class":105},[95,846,847],{"class":134},"\"\u002Fdata\u002Foutput\u002Fmerged_tiles.gpkg\"",[95,849,503],{"class":105},[95,851,852],{"class":97,"line":211},[95,853,854],{"class":105},"})\n",[95,856,857],{"class":97,"line":225},[95,858,119],{"emptyLinePlaceholder":118},[95,860,861,863,865,868],{"class":97,"line":230},[95,862,170],{"class":149},[95,864,173],{"class":105},[95,866,867],{"class":134},"\"Wrote \u002Fdata\u002Foutput\u002Fmerged_tiles.gpkg\"",[95,869,138],{"class":105},[14,871,872,874,875,877,878,880],{},[66,873,272],{}," The ",[18,876,771],{}," extension selects the GeoPackage driver automatically. Passing path strings in ",[18,879,559],{}," lets the algorithm stream from disk, which is lighter on memory for large batches. The output preserves full field names and Unicode, unlike a shapefile destination.",[14,882,883,884,888,889,893],{},"Once merged, converting the layer for web use is a short follow-on step — see ",[23,885,887],{"href":886},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fautomating-shapefile-to-geojson-conversion\u002F","Automating Shapefile to GeoJSON Conversion in QGIS",". To trim the merged layer to a study area afterward, use ",[23,890,892],{"href":891},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fclip-vector-layer-pyqgis\u002F","Clip a Vector Layer in PyQGIS",".",[36,895,897],{"id":896},"filter-inputs-by-geometry-type","Filter Inputs by Geometry Type",[14,899,900,901,903,904,907],{},"Because ",[18,902,20],{}," refuses to mix geometry types, a folder containing both line and polygon shapefiles needs filtering first. Load each candidate, check its ",[18,905,906],{},"geometryType",", and group accordingly:",[86,909,911],{"className":88,"code":910,"language":90,"meta":91,"style":91},"from pathlib import Path\nfrom qgis.core import QgsVectorLayer, QgsWkbTypes\n\nsource_dir = Path(\"\u002Fdata\u002Fmixed\")\nby_type = {}\n\nfor shp in sorted(source_dir.glob(\"*.shp\")):\n    layer = QgsVectorLayer(str(shp), shp.stem, \"ogr\")\n    if not layer.isValid():\n        continue\n    gtype = QgsWkbTypes.geometryDisplayString(layer.geometryType())\n    by_type.setdefault(gtype, []).append(layer)\n\nfor gtype, group in by_type.items():\n    print(f\"{gtype}: {len(group)} layers\")\n",[18,912,913,923,934,938,951,961,965,982,998,1006,1011,1021,1027,1032,1045],{"__ignoreMap":91},[95,914,915,917,919,921],{"class":97,"line":98},[95,916,102],{"class":101},[95,918,106],{"class":105},[95,920,109],{"class":101},[95,922,112],{"class":105},[95,924,925,927,929,931],{"class":97,"line":115},[95,926,102],{"class":101},[95,928,307],{"class":105},[95,930,109],{"class":101},[95,932,933],{"class":105}," QgsVectorLayer, QgsWkbTypes\n",[95,935,936],{"class":97,"line":122},[95,937,119],{"emptyLinePlaceholder":118},[95,939,940,942,944,946,949],{"class":97,"line":141},[95,941,125],{"class":105},[95,943,128],{"class":101},[95,945,131],{"class":105},[95,947,948],{"class":134},"\"\u002Fdata\u002Fmixed\"",[95,950,138],{"class":105},[95,952,953,956,958],{"class":97,"line":162},[95,954,955],{"class":105},"by_type ",[95,957,128],{"class":101},[95,959,960],{"class":105}," {}\n",[95,962,963],{"class":97,"line":167},[95,964,119],{"emptyLinePlaceholder":118},[95,966,967,969,971,973,975,977,979],{"class":97,"line":196},[95,968,199],{"class":101},[95,970,202],{"class":105},[95,972,205],{"class":101},[95,974,150],{"class":149},[95,976,153],{"class":105},[95,978,156],{"class":134},[95,980,981],{"class":105},")):\n",[95,983,984,986,988,990,992,994,996],{"class":97,"line":211},[95,985,341],{"class":105},[95,987,128],{"class":101},[95,989,346],{"class":105},[95,991,349],{"class":149},[95,993,352],{"class":105},[95,995,355],{"class":134},[95,997,138],{"class":105},[95,999,1000,1002,1004],{"class":97,"line":225},[95,1001,362],{"class":101},[95,1003,236],{"class":101},[95,1005,365],{"class":105},[95,1007,1008],{"class":97,"line":230},[95,1009,1010],{"class":101},"        continue\n",[95,1012,1013,1016,1018],{"class":97,"line":241},[95,1014,1015],{"class":105},"    gtype ",[95,1017,128],{"class":101},[95,1019,1020],{"class":105}," QgsWkbTypes.geometryDisplayString(layer.geometryType())\n",[95,1022,1024],{"class":97,"line":1023},12,[95,1025,1026],{"class":105},"    by_type.setdefault(gtype, []).append(layer)\n",[95,1028,1030],{"class":97,"line":1029},13,[95,1031,119],{"emptyLinePlaceholder":118},[95,1033,1035,1037,1040,1042],{"class":97,"line":1034},14,[95,1036,199],{"class":101},[95,1038,1039],{"class":105}," gtype, group ",[95,1041,205],{"class":101},[95,1043,1044],{"class":105}," by_type.items():\n",[95,1046,1048,1050,1052,1054,1056,1058,1061,1063,1065,1067,1070,1072,1075],{"class":97,"line":1047},15,[95,1049,214],{"class":149},[95,1051,173],{"class":105},[95,1053,176],{"class":101},[95,1055,265],{"class":134},[95,1057,257],{"class":149},[95,1059,1060],{"class":105},"gtype",[95,1062,188],{"class":149},[95,1064,497],{"class":134},[95,1066,182],{"class":149},[95,1068,1069],{"class":105},"(group)",[95,1071,188],{"class":149},[95,1073,1074],{"class":134}," layers\"",[95,1076,138],{"class":105},[14,1078,1079,273,1081,1084,1085,1088,1089,1092,1093,1096,1097,893],{},[66,1080,272],{},[18,1082,1083],{},"layer.geometryType()"," returns a ",[18,1086,1087],{},"QgsWkbTypes.GeometryType"," enum (point, line, or polygon), and ",[18,1090,1091],{},"geometryDisplayString"," turns it into a readable key. Grouping into a dict means you can run one merge per geometry type, each producing a clean single-type output. Feed each ",[18,1094,1095],{},"group"," list straight into ",[18,1098,20],{},[36,1100,1102],{"id":1101},"deduplicate-after-merging","Deduplicate After Merging",[14,1104,1105,1106,1109],{},"Tiled datasets frequently overlap at their edges, so the merged layer can contain duplicate features along seams. Remove exact geometry-and-attribute duplicates with ",[18,1107,1108],{},"native:deleteduplicategeometries",":",[86,1111,1113],{"className":88,"code":1112,"language":90,"meta":91,"style":91},"import processing\n\ndeduped = processing.run(\"native:deleteduplicategeometries\", {\n    \"INPUT\": merged,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nprint(f\"Before: {merged.featureCount()}  After: {deduped.featureCount()}\")\n",[18,1114,1115,1121,1125,1139,1145,1155,1163,1167],{"__ignoreMap":91},[95,1116,1117,1119],{"class":97,"line":98},[95,1118,109],{"class":101},[95,1120,436],{"class":105},[95,1122,1123],{"class":97,"line":115},[95,1124,119],{"emptyLinePlaceholder":118},[95,1126,1127,1130,1132,1134,1137],{"class":97,"line":122},[95,1128,1129],{"class":105},"deduped ",[95,1131,128],{"class":101},[95,1133,461],{"class":105},[95,1135,1136],{"class":134},"\"native:deleteduplicategeometries\"",[95,1138,467],{"class":105},[95,1140,1141,1143],{"class":97,"line":141},[95,1142,640],{"class":134},[95,1144,643],{"class":105},[95,1146,1147,1149,1151,1153],{"class":97,"line":162},[95,1148,494],{"class":134},[95,1150,497],{"class":105},[95,1152,500],{"class":134},[95,1154,503],{"class":105},[95,1156,1157,1159,1161],{"class":97,"line":167},[95,1158,508],{"class":105},[95,1160,511],{"class":134},[95,1162,514],{"class":105},[95,1164,1165],{"class":97,"line":196},[95,1166,119],{"emptyLinePlaceholder":118},[95,1168,1169,1171,1173,1175,1178,1180,1182,1184,1187,1189,1192,1194,1196],{"class":97,"line":211},[95,1170,170],{"class":149},[95,1172,173],{"class":105},[95,1174,176],{"class":101},[95,1176,1177],{"class":134},"\"Before: ",[95,1179,257],{"class":149},[95,1181,534],{"class":105},[95,1183,188],{"class":149},[95,1185,1186],{"class":134},"  After: ",[95,1188,257],{"class":149},[95,1190,1191],{"class":105},"deduped.featureCount()",[95,1193,188],{"class":149},[95,1195,265],{"class":134},[95,1197,138],{"class":105},[14,1199,1200,273,1202,1204],{},[66,1201,272],{},[18,1203,1108],{}," drops features whose geometry is identical to one already kept, which cleans up the overlap seams typical of tiled survey exports. Comparing feature counts before and after confirms how many duplicates were removed. Run this after the merge and before writing your final GeoPackage.",[36,1206,1208],{"id":1207},"qgis-version-compatibility","QGIS Version Compatibility",[14,1210,1211,1212,1215],{},"The code targets ",[66,1213,1214],{},"QGIS 3.34 LTR"," (Python 3.12).",[1217,1218,1219,1235],"table",{},[1220,1221,1222],"thead",{},[1223,1224,1225,1229,1232],"tr",{},[1226,1227,1228],"th",{},"QGIS version",[1226,1230,1231],{},"Python",[1226,1233,1234],{},"Notes",[1236,1237,1238,1256,1267],"tbody",{},[1223,1239,1240,1244,1247],{},[1241,1242,1243],"td",{},"3.28 LTR",[1241,1245,1246],{},"3.9",[1241,1248,1249,1250,1252,1253,1255],{},"Same algorithm; ",[18,1251,33],{}," glob unchanged. ",[18,1254,748],{}," codes identical.",[1223,1257,1258,1261,1264],{},[1241,1259,1260],{},"3.34 LTR",[1241,1262,1263],{},"3.12",[1241,1265,1266],{},"Baseline for this page.",[1223,1268,1269,1272,1274],{},[1241,1270,1271],{},"3.40 \u002F 3.44",[1241,1273,1263],{},[1241,1275,1276,1278],{},[18,1277,20],{}," unchanged; better performance on many inputs.",[14,1280,1281,1282,1284,1285,1287],{},"The auto-generated ",[18,1283,596],{}," and ",[18,1286,602],{}," provenance fields have been present since early 3.x, so the merge behavior is stable across every version above.",[36,1289,1291],{"id":1290},"troubleshooting","Troubleshooting",[41,1293,1294,1302,1311,1320,1328],{},[44,1295,1296,1301],{},[66,1297,1298,893],{},[18,1299,1300],{},"Layers do not have the same geometry type"," One file is a different type (e.g. a points file mixed in with polygons). Filter by geometry before merging, or run separate merges per type.",[44,1303,1304,1307,1308,893],{},[66,1305,1306],{},"Merge runs but a file's features are missing."," That layer failed validity and was skipped — check the console for the skip message and inspect the file with ",[18,1309,1310],{},"ogrinfo",[44,1312,1313,1316,1317,1319],{},[66,1314,1315],{},"Field names look truncated or mangled."," You wrote to a shapefile output. Use a ",[18,1318,771],{}," destination to keep full names.",[44,1321,1322,874,1325,1327],{},[66,1323,1324],{},"Everything reprojected unexpectedly.",[18,1326,423],{}," parameter forces a target projection. Set it to the CRS of your inputs if you do not want reprojection.",[44,1329,1330,1335,1336,1339,1340,1343],{},[66,1331,1332,893],{},[18,1333,1334],{},"No shapefiles found"," The glob pattern or path is wrong. Confirm with ",[18,1337,1338],{},"list(Path(source_dir).glob(\"*.shp\"))",", and use ",[18,1341,1342],{},"rglob"," if files sit in subfolders.",[36,1345,1347],{"id":1346},"conclusion","Conclusion",[14,1349,1350,1351,1353,1354,1356,1357,1359,1360,1362,1363,1365],{},"Merging shapefiles in PyQGIS comes down to three moves: glob the folder with ",[18,1352,33],{}," to gather inputs reproducibly, run ",[18,1355,20],{}," with a forced ",[18,1358,423],{}," so projections and schemas reconcile in one pass, and write the union to a GeoPackage to escape shapefile limits. The algorithm's automatic ",[18,1361,596],{},"\u002F",[18,1364,602],{}," fields give you provenance for free, and validity checks keep one bad file from sinking the whole batch — making this pattern dependable from a handful of tiles to hundreds of survey exports.",[36,1367,1369],{"id":1368},"frequently-asked-questions","Frequently Asked Questions",[14,1371,1372,1375,1376,1378,1379,1381],{},[66,1373,1374],{},"Can I merge shapefiles with different attribute columns?","\nYes. ",[18,1377,20],{}," unions all columns and fills missing values with ",[18,1380,588],{},". Conflicting types for the same field name may be kept separately, so standardize types first if a downstream tool needs one typed column.",[14,1383,1384,1387,1388,1284,1390,1392],{},[66,1385,1386],{},"How do I know which file each feature came from?","\nThe algorithm adds ",[18,1389,596],{},[18,1391,602],{}," fields automatically, recording the source layer name and full path for every feature. You can derive a cleaner source code from these with the field calculator.",[14,1394,1395,1398,1399,1401],{},[66,1396,1397],{},"Do all the shapefiles need the same CRS?","\nNo. Set the ",[18,1400,423],{}," parameter to your target projection and the algorithm reprojects any non-matching input during the merge, producing one consistently projected output.",[14,1403,1404,1407],{},[66,1405,1406],{},"Should I output a shapefile or a GeoPackage?","\nPrefer GeoPackage. It avoids the 10-character field-name truncation, the 2 GB size cap, and the multi-file sprawl of shapefiles, and it stores everything in a single portable file.",[36,1409,1411],{"id":1410},"related","Related",[41,1413,1414,1419,1423],{},[44,1415,1416],{},[23,1417,1418],{"href":25},"Vector Data Manipulation in PyQGIS",[44,1420,1421],{},[23,1422,892],{"href":891},[44,1424,1425],{},[23,1426,887],{"href":886},[1428,1429,1430],"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 .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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":91,"searchDepth":115,"depth":115,"links":1432},[1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444],{"id":38,"depth":115,"text":39},{"id":72,"depth":115,"text":73},{"id":413,"depth":115,"text":414},{"id":578,"depth":115,"text":579},{"id":760,"depth":115,"text":761},{"id":896,"depth":115,"text":897},{"id":1101,"depth":115,"text":1102},{"id":1207,"depth":115,"text":1208},{"id":1290,"depth":115,"text":1291},{"id":1346,"depth":115,"text":1347},{"id":1368,"depth":115,"text":1369},{"id":1410,"depth":115,"text":1411},"Merge multiple shapefiles into one layer in PyQGIS. Glob a folder with pathlib, handle mixed schemas and CRS, add a source field, and write a GeoPackage.","md",{},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis",{"title":5,"description":1445},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis\u002Findex","6EXzL2aE0PYdUsY9NUqPae-rRIsRqExDvD-M_Aly1Xo",1781792483477]