[{"data":1,"prerenderedAt":1811},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fread-and-write-layer-metadata-pyqgis":3},{"id":4,"title":5,"body":6,"description":1800,"extension":1801,"meta":1802,"navigation":248,"path":1807,"seo":1808,"stem":1809,"__hash__":1810},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fread-and-write-layer-metadata-pyqgis\u002Findex.md","Read and Write Layer Metadata in PyQGIS",{"type":7,"value":8,"toc":1785},"minimark",[9,13,17,26,185,190,207,211,214,392,413,417,420,663,679,694,698,701,748,758,856,860,863,944,968,972,975,1386,1397,1400,1404,1407,1498,1510,1514,1520,1524,1530,1616,1620,1682,1686,1692,1696,1708,1719,1734,1747,1751,1781],[10,11,5],"h1",{"id":12},"read-and-write-layer-metadata-in-pyqgis",[14,15,16],"p",{},"Metadata is the part of a dataset that answers the questions the attribute table cannot: where did this come from, when, under what licence, and who do I ask when it looks wrong. QGIS has a full metadata model built on the ISO 19115 lineage, and almost nobody fills it in — largely because doing it by hand through a dialog for forty layers is unbearable. From Python it is a loop.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002F","Working with QGIS Projects in PyQGIS",". It covers reading and writing a layer's metadata object, the fields that matter for discovery, saving it as a sidecar or into the data source, validating before publishing, and where project metadata differs from layer metadata.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,100,105,110,114,119,123,128,131,135,139,144,147,150,153,156,161,165,169,171,176,179,182],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 320","img","Where layer metadata can be stored: in the project file, in a sidecar qmd file next to the data, or inside a GeoPackage's metadata table","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three places metadata can live",[40,41,42],"desc",{},"Metadata held only in the project travels with the project and is lost if the data is shared alone. A qmd sidecar sits next to the data file and is picked up automatically when the layer is loaded. A GeoPackage can store the metadata inside the container itself, so a single file carries both the data and its description.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","320","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"mdArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Only two of these survive the data being emailed on",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"20","56","228","212","10","#fdf2e2","#b45309","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"134","84","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","in the project",[44,93],{"x":94,"y":95,"width":96,"height":97,"rx":59,"fill":98,"stroke":84,"style":99},"44","102","180","40","#fffdf7","stroke-width:1.6",[68,101,104],{"x":88,"y":102,"style":103,"fill":66,"textAnchor":74},"127","text-anchor:middle;font-size:9.5px;font-family:monospace","survey.qgz",[68,106,109],{"x":88,"y":107,"style":108,"fill":66,"textAnchor":74},"168","text-anchor:middle;font-size:10.5px;font-family:sans-serif","set in the properties dialog",[68,111,113],{"x":88,"y":112,"style":108,"fill":66,"textAnchor":74},"190","travels with the project",[68,115,118],{"x":88,"y":116,"style":108,"fill":117,"textAnchor":74},"222","#b91c1c","lost if the data alone",[68,120,122],{"x":88,"y":121,"style":108,"fill":117,"textAnchor":74},"240","is shared",[44,124],{"x":125,"y":79,"width":80,"height":81,"rx":82,"fill":126,"stroke":127,"style":85},"266","#edf8e9","#15803d",[68,129,130],{"x":70,"y":89,"style":90,"fill":127,"textAnchor":74},"a .qmd sidecar",[44,132],{"x":133,"y":95,"width":96,"height":97,"rx":59,"fill":134,"stroke":127,"style":99},"290","#e8efe6",[68,136,138],{"x":70,"y":137,"style":103,"fill":66,"textAnchor":74},"120","parcels.gpkg",[68,140,143],{"x":70,"y":141,"style":103,"fill":142,"textAnchor":74},"136","#166534","parcels.qmd",[68,145,146],{"x":70,"y":107,"style":108,"fill":66,"textAnchor":74},"read automatically on load",[68,148,149],{"x":70,"y":112,"style":108,"fill":66,"textAnchor":74},"plain XML, diffable",[68,151,152],{"x":70,"y":116,"style":108,"fill":84,"textAnchor":74},"two files to keep",[68,154,155],{"x":70,"y":121,"style":108,"fill":84,"textAnchor":74},"together",[44,157],{"x":158,"y":79,"width":80,"height":81,"rx":82,"fill":159,"stroke":160,"style":85},"512","#eff3ff","#2563eb",[68,162,164],{"x":163,"y":89,"style":90,"fill":160,"textAnchor":74},"626","inside the GeoPackage",[44,166],{"x":167,"y":95,"width":96,"height":97,"rx":59,"fill":168,"stroke":160,"style":99},"536","#dbeafe",[68,170,138],{"x":163,"y":137,"style":103,"fill":66,"textAnchor":74},[68,172,175],{"x":163,"y":141,"style":173,"fill":174,"textAnchor":74},"text-anchor:middle;font-size:9px;font-family:sans-serif","#1e40af","gpkg_metadata table",[68,177,178],{"x":163,"y":107,"style":108,"fill":66,"textAnchor":74},"one file carries both",[68,180,181],{"x":163,"y":112,"style":108,"fill":66,"textAnchor":74},"nothing to lose",[68,183,184],{"x":163,"y":116,"style":108,"fill":127,"textAnchor":74},"the best default",[186,187,189],"h2",{"id":188},"prerequisites","Prerequisites",[191,192,193,201,204],"ul",{},[194,195,196,200],"li",{},[197,198,199],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[194,202,203],{},"A layer to describe. GeoPackage is assumed for the in-container example; shapefiles support only the sidecar route.",[194,205,206],{},"Write access next to the data if you are producing sidecars.",[186,208,210],{"id":209},"read-what-is-there","Read what is there",[14,212,213],{},"Every layer has a metadata object, empty or not.",[215,216,221],"pre",{"className":217,"code":218,"language":219,"meta":220,"style":220},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nmetadata = layer.metadata()\n\nprint(\"title:   \", metadata.title() or \"(empty)\")\nprint(\"abstract:\", (metadata.abstract() or \"(empty)\")[:80])\nprint(\"licences:\", metadata.licenses())\nprint(\"keywords:\", metadata.keywords())\nprint(\"contacts:\", [c.name for c in metadata.contacts()])\n","python","",[222,223,224,243,250,275,286,291,315,341,354,367],"code",{"__ignoreMap":220},[225,226,229,233,237,240],"span",{"class":227,"line":228},"line",1,[225,230,232],{"class":231},"snl16","from",[225,234,236],{"class":235},"s95oV"," qgis.core ",[225,238,239],{"class":231},"import",[225,241,242],{"class":235}," QgsProject\n",[225,244,246],{"class":227,"line":245},2,[225,247,249],{"emptyLinePlaceholder":248},true,"\n",[225,251,253,256,259,262,266,269,272],{"class":227,"line":252},3,[225,254,255],{"class":235},"layer ",[225,257,258],{"class":231},"=",[225,260,261],{"class":235}," QgsProject.instance().mapLayersByName(",[225,263,265],{"class":264},"sU2Wk","\"parcels\"",[225,267,268],{"class":235},")[",[225,270,46],{"class":271},"sDLfK",[225,273,274],{"class":235},"]\n",[225,276,278,281,283],{"class":227,"line":277},4,[225,279,280],{"class":235},"metadata ",[225,282,258],{"class":231},[225,284,285],{"class":235}," layer.metadata()\n",[225,287,289],{"class":227,"line":288},5,[225,290,249],{"emptyLinePlaceholder":248},[225,292,294,297,300,303,306,309,312],{"class":227,"line":293},6,[225,295,296],{"class":271},"print",[225,298,299],{"class":235},"(",[225,301,302],{"class":264},"\"title:   \"",[225,304,305],{"class":235},", metadata.title() ",[225,307,308],{"class":231},"or",[225,310,311],{"class":264}," \"(empty)\"",[225,313,314],{"class":235},")\n",[225,316,318,320,322,325,328,330,332,335,338],{"class":227,"line":317},7,[225,319,296],{"class":271},[225,321,299],{"class":235},[225,323,324],{"class":264},"\"abstract:\"",[225,326,327],{"class":235},", (metadata.abstract() ",[225,329,308],{"class":231},[225,331,311],{"class":264},[225,333,334],{"class":235},")[:",[225,336,337],{"class":271},"80",[225,339,340],{"class":235},"])\n",[225,342,344,346,348,351],{"class":227,"line":343},8,[225,345,296],{"class":271},[225,347,299],{"class":235},[225,349,350],{"class":264},"\"licences:\"",[225,352,353],{"class":235},", metadata.licenses())\n",[225,355,357,359,361,364],{"class":227,"line":356},9,[225,358,296],{"class":271},[225,360,299],{"class":235},[225,362,363],{"class":264},"\"keywords:\"",[225,365,366],{"class":235},", metadata.keywords())\n",[225,368,370,372,374,377,380,383,386,389],{"class":227,"line":369},10,[225,371,296],{"class":271},[225,373,299],{"class":235},[225,375,376],{"class":264},"\"contacts:\"",[225,378,379],{"class":235},", [c.name ",[225,381,382],{"class":231},"for",[225,384,385],{"class":235}," c ",[225,387,388],{"class":231},"in",[225,390,391],{"class":235}," metadata.contacts()])\n",[14,393,394,397,398,401,402,405,406,409,410,412],{},[197,395,396],{},"Breakdown:"," ",[222,399,400],{},"metadata()"," returns a copy in most versions, so mutating it does nothing until it is set back — a detail that accounts for a great deal of confusion, because the code looks like it worked. ",[222,403,404],{},"keywords()"," returns a dictionary keyed by vocabulary rather than a flat list, which matters when writing them. Every getter returns an empty value rather than ",[222,407,408],{},"None"," for unset fields, so the ",[222,411,308],{}," fallbacks above are for readability rather than safety.",[186,414,416],{"id":415},"write-the-fields-that-matter","Write the fields that matter",[14,418,419],{},"Not every ISO field earns its keep. Six do most of the work for discovery and reuse.",[215,421,423],{"className":217,"code":422,"language":219,"meta":220,"style":220},"from qgis.core import QgsLayerMetadata, QgsAbstractMetadataBase\nfrom qgis.PyQt.QtCore import QDate, QDateTime, QTime\n\nmetadata = layer.metadata()\nmetadata.setTitle(\"Cadastral parcels, Northshire, 2026\")\nmetadata.setAbstract(\n    \"Parcel boundaries digitised from the 2026 survey, snapped to the \"\n    \"national grid. Areas recomputed after edits; see lineage for the \"\n    \"processing chain. Not a legal record of ownership.\"\n)\nmetadata.setLanguage(\"eng\")\nmetadata.setLicenses([\"OGL-UK-3.0\"])\nmetadata.setKeywords({\"gemet\": [\"cadastre\", \"land use\"], \"local\": [\"northshire\"]})\n\ncontact = QgsAbstractMetadataBase.Contact()\ncontact.name = \"Northshire GIS team\"\ncontact.email = \"gis@example.org\"\ncontact.role = \"pointOfContact\"\nmetadata.setContacts([contact])\n\nmetadata.setDateTime(\n    QgsLayerMetadata.Published, QDateTime(QDate(2026, 8, 1), QTime(0, 0))\n)\n\nlayer.setMetadata(metadata)\n",[222,424,425,436,448,452,460,470,475,480,485,490,494,505,516,551,556,567,578,589,600,606,611,617,647,652,657],{"__ignoreMap":220},[225,426,427,429,431,433],{"class":227,"line":228},[225,428,232],{"class":231},[225,430,236],{"class":235},[225,432,239],{"class":231},[225,434,435],{"class":235}," QgsLayerMetadata, QgsAbstractMetadataBase\n",[225,437,438,440,443,445],{"class":227,"line":245},[225,439,232],{"class":231},[225,441,442],{"class":235}," qgis.PyQt.QtCore ",[225,444,239],{"class":231},[225,446,447],{"class":235}," QDate, QDateTime, QTime\n",[225,449,450],{"class":227,"line":252},[225,451,249],{"emptyLinePlaceholder":248},[225,453,454,456,458],{"class":227,"line":277},[225,455,280],{"class":235},[225,457,258],{"class":231},[225,459,285],{"class":235},[225,461,462,465,468],{"class":227,"line":288},[225,463,464],{"class":235},"metadata.setTitle(",[225,466,467],{"class":264},"\"Cadastral parcels, Northshire, 2026\"",[225,469,314],{"class":235},[225,471,472],{"class":227,"line":293},[225,473,474],{"class":235},"metadata.setAbstract(\n",[225,476,477],{"class":227,"line":317},[225,478,479],{"class":264},"    \"Parcel boundaries digitised from the 2026 survey, snapped to the \"\n",[225,481,482],{"class":227,"line":343},[225,483,484],{"class":264},"    \"national grid. Areas recomputed after edits; see lineage for the \"\n",[225,486,487],{"class":227,"line":356},[225,488,489],{"class":264},"    \"processing chain. Not a legal record of ownership.\"\n",[225,491,492],{"class":227,"line":369},[225,493,314],{"class":235},[225,495,497,500,503],{"class":227,"line":496},11,[225,498,499],{"class":235},"metadata.setLanguage(",[225,501,502],{"class":264},"\"eng\"",[225,504,314],{"class":235},[225,506,508,511,514],{"class":227,"line":507},12,[225,509,510],{"class":235},"metadata.setLicenses([",[225,512,513],{"class":264},"\"OGL-UK-3.0\"",[225,515,340],{"class":235},[225,517,519,522,525,528,531,534,537,540,543,545,548],{"class":227,"line":518},13,[225,520,521],{"class":235},"metadata.setKeywords({",[225,523,524],{"class":264},"\"gemet\"",[225,526,527],{"class":235},": [",[225,529,530],{"class":264},"\"cadastre\"",[225,532,533],{"class":235},", ",[225,535,536],{"class":264},"\"land use\"",[225,538,539],{"class":235},"], ",[225,541,542],{"class":264},"\"local\"",[225,544,527],{"class":235},[225,546,547],{"class":264},"\"northshire\"",[225,549,550],{"class":235},"]})\n",[225,552,554],{"class":227,"line":553},14,[225,555,249],{"emptyLinePlaceholder":248},[225,557,559,562,564],{"class":227,"line":558},15,[225,560,561],{"class":235},"contact ",[225,563,258],{"class":231},[225,565,566],{"class":235}," QgsAbstractMetadataBase.Contact()\n",[225,568,570,573,575],{"class":227,"line":569},16,[225,571,572],{"class":235},"contact.name ",[225,574,258],{"class":231},[225,576,577],{"class":264}," \"Northshire GIS team\"\n",[225,579,581,584,586],{"class":227,"line":580},17,[225,582,583],{"class":235},"contact.email ",[225,585,258],{"class":231},[225,587,588],{"class":264}," \"gis@example.org\"\n",[225,590,592,595,597],{"class":227,"line":591},18,[225,593,594],{"class":235},"contact.role ",[225,596,258],{"class":231},[225,598,599],{"class":264}," \"pointOfContact\"\n",[225,601,603],{"class":227,"line":602},19,[225,604,605],{"class":235},"metadata.setContacts([contact])\n",[225,607,609],{"class":227,"line":608},20,[225,610,249],{"emptyLinePlaceholder":248},[225,612,614],{"class":227,"line":613},21,[225,615,616],{"class":235},"metadata.setDateTime(\n",[225,618,620,623,626,628,630,632,635,638,640,642,644],{"class":227,"line":619},22,[225,621,622],{"class":235},"    QgsLayerMetadata.Published, QDateTime(QDate(",[225,624,625],{"class":271},"2026",[225,627,533],{"class":235},[225,629,58],{"class":271},[225,631,533],{"class":235},[225,633,634],{"class":271},"1",[225,636,637],{"class":235},"), QTime(",[225,639,46],{"class":271},[225,641,533],{"class":235},[225,643,46],{"class":271},[225,645,646],{"class":235},"))\n",[225,648,650],{"class":227,"line":649},23,[225,651,314],{"class":235},[225,653,655],{"class":227,"line":654},24,[225,656,249],{"emptyLinePlaceholder":248},[225,658,660],{"class":227,"line":659},25,[225,661,662],{"class":235},"layer.setMetadata(metadata)\n",[14,664,665,397,667,670,671,674,675,678],{},[197,666,396],{},[222,668,669],{},"setMetadata()"," at the end is the line people omit, and without it every preceding statement modified a copy that is then discarded. Keywords take a dictionary of vocabulary to terms — using a recognised vocabulary such as GEMET makes the terms machine-comparable, while a ",[222,672,673],{},"local"," vocabulary is fine for anything organisation-specific. ",[222,676,677],{},"setLicenses()"," takes a list because a dataset can carry more than one, and using an SPDX identifier rather than prose means a catalogue can act on it. The abstract is where the honest caveat belongs: \"not a legal record\" is worth more to a future user than three more keywords.",[14,680,681,682,685,686,689,690,693],{},"The ",[222,683,684],{},"Contact"," type is on ",[222,687,688],{},"QgsAbstractMetadataBase"," rather than on ",[222,691,692],{},"QgsLayerMetadata",", which is a small API wrinkle worth knowing before hunting for it in the wrong class.",[186,695,697],{"id":696},"record-lineage","Record lineage",[14,699,700],{},"Lineage is the field that turns metadata from a label into documentation.",[215,702,704],{"className":217,"code":703,"language":219,"meta":220,"style":220},"metadata.setHistory([\n    \"2026-07-14 imported from survey_2026.gdb (EPSG:27700)\",\n    \"2026-07-15 geometries validated with native:fixgeometries, 12 repaired\",\n    \"2026-07-16 area_ha recomputed from geometry after repair\",\n    \"2026-08-01 published\",\n])\nlayer.setMetadata(metadata)\n",[222,705,706,711,719,726,733,740,744],{"__ignoreMap":220},[225,707,708],{"class":227,"line":228},[225,709,710],{"class":235},"metadata.setHistory([\n",[225,712,713,716],{"class":227,"line":245},[225,714,715],{"class":264},"    \"2026-07-14 imported from survey_2026.gdb (EPSG:27700)\"",[225,717,718],{"class":235},",\n",[225,720,721,724],{"class":227,"line":252},[225,722,723],{"class":264},"    \"2026-07-15 geometries validated with native:fixgeometries, 12 repaired\"",[225,725,718],{"class":235},[225,727,728,731],{"class":227,"line":277},[225,729,730],{"class":264},"    \"2026-07-16 area_ha recomputed from geometry after repair\"",[225,732,718],{"class":235},[225,734,735,738],{"class":227,"line":288},[225,736,737],{"class":264},"    \"2026-08-01 published\"",[225,739,718],{"class":235},[225,741,742],{"class":227,"line":293},[225,743,340],{"class":235},[225,745,746],{"class":227,"line":317},[225,747,662],{"class":235},[14,749,750,752,753,757],{},[197,751,396],{}," A dated line per processing step is enough — this is not a version control system, it is a note to whoever opens the file in three years. The most valuable entries are the ones that explain a discrepancy: twelve repaired geometries is exactly the sort of thing that makes a later area total differ from an earlier report, and one line here saves an afternoon of archaeology. Generating these lines from the script that did the work, rather than typing them, is what makes the practice survive; the ",[21,754,756],{"href":755},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fhandle-processing-feedback-and-errors-pyqgis\u002F","processing feedback object"," already knows what happened.",[14,759,760],{},[29,761,764,767,770,773,776,781,785,790,795,798,802,805,808,812,816,819,822,828,833,837,841,846,850,853],{"viewBox":762,"role":32,"ariaLabel":763,"xmlns":34},"0 0 760 288","A metadata record whose fields map onto the questions a future user asks: what is it, who made it, can I use it, and what happened to it",[36,765,766],{},"Metadata fields as answers to questions",[40,768,769],{},"Title and abstract answer what the dataset is. Contacts answer who to ask. Licence answers whether it can be used. Lineage answers what has been done to it. Keywords make it findable. Everything else in the ISO model is optional detail.",[44,771],{"x":46,"y":46,"width":47,"height":772,"fill":49},"288",[68,774,775],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Five fields answer the questions people actually ask",[44,777],{"x":778,"y":79,"width":779,"height":780,"rx":82,"fill":159,"stroke":160,"style":85},"24","220","90",[68,782,784],{"x":88,"y":89,"style":783,"fill":160,"textAnchor":74},"text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","what is it?",[68,786,789],{"x":88,"y":787,"style":788,"fill":66,"textAnchor":74},"108","text-anchor:middle;font-size:10px;font-family:monospace","title · abstract",[68,791,794],{"x":88,"y":792,"style":793,"fill":66,"textAnchor":74},"130","text-anchor:middle;font-size:10px;font-family:sans-serif","including the caveats",[44,796],{"x":797,"y":79,"width":779,"height":780,"rx":82,"fill":126,"stroke":127,"style":85},"262",[68,799,801],{"x":800,"y":89,"style":783,"fill":127,"textAnchor":74},"372","who do I ask?",[68,803,804],{"x":800,"y":787,"style":788,"fill":66,"textAnchor":74},"contacts",[68,806,807],{"x":800,"y":792,"style":793,"fill":66,"textAnchor":74},"a team, not a person",[44,809],{"x":810,"y":79,"width":811,"height":780,"rx":82,"fill":83,"stroke":84,"style":85},"500","236",[68,813,815],{"x":814,"y":89,"style":783,"fill":84,"textAnchor":74},"618","may I use it?",[68,817,818],{"x":814,"y":787,"style":788,"fill":66,"textAnchor":74},"licenses · rights",[68,820,821],{"x":814,"y":792,"style":793,"fill":66,"textAnchor":74},"an SPDX id, not prose",[44,823],{"x":778,"y":824,"width":825,"height":780,"rx":82,"fill":826,"stroke":827,"style":85},"164","342","#eef7f4","#0f766e",[68,829,832],{"x":830,"y":831,"style":783,"fill":827,"textAnchor":74},"195","192","what happened to it?",[68,834,836],{"x":830,"y":835,"style":788,"fill":66,"textAnchor":74},"216","history — the lineage",[68,838,840],{"x":830,"y":839,"style":793,"fill":66,"textAnchor":74},"238","one dated line per processing step",[44,842],{"x":843,"y":824,"width":844,"height":780,"rx":82,"fill":98,"stroke":845,"style":85},"384","352","#59645f",[68,847,849],{"x":848,"y":831,"style":783,"fill":73,"textAnchor":74},"560","how do I find it?",[68,851,852],{"x":848,"y":835,"style":788,"fill":66,"textAnchor":74},"keywords, by vocabulary",[68,854,855],{"x":848,"y":839,"style":793,"fill":66,"textAnchor":74},"a standard vocabulary beats a clever one",[186,857,859],{"id":858},"save-it-somewhere-it-will-survive","Save it somewhere it will survive",[14,861,862],{},"Setting metadata on a layer object keeps it only in memory and, if the project is saved, in the project. Neither travels with the data.",[215,864,866],{"className":217,"code":865,"language":219,"meta":220,"style":220},"# a sidecar next to the file\nmessage, ok = layer.saveDefaultMetadata()\nif not ok:\n    raise RuntimeError(f\"metadata not saved: {message}\")\n\n# or explicitly, to a chosen path\nlayer.saveNamedMetadata(\"\u002Fdata\u002Fparcels.qmd\")\n",[222,867,868,874,884,895,925,929,934],{"__ignoreMap":220},[225,869,870],{"class":227,"line":228},[225,871,873],{"class":872},"sjoCn","# a sidecar next to the file\n",[225,875,876,879,881],{"class":227,"line":245},[225,877,878],{"class":235},"message, ok ",[225,880,258],{"class":231},[225,882,883],{"class":235}," layer.saveDefaultMetadata()\n",[225,885,886,889,892],{"class":227,"line":252},[225,887,888],{"class":231},"if",[225,890,891],{"class":231}," not",[225,893,894],{"class":235}," ok:\n",[225,896,897,900,903,905,908,911,914,917,920,923],{"class":227,"line":277},[225,898,899],{"class":231},"    raise",[225,901,902],{"class":271}," RuntimeError",[225,904,299],{"class":235},[225,906,907],{"class":231},"f",[225,909,910],{"class":264},"\"metadata not saved: ",[225,912,913],{"class":271},"{",[225,915,916],{"class":235},"message",[225,918,919],{"class":271},"}",[225,921,922],{"class":264},"\"",[225,924,314],{"class":235},[225,926,927],{"class":227,"line":288},[225,928,249],{"emptyLinePlaceholder":248},[225,930,931],{"class":227,"line":293},[225,932,933],{"class":872},"# or explicitly, to a chosen path\n",[225,935,936,939,942],{"class":227,"line":317},[225,937,938],{"class":235},"layer.saveNamedMetadata(",[225,940,941],{"class":264},"\"\u002Fdata\u002Fparcels.qmd\"",[225,943,314],{"class":235},[14,945,946,397,948,951,952,955,956,959,960,963,964,967],{},[197,947,396],{},[222,949,950],{},"saveDefaultMetadata()"," writes to wherever the provider considers default — a ",[222,953,954],{},".qmd"," beside a file-based layer, or the ",[222,957,958],{},"gpkg_metadata"," table inside a GeoPackage, which is the better outcome and happens automatically. It returns the same ",[222,961,962],{},"(message, ok)"," pair as the style functions and needs the same check. Loading is the mirror: ",[222,965,966],{},"loadDefaultMetadata()"," runs automatically when a layer is added, so a sidecar written today is picked up by everyone who opens the file tomorrow with no action on their part.",[186,969,971],{"id":970},"describing-forty-layers-at-once","Describing forty layers at once",[14,973,974],{},"The reason to do any of this from Python is that a project has thirty or forty layers and a dialog does not scale. Splitting the shared fields from the per-layer ones makes the loop short.",[215,976,978],{"className":217,"code":977,"language":219,"meta":220,"style":220},"from qgis.core import QgsProject, QgsAbstractMetadataBase\n\nSHARED = {\n    \"language\": \"eng\",\n    \"licenses\": [\"OGL-UK-3.0\"],\n    \"contact_name\": \"Northshire GIS team\",\n    \"contact_email\": \"gis@example.org\",\n}\n\nPER_LAYER = {\n    \"parcels\":   (\"Cadastral parcels, 2026\", \"Digitised from the 2026 survey.\"),\n    \"buildings\": (\"Building footprints, 2026\", \"Derived from lidar and verified on site.\"),\n    \"roads\":     (\"Adopted highways\", \"Maintained network only; private roads excluded.\"),\n}\n\n\ndef describe_all(project=None):\n    project = project or QgsProject.instance()\n    contact = QgsAbstractMetadataBase.Contact()\n    contact.name = SHARED[\"contact_name\"]\n    contact.email = SHARED[\"contact_email\"]\n    contact.role = \"pointOfContact\"\n\n    missing = []\n    for layer in project.mapLayers().values():\n        entry = PER_LAYER.get(layer.name())\n        if entry is None:\n            missing.append(layer.name())\n            continue\n        title, abstract = entry\n        metadata = layer.metadata()\n        metadata.setTitle(title)\n        metadata.setAbstract(abstract)\n        metadata.setLanguage(SHARED[\"language\"])\n        metadata.setLicenses(SHARED[\"licenses\"])\n        metadata.setContacts([contact])\n        layer.setMetadata(metadata)\n        layer.saveDefaultMetadata()\n    return missing\n",[222,979,980,991,995,1006,1018,1030,1042,1054,1059,1063,1072,1091,1109,1127,1131,1135,1139,1158,1173,1182,1200,1216,1225,1229,1239,1252,1266,1284,1290,1296,1307,1317,1323,1329,1344,1359,1365,1371,1377],{"__ignoreMap":220},[225,981,982,984,986,988],{"class":227,"line":228},[225,983,232],{"class":231},[225,985,236],{"class":235},[225,987,239],{"class":231},[225,989,990],{"class":235}," QgsProject, QgsAbstractMetadataBase\n",[225,992,993],{"class":227,"line":245},[225,994,249],{"emptyLinePlaceholder":248},[225,996,997,1000,1003],{"class":227,"line":252},[225,998,999],{"class":271},"SHARED",[225,1001,1002],{"class":231}," =",[225,1004,1005],{"class":235}," {\n",[225,1007,1008,1011,1014,1016],{"class":227,"line":277},[225,1009,1010],{"class":264},"    \"language\"",[225,1012,1013],{"class":235},": ",[225,1015,502],{"class":264},[225,1017,718],{"class":235},[225,1019,1020,1023,1025,1027],{"class":227,"line":288},[225,1021,1022],{"class":264},"    \"licenses\"",[225,1024,527],{"class":235},[225,1026,513],{"class":264},[225,1028,1029],{"class":235},"],\n",[225,1031,1032,1035,1037,1040],{"class":227,"line":293},[225,1033,1034],{"class":264},"    \"contact_name\"",[225,1036,1013],{"class":235},[225,1038,1039],{"class":264},"\"Northshire GIS team\"",[225,1041,718],{"class":235},[225,1043,1044,1047,1049,1052],{"class":227,"line":317},[225,1045,1046],{"class":264},"    \"contact_email\"",[225,1048,1013],{"class":235},[225,1050,1051],{"class":264},"\"gis@example.org\"",[225,1053,718],{"class":235},[225,1055,1056],{"class":227,"line":343},[225,1057,1058],{"class":235},"}\n",[225,1060,1061],{"class":227,"line":356},[225,1062,249],{"emptyLinePlaceholder":248},[225,1064,1065,1068,1070],{"class":227,"line":369},[225,1066,1067],{"class":271},"PER_LAYER",[225,1069,1002],{"class":231},[225,1071,1005],{"class":235},[225,1073,1074,1077,1080,1083,1085,1088],{"class":227,"line":496},[225,1075,1076],{"class":264},"    \"parcels\"",[225,1078,1079],{"class":235},":   (",[225,1081,1082],{"class":264},"\"Cadastral parcels, 2026\"",[225,1084,533],{"class":235},[225,1086,1087],{"class":264},"\"Digitised from the 2026 survey.\"",[225,1089,1090],{"class":235},"),\n",[225,1092,1093,1096,1099,1102,1104,1107],{"class":227,"line":507},[225,1094,1095],{"class":264},"    \"buildings\"",[225,1097,1098],{"class":235},": (",[225,1100,1101],{"class":264},"\"Building footprints, 2026\"",[225,1103,533],{"class":235},[225,1105,1106],{"class":264},"\"Derived from lidar and verified on site.\"",[225,1108,1090],{"class":235},[225,1110,1111,1114,1117,1120,1122,1125],{"class":227,"line":518},[225,1112,1113],{"class":264},"    \"roads\"",[225,1115,1116],{"class":235},":     (",[225,1118,1119],{"class":264},"\"Adopted highways\"",[225,1121,533],{"class":235},[225,1123,1124],{"class":264},"\"Maintained network only; private roads excluded.\"",[225,1126,1090],{"class":235},[225,1128,1129],{"class":227,"line":553},[225,1130,1058],{"class":235},[225,1132,1133],{"class":227,"line":558},[225,1134,249],{"emptyLinePlaceholder":248},[225,1136,1137],{"class":227,"line":569},[225,1138,249],{"emptyLinePlaceholder":248},[225,1140,1141,1144,1148,1151,1153,1155],{"class":227,"line":580},[225,1142,1143],{"class":231},"def",[225,1145,1147],{"class":1146},"svObZ"," describe_all",[225,1149,1150],{"class":235},"(project",[225,1152,258],{"class":231},[225,1154,408],{"class":271},[225,1156,1157],{"class":235},"):\n",[225,1159,1160,1163,1165,1168,1170],{"class":227,"line":591},[225,1161,1162],{"class":235},"    project ",[225,1164,258],{"class":231},[225,1166,1167],{"class":235}," project ",[225,1169,308],{"class":231},[225,1171,1172],{"class":235}," QgsProject.instance()\n",[225,1174,1175,1178,1180],{"class":227,"line":602},[225,1176,1177],{"class":235},"    contact ",[225,1179,258],{"class":231},[225,1181,566],{"class":235},[225,1183,1184,1187,1189,1192,1195,1198],{"class":227,"line":608},[225,1185,1186],{"class":235},"    contact.name ",[225,1188,258],{"class":231},[225,1190,1191],{"class":271}," SHARED",[225,1193,1194],{"class":235},"[",[225,1196,1197],{"class":264},"\"contact_name\"",[225,1199,274],{"class":235},[225,1201,1202,1205,1207,1209,1211,1214],{"class":227,"line":613},[225,1203,1204],{"class":235},"    contact.email ",[225,1206,258],{"class":231},[225,1208,1191],{"class":271},[225,1210,1194],{"class":235},[225,1212,1213],{"class":264},"\"contact_email\"",[225,1215,274],{"class":235},[225,1217,1218,1221,1223],{"class":227,"line":619},[225,1219,1220],{"class":235},"    contact.role ",[225,1222,258],{"class":231},[225,1224,599],{"class":264},[225,1226,1227],{"class":227,"line":649},[225,1228,249],{"emptyLinePlaceholder":248},[225,1230,1231,1234,1236],{"class":227,"line":654},[225,1232,1233],{"class":235},"    missing ",[225,1235,258],{"class":231},[225,1237,1238],{"class":235}," []\n",[225,1240,1241,1244,1247,1249],{"class":227,"line":659},[225,1242,1243],{"class":231},"    for",[225,1245,1246],{"class":235}," layer ",[225,1248,388],{"class":231},[225,1250,1251],{"class":235}," project.mapLayers().values():\n",[225,1253,1255,1258,1260,1263],{"class":227,"line":1254},26,[225,1256,1257],{"class":235},"        entry ",[225,1259,258],{"class":231},[225,1261,1262],{"class":271}," PER_LAYER",[225,1264,1265],{"class":235},".get(layer.name())\n",[225,1267,1269,1272,1275,1278,1281],{"class":227,"line":1268},27,[225,1270,1271],{"class":231},"        if",[225,1273,1274],{"class":235}," entry ",[225,1276,1277],{"class":231},"is",[225,1279,1280],{"class":271}," None",[225,1282,1283],{"class":235},":\n",[225,1285,1287],{"class":227,"line":1286},28,[225,1288,1289],{"class":235},"            missing.append(layer.name())\n",[225,1291,1293],{"class":227,"line":1292},29,[225,1294,1295],{"class":231},"            continue\n",[225,1297,1299,1302,1304],{"class":227,"line":1298},30,[225,1300,1301],{"class":235},"        title, abstract ",[225,1303,258],{"class":231},[225,1305,1306],{"class":235}," entry\n",[225,1308,1310,1313,1315],{"class":227,"line":1309},31,[225,1311,1312],{"class":235},"        metadata ",[225,1314,258],{"class":231},[225,1316,285],{"class":235},[225,1318,1320],{"class":227,"line":1319},32,[225,1321,1322],{"class":235},"        metadata.setTitle(title)\n",[225,1324,1326],{"class":227,"line":1325},33,[225,1327,1328],{"class":235},"        metadata.setAbstract(abstract)\n",[225,1330,1332,1335,1337,1339,1342],{"class":227,"line":1331},34,[225,1333,1334],{"class":235},"        metadata.setLanguage(",[225,1336,999],{"class":271},[225,1338,1194],{"class":235},[225,1340,1341],{"class":264},"\"language\"",[225,1343,340],{"class":235},[225,1345,1347,1350,1352,1354,1357],{"class":227,"line":1346},35,[225,1348,1349],{"class":235},"        metadata.setLicenses(",[225,1351,999],{"class":271},[225,1353,1194],{"class":235},[225,1355,1356],{"class":264},"\"licenses\"",[225,1358,340],{"class":235},[225,1360,1362],{"class":227,"line":1361},36,[225,1363,1364],{"class":235},"        metadata.setContacts([contact])\n",[225,1366,1368],{"class":227,"line":1367},37,[225,1369,1370],{"class":235},"        layer.setMetadata(metadata)\n",[225,1372,1374],{"class":227,"line":1373},38,[225,1375,1376],{"class":235},"        layer.saveDefaultMetadata()\n",[225,1378,1380,1383],{"class":227,"line":1379},39,[225,1381,1382],{"class":231},"    return",[225,1384,1385],{"class":235}," missing\n",[14,1387,1388,1390,1391,1393,1394,1396],{},[197,1389,396],{}," Returning the undescribed layer names rather than silently skipping them is what stops the table drifting out of step with the project — a layer added last month appears in the return value and gets noticed. Building the contact once outside the loop is not merely tidier: each ",[222,1392,684],{}," is a value object, and constructing forty identical ones is pure waste. Keeping ",[222,1395,1067],{}," as a literal in a module that lives in version control means a change to an abstract is a reviewable diff rather than an untracked click in a dialog.",[14,1398,1399],{},"Running this as part of a publishing script, immediately before the data is copied out, is the arrangement that keeps metadata accurate — it is regenerated from the source of truth every time rather than maintained by hand and gradually diverging.",[186,1401,1403],{"id":1402},"validate-before-publishing","Validate before publishing",[14,1405,1406],{},"An incomplete record is worse than an obviously absent one, because it looks like somebody checked.",[215,1408,1410],{"className":217,"code":1409,"language":219,"meta":220,"style":220},"from qgis.core import QgsNativeMetadataValidator\n\nvalidator = QgsNativeMetadataValidator()\nok, results = validator.validate(layer.metadata())\nif not ok:\n    for result in results:\n        print(f\"{result.section}: {result.note}\")\n",[222,1411,1412,1423,1427,1437,1447,1455,1467],{"__ignoreMap":220},[225,1413,1414,1416,1418,1420],{"class":227,"line":228},[225,1415,232],{"class":231},[225,1417,236],{"class":235},[225,1419,239],{"class":231},[225,1421,1422],{"class":235}," QgsNativeMetadataValidator\n",[225,1424,1425],{"class":227,"line":245},[225,1426,249],{"emptyLinePlaceholder":248},[225,1428,1429,1432,1434],{"class":227,"line":252},[225,1430,1431],{"class":235},"validator ",[225,1433,258],{"class":231},[225,1435,1436],{"class":235}," QgsNativeMetadataValidator()\n",[225,1438,1439,1442,1444],{"class":227,"line":277},[225,1440,1441],{"class":235},"ok, results ",[225,1443,258],{"class":231},[225,1445,1446],{"class":235}," validator.validate(layer.metadata())\n",[225,1448,1449,1451,1453],{"class":227,"line":288},[225,1450,888],{"class":231},[225,1452,891],{"class":231},[225,1454,894],{"class":235},[225,1456,1457,1459,1462,1464],{"class":227,"line":293},[225,1458,1243],{"class":231},[225,1460,1461],{"class":235}," result ",[225,1463,388],{"class":231},[225,1465,1466],{"class":235}," results:\n",[225,1468,1469,1472,1474,1476,1478,1480,1483,1485,1487,1489,1492,1494,1496],{"class":227,"line":317},[225,1470,1471],{"class":271},"        print",[225,1473,299],{"class":235},[225,1475,907],{"class":231},[225,1477,922],{"class":264},[225,1479,913],{"class":271},[225,1481,1482],{"class":235},"result.section",[225,1484,919],{"class":271},[225,1486,1013],{"class":264},[225,1488,913],{"class":271},[225,1490,1491],{"class":235},"result.note",[225,1493,919],{"class":271},[225,1495,922],{"class":264},[225,1497,314],{"class":235},[14,1499,1500,397,1502,1505,1506,1509],{},[197,1501,396],{},[222,1503,1504],{},"QgsNativeMetadataValidator"," checks QGIS's own required set — identifier, title, language, type, CRS, extent, contacts and licences. Each failure names the section and explains what is missing, which makes it usable as a pre-publication gate in a script. There is also ",[222,1507,1508],{},"QgsLayerMetadataValidator"," subclasses for stricter profiles; running the native one and treating its output as a checklist is enough for most internal publishing.",[186,1511,1513],{"id":1512},"project-metadata-is-a-different-object","Project metadata is a different object",[14,1515,1516,1519],{},[222,1517,1518],{},"QgsProject.instance().metadata()"," exists too, and describes the project rather than any layer. It shares most of the same fields through the common base class, so the code above transfers with the constructor swapped. What it does not have is a CRS or an extent, since a project has no single one — and what it does not do is propagate to the layers, so a project described carefully still ships layers with nothing on them. Both are worth filling in, and only the layer-level record survives the data being shared on its own.",[186,1521,1523],{"id":1522},"qgis-version-compatibility","QGIS version compatibility",[14,1525,1526,1527,1529],{},"The examples target ",[197,1528,199],{}," (Python 3.12).",[1531,1532,1533,1549],"table",{},[1534,1535,1536],"thead",{},[1537,1538,1539,1543,1546],"tr",{},[1540,1541,1542],"th",{},"QGIS version",[1540,1544,1545],{},"Python",[1540,1547,1548],{},"Notes",[1550,1551,1552,1569,1583,1595,1606],"tbody",{},[1537,1553,1554,1558,1561],{},[1555,1556,1557],"td",{},"3.16 LTR",[1555,1559,1560],{},"3.7",[1555,1562,1563,1565,1566,1568],{},[222,1564,692],{}," and ",[222,1567,954],{}," sidecars present.",[1537,1570,1571,1574,1577],{},[1555,1572,1573],{},"3.22 LTR",[1555,1575,1576],{},"3.9",[1555,1578,1579,1580,1582],{},"GeoPackage ",[222,1581,958],{}," writing supported by the OGR provider.",[1537,1584,1585,1588,1590],{},[1555,1586,1587],{},"3.28 LTR",[1555,1589,1576],{},[1555,1591,1592,1594],{},[222,1593,1504],{}," stable.",[1537,1596,1597,1600,1603],{},[1555,1598,1599],{},"3.34 LTR",[1555,1601,1602],{},"3.12",[1555,1604,1605],{},"Baseline for this page.",[1537,1607,1608,1611,1613],{},[1555,1609,1610],{},"3.40+",[1555,1612,1602],{},[1555,1614,1615],{},"Layer metadata search and a metadata catalogue panel added.",[186,1617,1619],{"id":1618},"troubleshooting","Troubleshooting",[191,1621,1622,1634,1643,1652,1666,1676],{},[194,1623,1624,397,1627,1630,1631,1633],{},[197,1625,1626],{},"Changes disappear.",[222,1628,1629],{},"layer.setMetadata(metadata)"," was never called — ",[222,1632,400],{}," returned a copy.",[194,1635,1636,1639,1640,1642],{},[197,1637,1638],{},"The sidecar is not picked up."," It must sit next to the data with the matching base name and a ",[222,1641,954],{}," extension.",[194,1644,1645,397,1648,1651],{},[197,1646,1647],{},"Keywords are rejected.",[222,1649,1650],{},"setKeywords()"," takes a dictionary of vocabulary to list, not a flat list.",[194,1653,1654,1659,1660,1662,1663,1665],{},[197,1655,1656,1658],{},[222,1657,684],{}," cannot be imported."," It lives on ",[222,1661,688],{},", not on ",[222,1664,692],{},".",[194,1667,1668,1671,1672,1675],{},[197,1669,1670],{},"Metadata vanished after a rewrite."," Writing a new file with ",[222,1673,1674],{},"QgsVectorFileWriter"," does not carry metadata across. Re-apply and re-save it.",[194,1677,1678,1681],{},[197,1679,1680],{},"A shapefile will not store it internally."," It cannot; only the sidecar route works. Convert to GeoPackage.",[186,1683,1685],{"id":1684},"conclusion","Conclusion",[14,1687,1688,1689,1691],{},"Fill in title, abstract, contacts, licence and lineage — the five fields that answer real questions — set the object back onto the layer, and save it with ",[222,1690,950],{}," so it travels with the data. Validate before publishing, and generate the lineage lines from the script that did the processing rather than typing them afterwards.",[186,1693,1695],{"id":1694},"frequently-asked-questions","Frequently Asked Questions",[14,1697,1698,1701,1702,1704,1705,1707],{},[197,1699,1700],{},"Can I export to ISO 19115 XML?","\nQGIS writes its own ",[222,1703,954],{}," format, which maps onto the ISO model but is not ISO XML. Catalogue software such as GeoNetwork ingests ",[222,1706,954],{}," via converters, or you can write ISO XML yourself from the same fields.",[14,1709,1710,1713,1714,1718],{},[197,1711,1712],{},"Does metadata affect how the layer draws?","\nNo. It is purely descriptive and separate from ",[21,1715,1717],{"href":1716},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis\u002F","styles",", which are stored differently and loaded independently.",[14,1720,1721,1724,1725,1565,1727,1729,1730,1733],{},[197,1722,1723],{},"Can I set metadata on a raster?","\nYes — the API is identical, since ",[222,1726,400],{},[222,1728,669],{}," are on ",[222,1731,1732],{},"QgsMapLayer",". It is particularly valuable on derived rasters, where the processing parameters belong in the lineage.",[14,1735,1736,1739,1740,1743,1744,1746],{},[197,1737,1738],{},"How do I do this for every layer in a project?","\nIterate ",[222,1741,1742],{},"QgsProject.instance().mapLayers().values()",", apply the shared fields and any per-layer ones from a lookup table, then ",[222,1745,950],{}," on each. That loop is the whole reason to do this from Python.",[186,1748,1750],{"id":1749},"related","Related",[191,1752,1753,1758,1764,1770,1776],{},[194,1754,1755,1757],{},[21,1756,24],{"href":23}," — the guide this recipe belongs to",[194,1759,1760],{},[21,1761,1763],{"href":1762},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fuse-project-variables-and-metadata-pyqgis\u002F","Use Project Variables and Metadata in PyQGIS",[194,1765,1766],{},[21,1767,1769],{"href":1768},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fsave-and-load-qgis-project-pyqgis\u002F","Save and Load a QGIS Project in PyQGIS",[194,1771,1772],{},[21,1773,1775],{"href":1774},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fwrite-vector-layer-to-geopackage-pyqgis\u002F","Write a Vector Layer to a GeoPackage in PyQGIS",[194,1777,1778],{},[21,1779,1780],{"href":1716},"Save and Load a QML Style in PyQGIS",[1782,1783,1784],"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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":220,"searchDepth":245,"depth":245,"links":1786},[1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799],{"id":188,"depth":245,"text":189},{"id":209,"depth":245,"text":210},{"id":415,"depth":245,"text":416},{"id":696,"depth":245,"text":697},{"id":858,"depth":245,"text":859},{"id":970,"depth":245,"text":971},{"id":1402,"depth":245,"text":1403},{"id":1512,"depth":245,"text":1513},{"id":1522,"depth":245,"text":1523},{"id":1618,"depth":245,"text":1619},{"id":1684,"depth":245,"text":1685},{"id":1694,"depth":245,"text":1695},{"id":1749,"depth":245,"text":1750},"Populate QgsLayerMetadata from Python — title, abstract, keywords, licence, contacts and lineage — save it as a .qmd sidecar or into a GeoPackage, and validate it before publishing.","md",{"slug":1803,"type":1804,"breadcrumb":1805,"datePublished":1806,"dateModified":1806},"read-and-write-layer-metadata-pyqgis","article","Layer Metadata","2026-08-27","\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fread-and-write-layer-metadata-pyqgis",{"title":5,"description":1800},"pyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fread-and-write-layer-metadata-pyqgis\u002Findex","-a1H4NiwXlVzsQIJlSLyj5PWXoAteaBqRZzzaL2WyaY",1787823360569]