Add a Field to a Layer in PyQGIS
Adding a column is the first step of almost every attribute workflow, and it fails in the same two ways for almost everyone: the field is created but the layer insists it does not exist, or the type turns out to be wrong once real values arrive. Both are avoidable with two extra lines — one to refresh the layer's schema cache, and one to check the provider can do what you are asking before you ask.
This page is a focused recipe within Attribute Tables and Field Management in PyQGIS. It covers adding one or several fields, choosing types, making the script idempotent, and the format-specific limits that bite when the target is a shapefile.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- A writable vector layer. GeoPackage is the recommended target; a WFS or delimited-text layer will reject the operation.
- Familiarity with the Python Console — see QGIS Python Console Basics.
Add a single field
Three lines, and the third is the one people forget.
from qgis.core import QgsField, QgsProject
from qgis.PyQt.QtCore import QVariant
layer = QgsProject.instance().mapLayersByName("parcels")[0]
layer.dataProvider().addAttributes([QgsField("value_per_m2", QVariant.Double)])
layer.updateFields()
print(layer.fields().names())
Breakdown: addAttributes() takes a list, even for one field, and writes straight to the data source with no undo. QVariant.Double is a Qt type rather than a Python one — the mapping is given in the table below. updateFields() re-reads the schema into the layer object; without it, layer.fields() keeps returning the pre-change list and every lookup by name fails with a confusing -1.
Choose the right type
The type is fixed once the field exists, and changing it later means creating a new field and copying values across. It is worth a moment's thought.
| Data | Qt type | Notes |
|---|---|---|
| Whole numbers | QVariant.Int | 32-bit; use QVariant.LongLong for identifiers above two billion |
| Decimals | QVariant.Double | Set prec for the number of decimal places the provider stores |
| Text | QVariant.String | Set len on formats that require it; a shapefile caps names at 10 characters |
| True/false | QVariant.Bool | Supported by GeoPackage and PostGIS; a shapefile stores it as an integer |
| A date | QVariant.Date | Not available in shapefile with a time component — use QVariant.DateTime on GeoPackage |
from qgis.core import QgsField
from qgis.PyQt.QtCore import QVariant
fields = [
QgsField("parcel_ref", QVariant.String, len=32),
QgsField("value_per_m2", QVariant.Double, len=12, prec=2),
QgsField("surveyed_on", QVariant.Date),
QgsField("is_exempt", QVariant.Bool),
]
layer.dataProvider().addAttributes(fields)
layer.updateFields()
Breakdown: Adding several fields in one addAttributes() call is a single provider round-trip rather than four, which matters on a network database. len and prec are honoured by providers that store them and ignored by those that do not, so setting them costs nothing and helps where it counts. Note prec=2 on a Double means the stored precision — it does not round values you write, it declares how many decimals the column keeps.
Make it safe to re-run
A script that fails the second time it runs is a script nobody trusts. Guard both the capability and the existence.
from qgis.core import QgsField, QgsProject, QgsVectorDataProvider
from qgis.PyQt.QtCore import QVariant
WANTED = {
"value_per_m2": (QVariant.Double, 12, 2),
"rate_band": (QVariant.String, 4, 0),
}
layer = QgsProject.instance().mapLayersByName("parcels")[0]
provider = layer.dataProvider()
if not provider.capabilities() & QgsVectorDataProvider.AddAttributes:
raise RuntimeError(f"{layer.name()} does not support adding fields")
existing = set(layer.fields().names())
to_add = [
QgsField(name, qtype, len=length, prec=precision)
for name, (qtype, length, precision) in WANTED.items()
if name not in existing
]
if to_add:
provider.addAttributes(to_add)
layer.updateFields()
print("added:", [f.name() for f in to_add])
else:
print("nothing to add")
Breakdown: The capability check turns a silent no-op into an explicit failure with the layer's name in it — far easier to diagnose than discovering later that nothing was written. Building to_add from a set difference means the script is idempotent: run it ten times and the schema is identical. Declaring the wanted schema as a dictionary at the top also documents it, which matters when the script is the only record of what the table should look like.
Populate the new field
A field with no values is NULL everywhere. Filling it in the same pass keeps the schema and data in step.
index = layer.fields().indexOf("value_per_m2")
updates = {}
for feature in layer.getFeatures():
area = feature.geometry().area()
value = feature["assessed_value"]
if area and value is not None:
updates[feature.id()] = {index: round(value / area, 2)}
layer.dataProvider().changeAttributeValues(updates)
Breakdown: Resolving the index after updateFields() is essential — before it, indexOf() returns -1 and changeAttributeValues() silently writes nothing. The dictionary maps feature ID to a dictionary of field index to value, which is the shape the provider expects, and building it first turns n round-trips into one. The guard skips features with no geometry or a NULL value rather than writing garbage. See Update Attribute Values in Bulk with PyQGIS for the transactional alternative.
Format limits worth knowing
The provider decides what is possible, and the differences are large enough to change which format you choose. A script that works perfectly against GeoPackage can quietly mangle the same schema written to a shapefile.
If a shapefile is unavoidable, verify the names actually survived rather than assuming they did:
WANTED = ["value_per_square_metre", "surveyed_on"]
layer.dataProvider().addAttributes([...])
layer.updateFields()
actual = layer.fields().names()
for name in WANTED:
if name not in actual:
close = [a for a in actual if a.startswith(name[:8])]
print(f"'{name}' became {close!r}")
Breakdown: Comparing the requested names against layer.fields().names() after the fact is the only reliable check, because addAttributes() reports success even when the driver renames what it stored. Matching on a truncated prefix identifies what the name became, which is what any downstream code will have to reference. The cleaner answer, wherever you have the choice, is to write GeoPackage — the format costs nothing extra and removes all four limitations at once.
QGIS version compatibility
The examples target QGIS 3.34 LTR (Python 3.12).
| QGIS version | Python | Notes |
|---|---|---|
| 3.28 LTR | 3.9 | Identical API. QVariant types unchanged. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40 / 3.44 | 3.12 | QgsField accepts QMetaType alongside QVariant; the QVariant form still works. |
addAttributes(), updateFields() and the capability flags have been stable across 3.x.
Troubleshooting
indexOf()returns -1 for a field that exists.updateFields()was not called afteraddAttributes().addAttributes()returns False. The provider does not support it. Checkcapabilities() & QgsVectorDataProvider.AddAttributesand confirm the file is not read-only on disk.- The field name was truncated. The target is a shapefile, which caps field names at ten characters. Write to GeoPackage instead.
- Values come back as text when the field is numeric. The field was created as
QVariant.String. Types cannot be changed in place — add a correctly typed field, copy the values across, and drop the old one. - A date field rejects values. Assign a Python
datetime.date, or aQDate, not a string. A string only works if the provider silently coerces it, which not all do. - The script fails on its second run. No existence check. Filter the wanted fields against
layer.fields().names()before adding.
Conclusion
Adding a field is addAttributes() followed by updateFields(), with a capability check in front and an existence check to keep the script re-runnable. Spend the extra moment on the type — it is the one decision you cannot revise without rebuilding the column — and prefer GeoPackage over shapefile so names and types survive intact.
Frequently Asked Questions
Why does my new field not show up?
The provider has it but the layer object is serving a cached schema. Call layer.updateFields() straight after addAttributes() and lookups by name will resolve.
Which QVariant type should I use for decimals?QVariant.Double, with len and prec set if the format stores them. prec declares how many decimal places the column keeps; it does not round the values you write.
Can I change a field's type after creating it?
Not in place. Add a new field with the correct type, copy the values across with changeAttributeValues(), then delete the original — remembering to delete by descending index if you remove several.
Can I add several fields in one call?
Yes, and you should — addAttributes() takes a list, so passing four QgsField objects together is one provider round-trip instead of four. Call updateFields() once afterwards.
Do I need to start an edit session to add a field?
No, if you go through layer.dataProvider().addAttributes(), which writes immediately. Use layer.startEditing() and layer.addAttribute() only when the change should be undoable, such as inside a plugin.
Is there a limit on how many fields a layer can have? Format-dependent. A shapefile caps out at 255 attribute fields, GeoPackage allows far more, and PostGIS effectively has no practical limit. A table approaching any of those limits is usually better normalised into related tables than widened further.