Copy a Style Between Layers in PyQGIS
A cartographer spends an afternoon getting one layer right, and then twelve more layers need the same treatment. Doing it by hand means twelve chances to end up with slightly different line widths; doing it from code takes two lines and guarantees they match. The subtlety is deciding what to copy — symbology alone, or labelling, field aliases, forms and joins as well — and QGIS lets you choose.
This recipe belongs to Programmatic Layer Styling. It covers cloning a renderer, copying selected style categories, the style manager for layers that carry several styles, applying a saved style to many layers at once, and what happens when the target layer's fields do not match the source.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- Two or more layers of the same geometry type — a polygon renderer applied to a point layer does nothing useful.
- A styled source layer; building one is covered in Set a Vector Layer Symbol Colour in PyQGIS.
Clone the renderer
The direct approach copies symbology and nothing else.
from qgis.core import QgsProject
project = QgsProject.instance()
source = project.mapLayersByName("Parcels 2025")[0]
target = project.mapLayersByName("Parcels 2026")[0]
target.setRenderer(source.renderer().clone())
target.triggerRepaint()
Breakdown: clone() is essential — assigning source.renderer() directly would give both layers the same renderer object, so a later change to one silently changes the other, and destroying one layer leaves the other holding freed memory. The clone is independent from the moment it is made. This copies the whole symbology including categories, rules, colour ramps and data-defined overrides, which is exactly what you want when the target has the same fields. When it does not, the categories still copy but match nothing, which is where the field question below comes in.
Labelling is a separate object and needs its own line:
if source.labeling() is not None:
target.setLabeling(source.labeling().clone())
target.setLabelsEnabled(source.labelsEnabled())
Breakdown: Two properties, both needed: the labelling configuration, and the flag that turns labelling on. Copying the first without the second produces a layer that is configured to label and does not, which is a puzzling ten minutes. labeling() returns None when the source has no labelling at all, so the check avoids an AttributeError on an unlabelled source.
Copy selected style categories
For anything more than symbology, the style-category API says precisely what to bring across.
from qgis.core import QgsMapLayer
categories = (QgsMapLayer.StyleCategory.Symbology
| QgsMapLayer.StyleCategory.Labeling
| QgsMapLayer.StyleCategory.Rendering)
error = target.importNamedStyle(source.exportNamedStyle(categories=categories))
Breakdown: exportNamedStyle() serialises the chosen categories to an XML document and importNamedStyle() reads it back into the target, which is the same mechanism as saving and loading a QML file without touching the disk. The categories are flags combined with the bitwise or, and the useful ones are Symbology, Labeling, Rendering (opacity, blend mode, scale visibility), Fields, Forms, Actions, MapTips, AttributeTable, Diagrams and CustomProperties. AllStyleCategories copies everything, which is right only between layers of genuinely identical structure — it brings field aliases, widget configuration and joins with it, and a mismatch produces an attribute table configured for fields that do not exist.
Apply one style to many layers
def apply_style(source, targets, categories):
document = source.exportNamedStyle(categories=categories)
applied, skipped = [], []
for layer in targets:
if layer.geometryType() != source.geometryType():
skipped.append(layer.name())
continue
layer.importNamedStyle(document)
layer.triggerRepaint()
applied.append(layer.name())
return applied, skipped
polygons = [l for l in QgsProject.instance().mapLayers().values()
if hasattr(l, "geometryType")]
applied, skipped = apply_style(source, polygons, categories)
print(f"styled {len(applied)}, skipped {len(skipped)}: {skipped}")
Breakdown: Exporting once and importing many times is faster than cloning per layer and, more usefully, guarantees every target gets an identical style. The geometry-type check prevents the quiet no-op of applying a polygon renderer to a point layer; reporting what was skipped is what stops that being a mystery later. The hasattr filter excludes raster layers, which have no geometryType() — a cleaner alternative on newer releases is checking layer.type() against the vector layer type. Note that importNamedStyle() returns a tuple of success and message on some paths; checking it is worthwhile in a script that must not silently do nothing.
Keep several styles on one layer
A layer can carry named styles and switch between them, which is how one project offers a printed look and a screen look.
manager = target.styleManager()
manager.addStyleFromLayer("Print") # capture the current style under a name
target.setRenderer(screen_renderer)
manager.addStyleFromLayer("Screen")
manager.setCurrentStyle("Print")
print(manager.styles(), manager.currentStyle())
Breakdown: addStyleFromLayer() snapshots the layer's current styling under a name, so the workflow is: style the layer, save it as a name, restyle, save that too. Switching with setCurrentStyle() changes the layer's appearance instantly and is remembered in the project. This is the mechanism behind the style combo in the layer properties dialog, and it pairs well with layouts: a map item locked to a map theme can render the print style while the canvas shows the screen one, as described in Add a Map Item and Set Its Extent in PyQGIS.
When the fields do not match
A categorized or graduated renderer refers to a field by name, and copying it to a layer without that field produces a layer where every feature falls into no category — visible as an unstyled or invisible layer rather than an error.
Three ways out, in order of preference. Rename the field in the target if it is genuinely the same attribute under a different name; the style then applies unchanged and everything downstream stays simple. Rewrite the renderer's attribute after copying, with renderer.setClassAttribute("new_field") for categorized and graduated renderers, which is a single line and keeps the colours and breaks. Rebuild the categories from the target's own values when the classification itself differs, as in Create a Categorized Renderer in PyQGIS.
renderer = target.renderer()
if hasattr(renderer, "setClassAttribute"):
renderer.setClassAttribute("landuse_code")
target.triggerRepaint()
Breakdown: The hasattr guard is there because a single-symbol or rule-based renderer has no class attribute, and a script applying a style across mixed layers will meet both. Checking rather than assuming keeps the helper usable everywhere. After changing the attribute the category values still refer to the old field's contents, so this only helps when the two fields hold the same vocabulary — otherwise the catch-all category is doing all the work and it is time to rebuild.
QGIS version compatibility
| QGIS version | Python | Notes |
|---|---|---|
| 3.22 LTR | 3.9 | clone(), style categories and the style manager all present. |
| 3.28 LTR | 3.9 | Identical. |
| 3.34 LTR | 3.12 | Baseline for this page; scoped QgsMapLayer.StyleCategory names available. |
| 3.40 / 3.44 | 3.12 | Identical; further categories added for newer layer types. |
Styles can also travel as QML files on disk, which is the right choice when the style must be shared between projects or committed to a repository — see Save and Load a QML Style in PyQGIS.
Troubleshooting
- Changing one layer changes another. The renderer was assigned rather than cloned. Always
clone(). - The target layer is invisible after copying. A categorized renderer on a field the target does not have. Rewrite the class attribute or rebuild.
- Labels did not come across. Labelling is separate. Copy it and set
setLabelsEnabled(True). - The attribute table looks wrong.
AllStyleCategoriesbrought field configuration from a differently shaped layer. Copy only symbology and labelling. - Nothing happened at all. Geometry types differ, or
triggerRepaint()was not called. - A saved style reverted. The project was not saved, or the layer's current style was switched back by the style manager.
Conclusion
Clone the renderer rather than assigning it, copy labelling separately along with its enabled flag, and use exportNamedStyle() with explicit categories when more than symbology should travel. Export once and import many times when styling a set of layers, check geometry types and report what was skipped, and when the target's fields differ, rewrite the renderer's class attribute rather than accepting a layer that quietly renders nothing.
Frequently Asked Questions
What is the difference between copying a style and loading a QML? None in substance — a QML is the same serialised style on disk. Copy in memory within one script, use a QML when the style must outlive it or be shared.
Can I copy a style from a raster to a vector layer? No. Their styling models are unrelated. Raster styles copy to other rasters the same way.
Does copying a style bring the layer's coordinate system?
Only if the CRS category is included, and doing so is almost never wanted — it changes how the data is interpreted, not how it looks.
How do I copy just the colours? There is no colour-only category. Clone the renderer and change what should differ, or extract the colour ramp and apply it to a renderer you build.
Can I apply a style to a layer that is not in the project? Yes. Styling belongs to the layer object, so a layer built in a script and never added can be styled and then exported.
Does the style survive an export to GeoPackage? Yes — a GeoPackage can store layer styles, and QGIS offers to save them there. A shapefile cannot, which is one more reason to prefer GeoPackage.