Set Vector Layer Symbol Color in PyQGIS

Recolouring a layer is the single most common styling task in PyQGIS, and it is a one-liner once you know which object holds the colour. The short version: reach the renderer's symbol, call setColor() with a QColor, then repaint. This page is a focused recipe within the broader Programmatic Layer Styling in PyQGIS cluster, covering the fill colour, the outline colour, hex versus RGBA, the differences between marker, fill, and line layers, and how to make the change survive a project reload.

Prerequisites

  • QGIS 3.34 LTR (Python 3.12), running code in the Python Console or a standalone script.
  • A loaded, valid vector layer with a single-symbol renderer (the default for a freshly added layer).
  • QColor imported from qgis.PyQt.QtGui.
  • A basic grasp of the renderer → symbol → symbol-layer chain described in the parent cluster.

Where the color lives

The colour you want to change is not a property of the layer directly — it is buried two levels down. The layer holds a renderer, the renderer holds a symbol, and the symbol holds one or more symbol layers that carry the actual QColor. That means there are two valid targets depending on intent: call setColor() on the symbol for a quick flat recolour that touches every symbol layer, or call it on a specific symbol layer (symbolLayer(0)) when you need to keep the fill and outline independent. The recipes below cover both, plus the geometry-specific quirks and how to make the change permanent.

Recipe 1: Set the whole symbol's color

For a flat recolour where the fill and any outline should take the same base colour, set it on the symbol itself. The colour cascades to every symbol layer in the stack.

from qgis.core import QgsProject
from qgis.PyQt.QtGui import QColor

layer = QgsProject.instance().mapLayersByName("districts")[0]

symbol = layer.renderer().symbol()
symbol.setColor(QColor("#0f766e"))   # teal, from a hex string

layer.triggerRepaint()

Breakdown: layer.renderer() returns the QgsSingleSymbolRenderer; .symbol() returns its QgsSymbol. setColor() on the symbol is a shortcut that applies the colour to all of its symbol layers at once. QColor accepts a hex string directly, so "#0f766e" needs no parsing. triggerRepaint() schedules the canvas redraw.

Recipe 2: Set fill and outline colors separately

Most cartographic work wants a fill in one colour and a contrasting outline in another. That requires addressing the symbol layer with symbolLayer(0) and calling setColor() for the fill and setStrokeColor() for the outline.

from qgis.core import QgsProject
from qgis.PyQt.QtGui import QColor

layer = QgsProject.instance().mapLayersByName("districts")[0]
fill_layer = layer.renderer().symbol().symbolLayer(0)

fill_layer.setColor(QColor(245, 158, 11))        # amber fill (RGB)
fill_layer.setStrokeColor(QColor("#17211d"))     # near-black outline
fill_layer.setStrokeWidth(0.5)                   # outline width in mm

layer.triggerRepaint()

Breakdown: symbolLayer(0) is the bottom-most drawing layer — a QgsSimpleFillSymbolLayer for polygons. setColor() here affects only that layer's fill, leaving you free to set a different setStrokeColor(). QColor is shown with three integers (0–255) to contrast with the hex form; both produce the same object. setStrokeWidth() is in millimetres by default, matching QGIS's symbol units.

Recipe 3: Color by geometry type

The setter names differ slightly between geometry types because the symbol layer classes differ. The snippet below detects the geometry and applies an appropriate colour and outline for each case.

from qgis.core import QgsProject, QgsWkbTypes
from qgis.PyQt.QtGui import QColor

layer = QgsProject.instance().mapLayersByName("features")[0]
sl = layer.renderer().symbol().symbolLayer(0)
gtype = layer.geometryType()

if gtype == QgsWkbTypes.PolygonGeometry:
    sl.setColor(QColor("#22c55e"))          # fill
    sl.setStrokeColor(QColor("#15803d"))    # outline
elif gtype == QgsWkbTypes.LineGeometry:
    sl.setColor(QColor("#2563eb"))          # the line is the color
elif gtype == QgsWkbTypes.PointGeometry:
    sl.setColor(QColor("#b45309"))          # marker fill
    sl.setStrokeColor(QColor("#ffffff"))    # marker outline / halo

