Save and Load a QML Style in PyQGIS
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.
This recipe belongs to 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.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- A styled layer — see Set a Vector Layer Symbol Colour in PyQGIS or Classify a Layer with Natural Breaks for ways to build one.
- Write access to wherever the style will be stored.
Save and load a QML file
from qgis.core import QgsProject
layer = QgsProject.instance().mapLayersByName("flood_zones")[0]
message, ok = layer.saveNamedStyle("/data/styles/flood_zones.qml")
if not ok:
raise RuntimeError(f"could not save style: {message}")
target = QgsProject.instance().mapLayersByName("flood_zones_2027")[0]
message, ok = target.loadNamedStyle("/data/styles/flood_zones.qml")
target.triggerRepaint()
Breakdown: Both calls return a (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. 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.
Saving a default style — a QML named after the data file and sitting beside it — makes QGIS apply it automatically whenever the layer is opened:
message, ok = layer.saveDefaultStyle()
Breakdown: For a file-based layer this writes flood_zones.qml next to 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.
Store the style with the data
A QML beside the file gets separated from it eventually. Both GeoPackage and PostGIS can hold styles internally.
layer.saveStyleToDatabase(
name="flood_default",
description="Depth bands, published 2026-08",
useAsDefault=True,
uiFileContent="",
)
related, names, descriptions, errors = layer.listStylesInDatabase()
print(names)
Breakdown: The style is written into a layer_styles table which QGIS creates on first use, so nothing needs preparing. 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. 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.
xml, description = layer.getStyleFromDatabase("1")
layer.loadNamedStyle(xml, True) # second argument: the string is XML, not a path
Breakdown: getStyleFromDatabase() takes the style id returned by listStylesInDatabase() and hands back the QML as a string. Passing True as the second argument to 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.
Copy a style between loaded layers
When both layers are already open, no file is needed at all.
source = QgsProject.instance().mapLayersByName("wards_2026")[0]
target = QgsProject.instance().mapLayersByName("wards_2027")[0]
target.setRenderer(source.renderer().clone())
target.setLabelsEnabled(source.labelsEnabled())
if source.labeling():
target.setLabeling(source.labeling().clone())
target.triggerRepaint()
Breakdown: clone() is essential — assigning 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 Add Rule-Based Labels in PyQGIS.
Apply only part of a style
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.
from qgis.core import QgsMapLayer
target.loadNamedStyle(
"/data/styles/flood_zones.qml",
categories=QgsMapLayer.Symbology | QgsMapLayer.Labeling,
)
target.triggerRepaint()
Breakdown: categories is a flag combination from 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. QgsMapLayer.AllStyleCategories restores the default behaviour explicitly when that is genuinely what you want.
Ship a style library with the project
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.
from pathlib import Path
from qgis.core import QgsMapLayer
STYLE_DIRS = [
Path("/srv/gis/styles/project"), # project-specific, wins
Path("/srv/gis/styles/corporate"), # organisation-wide fallback
]
def apply_style(layer, name):
for directory in STYLE_DIRS:
candidate = directory / f"{name}.qml"
if candidate.exists():
message, ok = layer.loadNamedStyle(
str(candidate),
categories=QgsMapLayer.Symbology | QgsMapLayer.Labeling,
)
if not ok:
raise RuntimeError(f"{candidate}: {message}")
layer.triggerRepaint()
return candidate
raise FileNotFoundError(f"no style named {name} in {[str(d) for d in STYLE_DIRS]}")
Breakdown: 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.
The same idea extends to symbols and colour ramps through QGIS's style manager: a .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.
QGIS version compatibility
The examples target QGIS 3.34 LTR (Python 3.12).
| QGIS version | Python | Notes |
|---|---|---|
| 3.22 LTR | 3.9 | All methods present; style categories available since 3.4. |
| 3.28 LTR | 3.9 | Behaviour matches this page. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40 / 3.44 | 3.12 | Style categories gain new members; existing flags unchanged. QML remains backward compatible within 3.x. |
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.
Troubleshooting
- The style saved but nothing changed on load. The return tuple's success flag was not checked, or
triggerRepaint()was not called. - "Style not found" when loading from a database.
listStylesInDatabase()returns ids as strings; pass the id, not the name. - A stored style is not applied on open. It was saved without
useAsDefault=True. - Loading a style broke the attribute form. The Fields and Forms categories came with it. Restrict the categories.
- 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.
- The renderer disappeared after copying between layers. The renderer was assigned without
clone(), so ownership was transferred rather than shared.
Conclusion
saveNamedStyle and loadNamedStyle move a style through a QML file; saveStyleToDatabase with useAsDefault=True keeps it inside the GeoPackage or PostGIS table so it travels with the data; and 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.
Frequently Asked Questions
QML or SLD? QML 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.
Can I edit a QML by hand? Yes, 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.
How do I apply one style to many layers? Load it in a loop over the layers, or copy the cloned renderer. Restrict the categories so per-layer field configuration survives.
Does a style saved in a GeoPackage travel to other users? Yes — it is a table inside the file, so anyone opening that GeoPackage in QGIS gets the default style automatically.
Why does my raster style not load onto a vector layer? Styles 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 Apply a Colour Ramp to a Raster in PyQGIS.