[{"data":1,"prerenderedAt":1511},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis":3},{"id":4,"title":5,"body":6,"description":1500,"extension":1501,"meta":1502,"navigation":241,"path":1507,"seo":1508,"stem":1509,"__hash__":1510},"docs\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis\u002Findex.md","Save and Load a QML Style in PyQGIS",{"type":7,"value":8,"toc":1487},"minimark",[9,13,17,26,172,177,204,208,378,392,399,413,426,430,433,519,536,570,589,593,596,666,683,779,783,786,842,858,862,865,1160,1165,1172,1259,1263,1269,1334,1337,1341,1389,1393,1414,1418,1424,1430,1436,1442,1452,1456,1483],[10,11,5],"h1",{"id":12},"save-and-load-a-qml-style-in-pyqgis",[14,15,16],"p",{},"A style built in code is worth keeping. Once a renderer, its class breaks, its labels and its opacity have been tuned, saving that as a QML turns a script into a reusable asset: the next dataset gets the same cartography in one line, and the colleague who opens the GeoPackage sees a finished map instead of grey polygons.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002F","Programmatic Layer Styling in PyQGIS",". It covers saving and loading QML files, storing styles inside a GeoPackage or PostGIS so they travel with the data, copying a style directly between layers in memory, and applying only part of a style.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,90,95,105,110,115,119,123,128,131,134,137,140,145,149,152,155,158,166,168],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 272","img","Three places a layer style can live: a QML file beside the data, a style table inside the container, and copied directly between layers in memory","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three homes for a layer style",[40,41,42],"desc",{},"A style can be written to a QML file next to the data, which is portable but easy to lose. It can be stored in a style table inside a GeoPackage or PostGIS database, where it travels with the data and can be the default. Or it can be cloned directly from one loaded layer to another in memory, which persists nothing but is instant.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","272","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"qmlArrow","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","Where the style lives decides who else sees it",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"284","48","192","46","#eef7f4","#0f766e","stroke-width:2.5",[68,86,89],{"x":70,"y":87,"style":88,"fill":83,"textAnchor":74},"70","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","layer.renderer()",[68,91,94],{"x":70,"y":92,"style":93,"fill":66,"textAnchor":74},"87","text-anchor:middle;font-size:10px;font-family:sans-serif","in memory, right now",[44,96],{"x":97,"y":98,"width":99,"height":100,"rx":101,"fill":102,"stroke":103,"style":104},"16","140","228","108","10","#fdf2e2","#b45309","stroke-width:2",[68,106,109],{"x":107,"y":108,"style":88,"fill":103,"textAnchor":74},"130","166","a .qml file",[68,111,114],{"x":107,"y":112,"style":113,"fill":66,"textAnchor":74},"190","text-anchor:middle;font-size:11px;font-family:sans-serif","saveNamedStyle(path)",[68,116,118],{"x":107,"y":117,"style":113,"fill":66,"textAnchor":74},"212","version controllable",[68,120,122],{"x":107,"y":121,"style":113,"fill":103,"textAnchor":74},"234","separate from the data",[44,124],{"x":125,"y":98,"width":99,"height":100,"rx":101,"fill":126,"stroke":127,"style":84},"266","#edf8e9","#15803d",[68,129,130],{"x":70,"y":108,"style":88,"fill":127,"textAnchor":74},"inside the container",[68,132,133],{"x":70,"y":112,"style":113,"fill":66,"textAnchor":74},"saveStyleToDatabase()",[68,135,136],{"x":70,"y":117,"style":113,"fill":66,"textAnchor":74},"applied automatically",[68,138,139],{"x":70,"y":121,"style":113,"fill":127,"textAnchor":74},"travels with the data",[44,141],{"x":142,"y":98,"width":99,"height":100,"rx":101,"fill":143,"stroke":144,"style":104},"516","#eff3ff","#2563eb",[68,146,148],{"x":147,"y":108,"style":88,"fill":144,"textAnchor":74},"630","cloned in memory",[68,150,151],{"x":147,"y":112,"style":113,"fill":66,"textAnchor":74},"setRenderer(clone())",[68,153,154],{"x":147,"y":117,"style":113,"fill":66,"textAnchor":74},"instant, no file",[68,156,157],{"x":147,"y":121,"style":113,"fill":144,"textAnchor":74},"gone when QGIS closes",[159,160],"line",{"x1":161,"y1":162,"x2":163,"y2":164,"stroke":66,"style":165},"330","94","160","134","stroke-width:2;marker-end:url(#qmlArrow)",[159,167],{"x1":70,"y1":162,"x2":70,"y2":164,"stroke":66,"style":165},[159,169],{"x1":170,"y1":162,"x2":171,"y2":164,"stroke":66,"style":165},"430","600",[173,174,176],"h2",{"id":175},"prerequisites","Prerequisites",[178,179,180,188,201],"ul",{},[181,182,183,187],"li",{},[184,185,186],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[181,189,190,191,195,196,200],{},"A styled layer — see ",[21,192,194],{"href":193},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fset-vector-layer-symbol-color-pyqgis\u002F","Set a Vector Layer Symbol Colour in PyQGIS"," or ",[21,197,199],{"href":198},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fclassify-layer-natural-breaks-jenks-pyqgis\u002F","Classify a Layer with Natural Breaks"," for ways to build one.",[181,202,203],{},"Write access to wherever the style will be stored.",[173,205,207],{"id":206},"save-and-load-a-qml-file","Save and load a QML file",[209,210,215],"pre",{"className":211,"code":212,"language":213,"meta":214,"style":214},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"flood_zones\")[0]\n\nmessage, ok = layer.saveNamedStyle(\"\u002Fdata\u002Fstyles\u002Fflood_zones.qml\")\nif not ok:\n    raise RuntimeError(f\"could not save style: {message}\")\n\ntarget = QgsProject.instance().mapLayersByName(\"flood_zones_2027\")[0]\nmessage, ok = target.loadNamedStyle(\"\u002Fdata\u002Fstyles\u002Fflood_zones.qml\")\ntarget.triggerRepaint()\n","python","",[216,217,218,236,243,268,273,290,302,334,339,358,372],"code",{"__ignoreMap":214},[219,220,222,226,230,233],"span",{"class":159,"line":221},1,[219,223,225],{"class":224},"snl16","from",[219,227,229],{"class":228},"s95oV"," qgis.core ",[219,231,232],{"class":224},"import",[219,234,235],{"class":228}," QgsProject\n",[219,237,239],{"class":159,"line":238},2,[219,240,242],{"emptyLinePlaceholder":241},true,"\n",[219,244,246,249,252,255,259,262,265],{"class":159,"line":245},3,[219,247,248],{"class":228},"layer ",[219,250,251],{"class":224},"=",[219,253,254],{"class":228}," QgsProject.instance().mapLayersByName(",[219,256,258],{"class":257},"sU2Wk","\"flood_zones\"",[219,260,261],{"class":228},")[",[219,263,46],{"class":264},"sDLfK",[219,266,267],{"class":228},"]\n",[219,269,271],{"class":159,"line":270},4,[219,272,242],{"emptyLinePlaceholder":241},[219,274,276,279,281,284,287],{"class":159,"line":275},5,[219,277,278],{"class":228},"message, ok ",[219,280,251],{"class":224},[219,282,283],{"class":228}," layer.saveNamedStyle(",[219,285,286],{"class":257},"\"\u002Fdata\u002Fstyles\u002Fflood_zones.qml\"",[219,288,289],{"class":228},")\n",[219,291,293,296,299],{"class":159,"line":292},6,[219,294,295],{"class":224},"if",[219,297,298],{"class":224}," not",[219,300,301],{"class":228}," ok:\n",[219,303,305,308,311,314,317,320,323,326,329,332],{"class":159,"line":304},7,[219,306,307],{"class":224},"    raise",[219,309,310],{"class":264}," RuntimeError",[219,312,313],{"class":228},"(",[219,315,316],{"class":224},"f",[219,318,319],{"class":257},"\"could not save style: ",[219,321,322],{"class":264},"{",[219,324,325],{"class":228},"message",[219,327,328],{"class":264},"}",[219,330,331],{"class":257},"\"",[219,333,289],{"class":228},[219,335,337],{"class":159,"line":336},8,[219,338,242],{"emptyLinePlaceholder":241},[219,340,342,345,347,349,352,354,356],{"class":159,"line":341},9,[219,343,344],{"class":228},"target ",[219,346,251],{"class":224},[219,348,254],{"class":228},[219,350,351],{"class":257},"\"flood_zones_2027\"",[219,353,261],{"class":228},[219,355,46],{"class":264},[219,357,267],{"class":228},[219,359,361,363,365,368,370],{"class":159,"line":360},10,[219,362,278],{"class":228},[219,364,251],{"class":224},[219,366,367],{"class":228}," target.loadNamedStyle(",[219,369,286],{"class":257},[219,371,289],{"class":228},[219,373,375],{"class":159,"line":374},11,[219,376,377],{"class":228},"target.triggerRepaint()\n",[14,379,380,383,384,387,388,391],{},[184,381,382],{},"Breakdown:"," Both calls return a ",[216,385,386],{},"(message, success)"," tuple in that order — the success flag is second, and code that tests the first element treats a non-empty message as truth and gets it exactly backwards. ",[216,389,390],{},"triggerRepaint()"," is needed after loading, because changing the renderer does not by itself invalidate the cached rendering. A QML is XML, so it diffs readably in version control, which makes a styles directory a genuinely useful thing to commit alongside a project.",[14,393,394,395,398],{},"Saving a ",[184,396,397],{},"default"," style — a QML named after the data file and sitting beside it — makes QGIS apply it automatically whenever the layer is opened:",[209,400,402],{"className":211,"code":401,"language":213,"meta":214,"style":214},"message, ok = layer.saveDefaultStyle()\n",[216,403,404],{"__ignoreMap":214},[219,405,406,408,410],{"class":159,"line":221},[219,407,278],{"class":228},[219,409,251],{"class":224},[219,411,412],{"class":228}," layer.saveDefaultStyle()\n",[14,414,415,417,418,421,422,425],{},[184,416,382],{}," For a file-based layer this writes ",[216,419,420],{},"flood_zones.qml"," next to ",[216,423,424],{},"flood_zones.gpkg","; for a layer in a database that supports style storage, it writes into the style table instead. It is the least effort route to \"the layer just opens looking right\", and the reason a data directory sometimes contains QML files nobody remembers creating.",[173,427,429],{"id":428},"store-the-style-with-the-data","Store the style with the data",[14,431,432],{},"A QML beside the file gets separated from it eventually. Both GeoPackage and PostGIS can hold styles internally.",[209,434,436],{"className":211,"code":435,"language":213,"meta":214,"style":214},"layer.saveStyleToDatabase(\n    name=\"flood_default\",\n    description=\"Depth bands, published 2026-08\",\n    useAsDefault=True,\n    uiFileContent=\"\",\n)\n\nrelated, names, descriptions, errors = layer.listStylesInDatabase()\nprint(names)\n",[216,437,438,443,457,469,481,493,497,501,511],{"__ignoreMap":214},[219,439,440],{"class":159,"line":221},[219,441,442],{"class":228},"layer.saveStyleToDatabase(\n",[219,444,445,449,451,454],{"class":159,"line":238},[219,446,448],{"class":447},"s9osk","    name",[219,450,251],{"class":224},[219,452,453],{"class":257},"\"flood_default\"",[219,455,456],{"class":228},",\n",[219,458,459,462,464,467],{"class":159,"line":245},[219,460,461],{"class":447},"    description",[219,463,251],{"class":224},[219,465,466],{"class":257},"\"Depth bands, published 2026-08\"",[219,468,456],{"class":228},[219,470,471,474,476,479],{"class":159,"line":270},[219,472,473],{"class":447},"    useAsDefault",[219,475,251],{"class":224},[219,477,478],{"class":264},"True",[219,480,456],{"class":228},[219,482,483,486,488,491],{"class":159,"line":275},[219,484,485],{"class":447},"    uiFileContent",[219,487,251],{"class":224},[219,489,490],{"class":257},"\"\"",[219,492,456],{"class":228},[219,494,495],{"class":159,"line":292},[219,496,289],{"class":228},[219,498,499],{"class":159,"line":304},[219,500,242],{"emptyLinePlaceholder":241},[219,502,503,506,508],{"class":159,"line":336},[219,504,505],{"class":228},"related, names, descriptions, errors ",[219,507,251],{"class":224},[219,509,510],{"class":228}," layer.listStylesInDatabase()\n",[219,512,513,516],{"class":159,"line":341},[219,514,515],{"class":264},"print",[219,517,518],{"class":228},"(names)\n",[14,520,521,523,524,527,528,531,532,535],{},[184,522,382],{}," The style is written into a ",[216,525,526],{},"layer_styles"," table which QGIS creates on first use, so nothing needs preparing. ",[216,529,530],{},"useAsDefault=True"," is what makes it apply automatically on open — without it the style is stored but dormant, which surprises people who then conclude the call did nothing. ",[216,533,534],{},"listStylesInDatabase()"," returns how many styles relate to this layer, their ids, their names and their descriptions, so a plugin can offer a picker. Multiple named styles per layer is the intended design: one for print, one for screen, one for the QA pass.",[209,537,539],{"className":211,"code":538,"language":213,"meta":214,"style":214},"xml, description = layer.getStyleFromDatabase(\"1\")\nlayer.loadNamedStyle(xml, True)          # second argument: the string is XML, not a path\n",[216,540,541,556],{"__ignoreMap":214},[219,542,543,546,548,551,554],{"class":159,"line":221},[219,544,545],{"class":228},"xml, description ",[219,547,251],{"class":224},[219,549,550],{"class":228}," layer.getStyleFromDatabase(",[219,552,553],{"class":257},"\"1\"",[219,555,289],{"class":228},[219,557,558,561,563,566],{"class":159,"line":238},[219,559,560],{"class":228},"layer.loadNamedStyle(xml, ",[219,562,478],{"class":264},[219,564,565],{"class":228},")          ",[219,567,569],{"class":568},"sjoCn","# second argument: the string is XML, not a path\n",[14,571,572,574,575,578,579,581,582,584,585,588],{},[184,573,382],{}," ",[216,576,577],{},"getStyleFromDatabase()"," takes the style id returned by ",[216,580,534],{}," and hands back the QML as a string. Passing ",[216,583,478],{}," as the second argument to ",[216,586,587],{},"loadNamedStyle()"," tells it the string is the document itself rather than a path — a small overload that is easy to miss and produces a \"file not found\" error when omitted.",[173,590,592],{"id":591},"copy-a-style-between-loaded-layers","Copy a style between loaded layers",[14,594,595],{},"When both layers are already open, no file is needed at all.",[209,597,599],{"className":211,"code":598,"language":213,"meta":214,"style":214},"source = QgsProject.instance().mapLayersByName(\"wards_2026\")[0]\ntarget = QgsProject.instance().mapLayersByName(\"wards_2027\")[0]\n\ntarget.setRenderer(source.renderer().clone())\ntarget.setLabelsEnabled(source.labelsEnabled())\nif source.labeling():\n    target.setLabeling(source.labeling().clone())\ntarget.triggerRepaint()\n",[216,600,601,619,636,640,645,650,657,662],{"__ignoreMap":214},[219,602,603,606,608,610,613,615,617],{"class":159,"line":221},[219,604,605],{"class":228},"source ",[219,607,251],{"class":224},[219,609,254],{"class":228},[219,611,612],{"class":257},"\"wards_2026\"",[219,614,261],{"class":228},[219,616,46],{"class":264},[219,618,267],{"class":228},[219,620,621,623,625,627,630,632,634],{"class":159,"line":238},[219,622,344],{"class":228},[219,624,251],{"class":224},[219,626,254],{"class":228},[219,628,629],{"class":257},"\"wards_2027\"",[219,631,261],{"class":228},[219,633,46],{"class":264},[219,635,267],{"class":228},[219,637,638],{"class":159,"line":245},[219,639,242],{"emptyLinePlaceholder":241},[219,641,642],{"class":159,"line":270},[219,643,644],{"class":228},"target.setRenderer(source.renderer().clone())\n",[219,646,647],{"class":159,"line":275},[219,648,649],{"class":228},"target.setLabelsEnabled(source.labelsEnabled())\n",[219,651,652,654],{"class":159,"line":292},[219,653,295],{"class":224},[219,655,656],{"class":228}," source.labeling():\n",[219,658,659],{"class":159,"line":304},[219,660,661],{"class":228},"    target.setLabeling(source.labeling().clone())\n",[219,663,664],{"class":159,"line":336},[219,665,377],{"class":228},[14,667,668,574,670,673,674,677,678,682],{},[184,669,382],{},[216,671,672],{},"clone()"," is essential — assigning ",[216,675,676],{},"source.renderer()"," directly would give two layers one renderer object, and the first of them to be deleted takes the renderer with it. Labels are a separate subsystem, so a renderer copy alone leaves the target unlabelled; copying both is what \"copy the style\" usually means to a user. The labelling settings themselves are covered in ",[21,679,681],{"href":680},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-rule-based-labels-pyqgis\u002F","Add Rule-Based Labels in PyQGIS",".",[14,684,685],{},[29,686,689,692,695,698,701,705,708,713,719,723,726,729,733,736,739,742,746,749,752,756,760,763,766,771,775],{"viewBox":687,"role":32,"ariaLabel":688,"xmlns":34},"0 0 760 262","A QML document broken into its categories — symbology, labeling, fields, forms, actions and layer configuration — showing that loadNamedStyle can apply a subset",[36,690,691],{},"A QML holds more than symbology",[40,693,694],{},"A single QML document contains several independent categories: symbology, labeling, field configuration, attribute forms, actions, and general layer configuration such as scale visibility. Loading with a category filter applies only the chosen parts, so a style can be reused without overwriting field aliases or forms.",[44,696],{"x":46,"y":46,"width":47,"height":697,"fill":49},"262",[68,699,700],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Loading a style can overwrite more than you meant",[44,702],{"x":97,"y":79,"width":703,"height":704,"rx":58,"fill":82,"stroke":83,"style":84},"728","42",[68,706,420],{"x":70,"y":707,"style":88,"fill":83,"textAnchor":74},"75",[44,709],{"x":97,"y":710,"width":711,"height":712,"rx":58,"fill":126,"stroke":127,"style":104},"106","172","76",[68,714,718],{"x":715,"y":716,"style":717,"fill":127,"textAnchor":74},"102","132","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","Symbology",[68,720,722],{"x":715,"y":721,"style":93,"fill":66,"textAnchor":74},"154","renderer, symbols",[68,724,725],{"x":715,"y":711,"style":93,"fill":66,"textAnchor":74},"class breaks, opacity",[44,727],{"x":728,"y":710,"width":711,"height":712,"rx":58,"fill":126,"stroke":127,"style":104},"200",[68,730,732],{"x":731,"y":716,"style":717,"fill":127,"textAnchor":74},"286","Labeling",[68,734,735],{"x":731,"y":721,"style":93,"fill":66,"textAnchor":74},"text format, placement",[68,737,738],{"x":731,"y":711,"style":93,"fill":66,"textAnchor":74},"rules",[44,740],{"x":741,"y":710,"width":711,"height":712,"rx":58,"fill":102,"stroke":103,"style":104},"384",[68,743,745],{"x":744,"y":716,"style":717,"fill":103,"textAnchor":74},"470","Fields & Forms",[68,747,748],{"x":744,"y":721,"style":93,"fill":66,"textAnchor":74},"aliases, widgets",[68,750,751],{"x":744,"y":711,"style":93,"fill":103,"textAnchor":74},"easily clobbered",[44,753],{"x":754,"y":710,"width":755,"height":712,"rx":58,"fill":102,"stroke":103,"style":104},"568","176",[68,757,759],{"x":758,"y":716,"style":717,"fill":103,"textAnchor":74},"656","LayerConfiguration",[68,761,762],{"x":758,"y":721,"style":93,"fill":66,"textAnchor":74},"scale visibility",[68,764,765],{"x":758,"y":711,"style":93,"fill":66,"textAnchor":74},"rendering flags",[44,767],{"x":97,"y":768,"width":703,"height":81,"rx":58,"fill":769,"stroke":770,"style":104},"198","#fffdf7","#59645f",[68,772,774],{"x":70,"y":773,"style":113,"fill":66,"textAnchor":74},"220","loadNamedStyle(path, categories=QgsMapLayer.Symbology | QgsMapLayer.Labeling)",[68,776,778],{"x":70,"y":777,"style":113,"fill":127,"textAnchor":74},"238","applies the two green boxes and leaves the amber ones alone",[173,780,782],{"id":781},"apply-only-part-of-a-style","Apply only part of a style",[14,784,785],{},"A QML carries field aliases, edit widgets, forms and scale-visibility settings as well as symbology. Reusing a style from a different dataset can therefore overwrite configuration that had nothing to do with colour.",[209,787,789],{"className":211,"code":788,"language":213,"meta":214,"style":214},"from qgis.core import QgsMapLayer\n\ntarget.loadNamedStyle(\n    \"\u002Fdata\u002Fstyles\u002Fflood_zones.qml\",\n    categories=QgsMapLayer.Symbology | QgsMapLayer.Labeling,\n)\ntarget.triggerRepaint()\n",[216,790,791,802,806,811,818,834,838],{"__ignoreMap":214},[219,792,793,795,797,799],{"class":159,"line":221},[219,794,225],{"class":224},[219,796,229],{"class":228},[219,798,232],{"class":224},[219,800,801],{"class":228}," QgsMapLayer\n",[219,803,804],{"class":159,"line":238},[219,805,242],{"emptyLinePlaceholder":241},[219,807,808],{"class":159,"line":245},[219,809,810],{"class":228},"target.loadNamedStyle(\n",[219,812,813,816],{"class":159,"line":270},[219,814,815],{"class":257},"    \"\u002Fdata\u002Fstyles\u002Fflood_zones.qml\"",[219,817,456],{"class":228},[219,819,820,823,825,828,831],{"class":159,"line":275},[219,821,822],{"class":447},"    categories",[219,824,251],{"class":224},[219,826,827],{"class":228},"QgsMapLayer.Symbology ",[219,829,830],{"class":224},"|",[219,832,833],{"class":228}," QgsMapLayer.Labeling,\n",[219,835,836],{"class":159,"line":292},[219,837,289],{"class":228},[219,839,840],{"class":159,"line":304},[219,841,377],{"class":228},[14,843,844,574,846,849,850,853,854,857],{},[184,845,382],{},[216,847,848],{},"categories"," is a flag combination from ",[216,851,852],{},"QgsMapLayer.StyleCategory",", and naming the two you want is far safer than the default of everything. This is the fix for the common complaint that loading a style \"broke the attribute form\" or \"made half the layer disappear at certain zoom levels\" — both are other categories arriving uninvited. ",[216,855,856],{},"QgsMapLayer.AllStyleCategories"," restores the default behaviour explicitly when that is genuinely what you want.",[173,859,861],{"id":860},"ship-a-style-library-with-the-project","Ship a style library with the project",[14,863,864],{},"Once a team has more than a handful of styles, loading them by path from wherever they happen to live stops scaling. A small library plus a resolution order fixes it.",[209,866,868],{"className":211,"code":867,"language":213,"meta":214,"style":214},"from pathlib import Path\nfrom qgis.core import QgsMapLayer\n\nSTYLE_DIRS = [\n    Path(\"\u002Fsrv\u002Fgis\u002Fstyles\u002Fproject\"),      # project-specific, wins\n    Path(\"\u002Fsrv\u002Fgis\u002Fstyles\u002Fcorporate\"),    # organisation-wide fallback\n]\n\ndef apply_style(layer, name):\n    for directory in STYLE_DIRS:\n        candidate = directory \u002F f\"{name}.qml\"\n        if candidate.exists():\n            message, ok = layer.loadNamedStyle(\n                str(candidate),\n                categories=QgsMapLayer.Symbology | QgsMapLayer.Labeling,\n            )\n            if not ok:\n                raise RuntimeError(f\"{candidate}: {message}\")\n            layer.triggerRepaint()\n            return candidate\n    raise FileNotFoundError(f\"no style named {name} in {[str(d) for d in STYLE_DIRS]}\")\n",[216,869,870,882,892,896,907,921,934,938,942,954,971,998,1007,1018,1027,1041,1047,1057,1091,1097,1106],{"__ignoreMap":214},[219,871,872,874,877,879],{"class":159,"line":221},[219,873,225],{"class":224},[219,875,876],{"class":228}," pathlib ",[219,878,232],{"class":224},[219,880,881],{"class":228}," Path\n",[219,883,884,886,888,890],{"class":159,"line":238},[219,885,225],{"class":224},[219,887,229],{"class":228},[219,889,232],{"class":224},[219,891,801],{"class":228},[219,893,894],{"class":159,"line":245},[219,895,242],{"emptyLinePlaceholder":241},[219,897,898,901,904],{"class":159,"line":270},[219,899,900],{"class":264},"STYLE_DIRS",[219,902,903],{"class":224}," =",[219,905,906],{"class":228}," [\n",[219,908,909,912,915,918],{"class":159,"line":275},[219,910,911],{"class":228},"    Path(",[219,913,914],{"class":257},"\"\u002Fsrv\u002Fgis\u002Fstyles\u002Fproject\"",[219,916,917],{"class":228},"),      ",[219,919,920],{"class":568},"# project-specific, wins\n",[219,922,923,925,928,931],{"class":159,"line":292},[219,924,911],{"class":228},[219,926,927],{"class":257},"\"\u002Fsrv\u002Fgis\u002Fstyles\u002Fcorporate\"",[219,929,930],{"class":228},"),    ",[219,932,933],{"class":568},"# organisation-wide fallback\n",[219,935,936],{"class":159,"line":304},[219,937,267],{"class":228},[219,939,940],{"class":159,"line":336},[219,941,242],{"emptyLinePlaceholder":241},[219,943,944,947,951],{"class":159,"line":341},[219,945,946],{"class":224},"def",[219,948,950],{"class":949},"svObZ"," apply_style",[219,952,953],{"class":228},"(layer, name):\n",[219,955,956,959,962,965,968],{"class":159,"line":360},[219,957,958],{"class":224},"    for",[219,960,961],{"class":228}," directory ",[219,963,964],{"class":224},"in",[219,966,967],{"class":264}," STYLE_DIRS",[219,969,970],{"class":228},":\n",[219,972,973,976,978,980,983,986,988,990,993,995],{"class":159,"line":374},[219,974,975],{"class":228},"        candidate ",[219,977,251],{"class":224},[219,979,961],{"class":228},[219,981,982],{"class":224},"\u002F",[219,984,985],{"class":224}," f",[219,987,331],{"class":257},[219,989,322],{"class":264},[219,991,992],{"class":228},"name",[219,994,328],{"class":264},[219,996,997],{"class":257},".qml\"\n",[219,999,1001,1004],{"class":159,"line":1000},12,[219,1002,1003],{"class":224},"        if",[219,1005,1006],{"class":228}," candidate.exists():\n",[219,1008,1010,1013,1015],{"class":159,"line":1009},13,[219,1011,1012],{"class":228},"            message, ok ",[219,1014,251],{"class":224},[219,1016,1017],{"class":228}," layer.loadNamedStyle(\n",[219,1019,1021,1024],{"class":159,"line":1020},14,[219,1022,1023],{"class":264},"                str",[219,1025,1026],{"class":228},"(candidate),\n",[219,1028,1030,1033,1035,1037,1039],{"class":159,"line":1029},15,[219,1031,1032],{"class":447},"                categories",[219,1034,251],{"class":224},[219,1036,827],{"class":228},[219,1038,830],{"class":224},[219,1040,833],{"class":228},[219,1042,1044],{"class":159,"line":1043},16,[219,1045,1046],{"class":228},"            )\n",[219,1048,1050,1053,1055],{"class":159,"line":1049},17,[219,1051,1052],{"class":224},"            if",[219,1054,298],{"class":224},[219,1056,301],{"class":228},[219,1058,1060,1063,1065,1067,1069,1071,1073,1076,1078,1081,1083,1085,1087,1089],{"class":159,"line":1059},18,[219,1061,1062],{"class":224},"                raise",[219,1064,310],{"class":264},[219,1066,313],{"class":228},[219,1068,316],{"class":224},[219,1070,331],{"class":257},[219,1072,322],{"class":264},[219,1074,1075],{"class":228},"candidate",[219,1077,328],{"class":264},[219,1079,1080],{"class":257},": ",[219,1082,322],{"class":264},[219,1084,325],{"class":228},[219,1086,328],{"class":264},[219,1088,331],{"class":257},[219,1090,289],{"class":228},[219,1092,1094],{"class":159,"line":1093},19,[219,1095,1096],{"class":228},"            layer.triggerRepaint()\n",[219,1098,1100,1103],{"class":159,"line":1099},20,[219,1101,1102],{"class":224},"            return",[219,1104,1105],{"class":228}," candidate\n",[219,1107,1109,1111,1114,1116,1118,1121,1123,1125,1127,1130,1132,1135,1138,1141,1144,1147,1149,1151,1154,1156,1158],{"class":159,"line":1108},21,[219,1110,307],{"class":224},[219,1112,1113],{"class":264}," FileNotFoundError",[219,1115,313],{"class":228},[219,1117,316],{"class":224},[219,1119,1120],{"class":257},"\"no style named ",[219,1122,322],{"class":264},[219,1124,992],{"class":228},[219,1126,328],{"class":264},[219,1128,1129],{"class":257}," in ",[219,1131,322],{"class":264},[219,1133,1134],{"class":228},"[",[219,1136,1137],{"class":264},"str",[219,1139,1140],{"class":228},"(d) ",[219,1142,1143],{"class":224},"for",[219,1145,1146],{"class":228}," d ",[219,1148,964],{"class":224},[219,1150,967],{"class":264},[219,1152,1153],{"class":228},"]",[219,1155,328],{"class":264},[219,1157,331],{"class":257},[219,1159,289],{"class":228},[14,1161,1162,1164],{},[184,1163,382],{}," Searching an ordered list gives a project the ability to override a corporate style without copying it, and the returned path tells the caller which one actually applied — worth logging, because \"why does this map look different on your machine\" is nearly always answered by that line. Restricting the categories means a shared style can be applied to layers with different attribute schemas without dragging field configuration between them. Raising on a missing style rather than silently leaving the layer unstyled turns a subtle visual difference into an immediate error.",[14,1166,1167,1168,1171],{},"The same idea extends to symbols and colour ramps through QGIS's style manager: a ",[216,1169,1170],{},".xml"," style database can be loaded once into the profile so every project has the organisation's palette available by name. For automated map production the file-based approach above is usually easier to reason about, because the styles travel with the repository rather than with a machine's profile.",[14,1173,1174],{},[29,1175,1178,1181,1184,1187,1194,1197,1202,1207,1210,1214,1219,1223,1226,1230,1234,1236,1239,1242,1248,1251,1256],{"viewBox":1176,"role":32,"ariaLabel":1177,"xmlns":34},"0 0 760 236","A style lookup checking a project directory first and a corporate directory second, with the resolved path logged so differences between machines are traceable",[36,1179,1180],{},"An ordered search makes overrides explicit",[40,1182,1183],{},"A style name is looked up first in the project style directory and then in the corporate one. The first match wins and its path is returned, so a project can override a shared style without copying it and the log records which file was actually applied.",[44,1185],{"x":46,"y":46,"width":47,"height":1186,"fill":49},"236",[51,1188,1189],{},[54,1190,1192],{"id":1191,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"styleArrow",[63,1193],{"d":65,"fill":66},[68,1195,1196],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"First match wins, and the winner is logged",[44,1198],{"x":97,"y":1199,"width":1200,"height":1201,"rx":58,"fill":82,"stroke":83,"style":84},"88","164","60",[68,1203,1206],{"x":1204,"y":1205,"style":717,"fill":83,"textAnchor":74},"98","112","apply_style(layer,",[68,1208,1209],{"x":1204,"y":716,"style":113,"fill":66,"textAnchor":74},"\"flood_zones\")",[44,1211],{"x":99,"y":1212,"width":1213,"height":1212,"rx":58,"fill":126,"stroke":127,"style":84},"56","240",[68,1215,1218],{"x":1216,"y":1217,"style":717,"fill":127,"textAnchor":74},"348","80","styles\u002Fproject\u002Fflood_zones.qml",[68,1220,1222],{"x":1216,"y":1221,"style":93,"fill":66,"textAnchor":74},"100","found — applied",[44,1224],{"x":99,"y":1225,"width":1213,"height":1212,"rx":58,"fill":769,"stroke":770,"style":104},"124",[68,1227,1229],{"x":1216,"y":1228,"style":113,"fill":66,"textAnchor":74},"148","styles\u002Fcorporate\u002Fflood_zones.qml",[68,1231,1233],{"x":1216,"y":1232,"style":93,"fill":770,"textAnchor":74},"168","not reached this time",[44,1235],{"x":142,"y":1199,"width":99,"height":1201,"rx":58,"fill":143,"stroke":144,"style":104},[68,1237,1238],{"x":147,"y":1205,"style":717,"fill":144,"textAnchor":74},"log the resolved path",[68,1240,1241],{"x":147,"y":716,"style":93,"fill":66,"textAnchor":74},"answers \"why does mine differ?\"",[159,1243],{"x1":1244,"y1":1245,"x2":1246,"y2":1199,"stroke":66,"style":1247},"180","104","222","stroke-width:2;marker-end:url(#styleArrow)",[159,1249],{"x1":1244,"y1":716,"x2":1246,"y2":1228,"stroke":770,"style":1250},"stroke-width:2;stroke-dasharray:5 4;marker-end:url(#styleArrow)",[159,1252],{"x1":1253,"y1":1254,"x2":1255,"y2":100,"stroke":66,"style":1247},"468","90","510",[68,1257,1258],{"x":70,"y":773,"style":113,"fill":770,"textAnchor":74},"Restrict the categories so a shared style never carries field configuration between schemas",[173,1260,1262],{"id":1261},"qgis-version-compatibility","QGIS version compatibility",[14,1264,1265,1266,1268],{},"The examples target ",[184,1267,186],{}," (Python 3.12).",[1270,1271,1272,1288],"table",{},[1273,1274,1275],"thead",{},[1276,1277,1278,1282,1285],"tr",{},[1279,1280,1281],"th",{},"QGIS version",[1279,1283,1284],{},"Python",[1279,1286,1287],{},"Notes",[1289,1290,1291,1303,1313,1324],"tbody",{},[1276,1292,1293,1297,1300],{},[1294,1295,1296],"td",{},"3.22 LTR",[1294,1298,1299],{},"3.9",[1294,1301,1302],{},"All methods present; style categories available since 3.4.",[1276,1304,1305,1308,1310],{},[1294,1306,1307],{},"3.28 LTR",[1294,1309,1299],{},[1294,1311,1312],{},"Behaviour matches this page.",[1276,1314,1315,1318,1321],{},[1294,1316,1317],{},"3.34 LTR",[1294,1319,1320],{},"3.12",[1294,1322,1323],{},"Baseline for this page.",[1276,1325,1326,1329,1331],{},[1294,1327,1328],{},"3.40 \u002F 3.44",[1294,1330,1320],{},[1294,1332,1333],{},"Style categories gain new members; existing flags unchanged. QML remains backward compatible within 3.x.",[14,1335,1336],{},"A QML written by a newer QGIS may reference symbol layer types an older one does not know, and those elements are skipped silently. Where a style must work across versions, author it on the oldest release in use.",[173,1338,1340],{"id":1339},"troubleshooting","Troubleshooting",[178,1342,1343,1352,1360,1368,1374,1380],{},[181,1344,1345,1348,1349,1351],{},[184,1346,1347],{},"The style saved but nothing changed on load."," The return tuple's success flag was not checked, or ",[216,1350,390],{}," was not called.",[181,1353,1354,574,1357,1359],{},[184,1355,1356],{},"\"Style not found\" when loading from a database.",[216,1358,534],{}," returns ids as strings; pass the id, not the name.",[181,1361,1362,1365,1366,682],{},[184,1363,1364],{},"A stored style is not applied on open."," It was saved without ",[216,1367,530],{},[181,1369,1370,1373],{},[184,1371,1372],{},"Loading a style broke the attribute form."," The Fields and Forms categories came with it. Restrict the categories.",[181,1375,1376,1379],{},[184,1377,1378],{},"Colours look different after loading."," The QML references a colour ramp or SVG marker that is not installed on this machine. Bundle the SVGs, or use built-in symbols.",[181,1381,1382,1385,1386,1388],{},[184,1383,1384],{},"The renderer disappeared after copying between layers."," The renderer was assigned without ",[216,1387,672],{},", so ownership was transferred rather than shared.",[173,1390,1392],{"id":1391},"conclusion","Conclusion",[14,1394,1395,1398,1399,1402,1403,1406,1407,1409,1410,1413],{},[216,1396,1397],{},"saveNamedStyle"," and ",[216,1400,1401],{},"loadNamedStyle"," move a style through a QML file; ",[216,1404,1405],{},"saveStyleToDatabase"," with ",[216,1408,530],{}," keeps it inside the GeoPackage or PostGIS table so it travels with the data; and ",[216,1411,1412],{},"renderer().clone()"," copies one between open layers. Check the second element of the return tuple, repaint afterwards, and restrict the style categories whenever the target layer has configuration worth keeping.",[173,1415,1417],{"id":1416},"frequently-asked-questions","Frequently Asked Questions",[14,1419,1420,1423],{},[184,1421,1422],{},"QML or SLD?","\nQML is QGIS's own format and captures everything QGIS can do. SLD is an OGC standard understood by other software but supports only a subset — export SLD for interoperability, keep QML as the source of truth.",[14,1425,1426,1429],{},[184,1427,1428],{},"Can I edit a QML by hand?","\nYes, it is plain XML, and a search-and-replace on a colour is sometimes the fastest edit there is. Reload it in QGIS afterwards to confirm it still parses.",[14,1431,1432,1435],{},[184,1433,1434],{},"How do I apply one style to many layers?","\nLoad it in a loop over the layers, or copy the cloned renderer. Restrict the categories so per-layer field configuration survives.",[14,1437,1438,1441],{},[184,1439,1440],{},"Does a style saved in a GeoPackage travel to other users?","\nYes — it is a table inside the file, so anyone opening that GeoPackage in QGIS gets the default style automatically.",[14,1443,1444,1447,1448,682],{},[184,1445,1446],{},"Why does my raster style not load onto a vector layer?","\nStyles are type-specific. A raster QML describes bands and ramps; a vector layer has no such concepts and the load fails. Raster styling is covered in ",[21,1449,1451],{"href":1450},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fapply-color-ramp-to-raster-pyqgis\u002F","Apply a Colour Ramp to a Raster in PyQGIS",[173,1453,1455],{"id":1454},"related","Related",[178,1457,1458,1463,1467,1473,1479],{},[181,1459,1460,1462],{},[21,1461,24],{"href":23}," — the guide this recipe belongs to",[181,1464,1465],{},[21,1466,194],{"href":193},[181,1468,1469],{},[21,1470,1472],{"href":1471},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Frule-based-renderer-pyqgis\u002F","Create a Rule-Based Renderer in PyQGIS",[181,1474,1475],{},[21,1476,1478],{"href":1477},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fwrite-vector-layer-to-geopackage-pyqgis\u002F","Write a Vector Layer to GeoPackage in PyQGIS",[181,1480,1481],{},[21,1482,1451],{"href":1450},[1484,1485,1486],"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}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":214,"searchDepth":238,"depth":238,"links":1488},[1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499],{"id":175,"depth":238,"text":176},{"id":206,"depth":238,"text":207},{"id":428,"depth":238,"text":429},{"id":591,"depth":238,"text":592},{"id":781,"depth":238,"text":782},{"id":860,"depth":238,"text":861},{"id":1261,"depth":238,"text":1262},{"id":1339,"depth":238,"text":1340},{"id":1391,"depth":238,"text":1392},{"id":1416,"depth":238,"text":1417},{"id":1454,"depth":238,"text":1455},"Persist layer symbology with saveNamedStyle and loadNamedStyle, store styles inside a GeoPackage or PostGIS, copy a style between layers, and control which style categories are applied.","md",{"slug":1503,"type":1504,"breadcrumb":1505,"datePublished":1506,"dateModified":1506},"save-and-load-qml-style-pyqgis","article","Save & Load QML Styles","2026-08-10","\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis",{"title":5,"description":1500},"pyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis\u002Findex","SVS7bXq3r8r2DGqfQ2NNFU7EaoYPmd80SQwGMhaQTqw",1786401337550]