Save Layer Styles to a Database in PyQGIS
A carefully styled layer is only carefully styled in the project where the style was made. Open the same GeoPackage in a new project, or load the same PostGIS table on a colleague's machine, and it appears with a random colour. QML files next to the data help, but they get separated from it. GeoPackage and PostGIS can store QGIS styles in the database itself, in a layer_styles table, so the style travels with the data — and a style marked as default loads automatically whenever anyone adds the layer.
This recipe belongs to Programmatic Layer Styling. It saves a style into a GeoPackage or PostGIS database, sets it as the default, stores several named styles per layer, lists and loads styles from Python, and styles a whole delivery so recipients see it as intended.
Prerequisites
- QGIS 3.34 LTR or newer, or the QGIS 4 series.
- A layer from a GeoPackage, PostGIS, SpatiaLite or MS SQL source — formats that support stored styles. Shapefiles and CSVs do not; use QML files for those, as in saving and loading QML styles.
- For PostGIS, permission to create and write the
layer_stylestable, usually in thepublicschema.
Save a style into the database
saveStyleToDatabase writes the layer's current style as a row in the database's layer_styles table, creating the table on first use.
from qgis.core import QgsProject, QgsVectorLayer
parcels = QgsVectorLayer("/data/cadastre/cadastre.gpkg|layername=parcels", "parcels", "ogr")
# ... style the layer: renderer, labels, form configuration ...
error = parcels.saveStyleToDatabase(
"parcels default", # style name
"Cadastre parcels, ownership colours", # description
True, # use as default
"") # optional UI file (rarely used)
print("saved" if not error else f"failed: {error}")
Breakdown: The name identifies the style among others for the same table; the description appears in QGIS's style selector. Marking it as default means QGIS applies it automatically whenever this table is added to any project. The style is stored as full QML — renderer, labels, diagrams, forms, field aliases and more — plus an SLD version for other software. On recent releases saveStyleToDatabase returns an error message string (empty on success); older releases return nothing and report errors through the message log.
Know what is stored, and where
The layer_styles table is a normal table in the database, readable with SQL. Knowing its structure helps when managing styles at scale.
from qgis.core import QgsProviderRegistry
md = QgsProviderRegistry.instance().providerMetadata("ogr")
conn = md.createConnection("/data/cadastre/cadastre.gpkg", {})
for row in conn.executeSql(
"SELECT f_table_name, stylename, useasdefault, update_time FROM layer_styles ORDER BY f_table_name"):
print(row)
Breakdown: Each row ties a style to a table and geometry column. Because it is plain SQL, styles can be audited — which tables have defaults, when they were last changed — and copied between databases with ordinary inserts. In PostGIS, layer_styles is usually created in the public schema and shared by all schemas in the database, so give editors write access to it and readers select access. The connections API used here is covered in using the database connections API.
Store several named styles
One table often needs several looks: a detailed style for editing, a simple one for overview maps, a print style with heavier lines. Each can be saved under its own name, with one marked as default.
from qgis.core import QgsSingleSymbolRenderer, QgsFillSymbol
parcels.setRenderer(QgsSingleSymbolRenderer(QgsFillSymbol.createSimple(
{"color": "255,255,255,0", "outline_color": "#59645f", "outline_width": "0.15"})))
parcels.setLabelsEnabled(False)
parcels.saveStyleToDatabase("parcels overview", "Outlines only, no labels", False, "")
count, ids, names, descriptions, error = parcels.listStylesInDatabase()
for sid, name, desc in zip(ids, names, descriptions):
print(sid, name, "-", desc)
Breakdown: Saving with useAsDefault=False stores an alternative without changing which style loads automatically. listStylesInDatabase returns the number of styles related to this table first — those for the same table come first in the lists — then parallel lists of ids, names and descriptions, and an error message. Users can pick a stored style in the layer properties' Style menu; scripts load one by id, as below.
Load a stored style from Python
Loading by id applies a stored style to a layer, for example to switch every layer of a project into its print style before export.
from qgis.PyQt.QtXml import QDomDocument
def apply_stored_style(layer, wanted_name):
count, ids, names, descs, err = layer.listStylesInDatabase()
for sid, name in zip(ids[:count], names[:count]):
if name == wanted_name:
qml, err = layer.getStyleFromDatabase(sid)
doc = QDomDocument()
doc.setContent(qml)
ok, msg = layer.importNamedStyle(doc)
layer.triggerRepaint()
return ok
return False
for lyr in QgsProject.instance().mapLayers().values():
if isinstance(lyr, QgsVectorLayer):
print(lyr.name(), apply_stored_style(lyr, f"{lyr.name()} print"))
Breakdown: Restricting the search to the first count entries — styles for this table — avoids applying a same-named style from another table. getStyleFromDatabase returns the QML text; importNamedStyle applies it to the layer. Naming styles with a convention, such as "layer print" and "layer overview", lets one loop switch a whole project between looks, which is useful before batch exports of print layouts.
Copy styles between databases
Organisations often keep a reference database of approved styles and apply them to new databases as they are created — a fresh GeoPackage for each project, a new PostGIS schema for each year. Copying layer_styles rows between databases moves the whole style set in one step.
src = md.createConnection("/data/styles/reference_styles.gpkg", {})
dst = md.createConnection("/data/projects/2027/project.gpkg", {})
rows = src.executeSql("SELECT f_table_name, f_geometry_column, stylename, styleqml, stylesld, "
"useasdefault, description FROM layer_styles")
dst_tables = {t.tableName() for t in dst.tables()}
copied = 0
for table, geom, name, qml, sld, default, desc in rows:
if table not in dst_tables:
continue
lyr = QgsVectorLayer(f"/data/projects/2027/project.gpkg|layername={table}", table, "ogr")
doc = QDomDocument()
doc.setContent(qml)
lyr.importNamedStyle(doc)
lyr.saveStyleToDatabase(name, desc or "", bool(default), "")
copied += 1
print(copied, "styles copied")
Breakdown: Reading styles from the reference database and saving them through each destination layer — rather than inserting rows with SQL — lets QGIS fill in the destination's own table and geometry column references correctly. Tables without a matching table in the destination are skipped, so one reference database can serve projects that use only some of its layers. The same loop works between GeoPackage and PostGIS in either direction, since both are reached through connections and layers.
Style a whole delivery
When sending a GeoPackage to someone else, storing default styles for every table means they see it exactly as intended, in any QGIS, without a project file.
from pathlib import Path
delivery = "/data/delivery/district_plan_2026.gpkg"
styles_dir = Path("/data/styles/district_plan")
for sub in QgsProviderRegistry.instance().providerMetadata("ogr").querySublayers(delivery):
lyr = QgsVectorLayer(sub.uri(), sub.name(), "ogr")
qml = styles_dir / f"{sub.name()}.qml"
if lyr.isValid() and qml.exists():
lyr.loadNamedStyle(str(qml))
err = lyr.saveStyleToDatabase(f"{sub.name()} default", "District plan 2026", True, "")
print(sub.name(), "styled" if not err else err)
Breakdown: Each table in the GeoPackage gets the QML maintained for it, saved into the package as its default style. Recipients who drag the GeoPackage into QGIS see every layer styled immediately. Keeping the QML files under version control and writing them into deliveries with a script means the styles in deliveries are always the reviewed ones, not whatever happened to be on someone's screen.
QGIS version compatibility
saveStyleToDatabase, listStylesInDatabase, getStyleFromDatabase and default-style loading work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4 for GeoPackage, PostGIS, SpatiaLite, MS SQL and Oracle sources. The return value of saveStyleToDatabase changed to an error string in the 3.x series; check if not error rather than truthiness of a boolean. Styles saved by newer QGIS versions may contain elements older versions ignore.
Troubleshooting
- The style does not load automatically. It was not saved as default, or the default belongs to a different geometry column.
- Saving fails on PostGIS. The role cannot create or write
public.layer_styles; grant the rights or ask an administrator to create the table. - Styles seem to belong to the wrong table. Two tables share a name in different schemas; check
f_table_schema. - Shapefile styles cannot be saved. Shapefiles have no style storage; save a QML or convert to GeoPackage.
Conclusion
Save styles into GeoPackage or PostGIS with saveStyleToDatabase, mark one default per table so it loads everywhere, keep alternatives as named styles, manage layer_styles with SQL when needed, switch projects between looks by loading stored styles by name, and write reviewed QML styles into deliveries so recipients see the data as intended.
Frequently Asked Questions
Do database styles include labels and forms? Yes — the stored QML includes everything a QML file does, unless saved with restricted categories.
Can QGIS Server use database styles? Yes; default styles apply when the server loads the table, and named styles can be requested as WMS styles.
What happens if both a QML file and a database default exist? The database default is used when loading from the database; a sidecar QML is used for file-based formats.
Can I edit a stored style with SQL? You can update the QML text, but it is safer to load, change and save through QGIS.