layer.triggerRepaint()

Breakdown: For polygons, setColor() is the fill and setStrokeColor() is the border. For lines, there is no fill — setColor() is the stroke, and a separate setStrokeColor() does not apply. For point markers, setColor() fills the marker shape while setStrokeColor() outlines it. Branching on layer.geometryType() keeps one function working across all three layer types. Continuous, per-feature colour instead of one flat colour is the territory of data-defined overrides covered in the parent cluster, or of a full Create a Choropleth Map in PyQGIS workflow when you need graduated classes.

Recipe 4: Hex, RGBA, and named colors

QColor is flexible about how you describe a colour, which matters when colours come from a config file, a web palette, or a brand guide.

from qgis.PyQt.QtGui import QColor

QColor("#2563eb")            # 6-digit hex
QColor("#882563eb")          # 8-digit hex: alpha first (AARRGGBB)
QColor(37, 99, 235)          # RGB integers 0-255
QColor(37, 99, 235, 128)     # RGBA, last value is 50% alpha
QColor("steelblue")          # SVG/CSS named color

# Build from a config value safely:
hex_value = "#0f766e"
color = QColor(hex_value)
if not color.isValid():
    color = QColor("#777777")   # fallback for a malformed string

Breakdown: Qt's 8-digit hex puts alpha first (#AARRGGBB), which trips people up; if you control the format, prefer the four-integer QColor(r, g, b, a) for clarity. QColor.isValid() lets you guard against a bad string from external input rather than silently drawing an invalid colour. Per-colour alpha set this way stacks multiplicatively with the symbol-level setOpacity().

Recipe 5: Recolor a categorized layer's classes

If the layer already uses a categorized renderer, renderer().symbol() does not exist — there is one symbol per class instead. You recolour by iterating the categories and rebuilding each one's symbol, which is a frequent need when adapting an existing thematic map to a new palette.

from qgis.core import QgsProject, QgsRendererCategory
from qgis.PyQt.QtGui import QColor

layer = QgsProject.instance().mapLayersByName("landuse")[0]
renderer = layer.renderer()  # a QgsCategorizedSymbolRenderer

palette = {
    "residential": "#b45309",
    "commercial": "#2563eb",
    "industrial": "#6b7280",
    "park": "#22c55e",
}

for i, category in enumerate(renderer.categories()):
    symbol = category.symbol().clone()
    hex_value = palette.get(str(category.value()))
    if hex_value:
        symbol.setColor(QColor(hex_value))
        renderer.updateCategorySymbol(i, symbol)

layer.triggerRepaint()

Breakdown: renderer.categories() returns the list of QgsRendererCategory objects, each pairing a value with a symbol. You clone the existing symbol so the original is not mutated mid-iteration, recolour the clone, and write it back with updateCategorySymbol(index, symbol). Mapping str(category.value()) against a dictionary keeps the colour assignment data-driven rather than positional. For building such a renderer from scratch, the Create a Choropleth Map in PyQGIS recipe is the right starting point.

Recipe 6: Repaint, refresh the legend, and persist

The colour change is live in memory after the setter call, but three follow-ups make it visible and durable.

from qgis.core import QgsProject
from qgis.utils import iface

layer = QgsProject.instance().mapLayersByName("districts")[0]

# 1. Redraw the canvas.
layer.triggerRepaint()

# 2. Update the Layers panel swatch (skip in headless scripts).
if iface is not None:
    iface.layerTreeView().refreshLayerSymbology(layer.id())

# 3. Save the style so it reloads with the layer next time.
result, message = layer.saveNamedStyle("/data/districts.qml")
if not result:
    print(f"Style save failed: {message}")

Breakdown: triggerRepaint() handles the map; refreshLayerSymbology() handles the legend swatch, which is a separate widget and will otherwise show the old colour. saveNamedStyle() returns a (success, message) tuple and writes a .qml sidecar that QGIS loads automatically beside the data source. To embed the style inside a GeoPackage or PostGIS table instead, use layer.saveStyleToDatabase("districts", "auto style", True, "").

Recipe 7: Copy a color from one layer to another

To keep a set of layers visually consistent, read the colour off a reference layer and apply it to others rather than hard-coding the hex string in several places.

from qgis.core import QgsProject

project = QgsProject.instance()
source = project.mapLayersByName("template")[0]
ref_color = source.renderer().symbol().color()   # a QColor

for name in ("layer_a", "layer_b", "layer_c"):
    layer = project.mapLayersByName(name)[0]
    layer.renderer().symbol().setColor(ref_color)
    layer.triggerRepaint()

Breakdown: symbol().color() reads the current symbol colour as a QColor, which you then push into each target with setColor(). Because QColor is copied by value, the layers stay independent — recolouring one later does not affect the others. This is a lightweight alternative to saving and loading a full QML style when only the colour needs to match.

QGIS Version Compatibility

Baseline for every snippet on this page is QGIS 3.34 LTR with Python 3.12. The colour and symbol-layer API has been stable across the 3.x series.

QGIS versionPythonNotes
3.28 LTR3.9setColor, setStrokeColor, saveNamedStyle all present and identical.
3.34 LTR3.12Reference version; run as written.
3.40 / 3.443.12QgsWkbTypes.PolygonGeometry etc. still resolve; scoped forms like Qgis.GeometryType.Polygon are the newer preferred spelling.

If you target both 3.28 and the 3.40+ line from one script, stick to the flat enum names shown here — they remain valid everywhere.

Troubleshooting

AttributeError: 'QgsCategorizedSymbolRenderer' object has no attribute 'symbol' The layer is not using a single-symbol renderer. renderer().symbol() only exists on QgsSingleSymbolRenderer. Either reset the layer with QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(layer.geometryType())) first, or, for a categorized layer, recolour an individual class via renderer().symbols(QgsRenderContext()).

The canvas updated but the legend still shows the old color. You called triggerRepaint() but not refreshLayerSymbology(). Add iface.layerTreeView().refreshLayerSymbology(layer.id()).

The color reverts after reopening the project. The change lived only in memory. Call saveNamedStyle() to write a .qml, or save the QGIS project so the styling is stored in the .qgz file.

setStrokeColor did nothing on a line layer. Line symbol layers have no separate stroke — the line itself is the stroke. Use setColor() to recolour a line and setWidth() to size it.

The colour looks too dark or fully opaque despite an alpha value. Check whether you used the 8-digit hex form, where alpha comes first (#AARRGGBB). A value like #ff2563eb is fully opaque blue, not 100% of something else. Prefer QColor(r, g, b, a) to avoid ambiguity.

Conclusion

Setting a vector layer's colour comes down to three decisions: which object to target (the symbol for a flat recolour, the symbol layer for separate fill and outline), how to express the colour (hex, RGBA, or a named colour via QColor), and how to make it stick (triggerRepaint() plus refreshLayerSymbology(), then saveNamedStyle()). With those in hand you can recolour any single-symbol layer reliably across QGIS versions. When you need colour to vary by value rather than a single flat fill, move on to a color ramp or a graduated renderer.

Frequently Asked Questions

How do I set only the outline color and leave the fill alone? Target the symbol layer and call only setStrokeColor(): layer.renderer().symbol().symbolLayer(0).setStrokeColor(QColor("#000000")), then triggerRepaint(). Do not call setColor(), which would change the fill.

Can I use a hex string directly without converting it? Yes. QColor("#2563eb") accepts a hex string. Guard external input with QColor(value).isValid() so a malformed string does not silently produce an invalid colour.

Why does the same setColor() change the fill on polygons but the line on lines? Because the symbol-layer classes differ. A line has no fill, so its primary colour is the stroke; on a polygon the primary colour is the fill and the outline is a separate setStrokeColor().

How do I make the colour change permanent? Either save the QGIS project, or call layer.saveNamedStyle("path.qml") to write a sidecar that loads with the data source. For database-backed layers, saveStyleToDatabase() stores the style in the GeoPackage or PostGIS table.