Read the PyQGIS API Docs and C++ Signatures
QGIS is written in C++, and Python reaches it through generated bindings. The PyQGIS documentation is generated from the same sources, so method signatures, types and notes are written with C++ in mind — const QgsFeatureRequest &request = QgsFeatureRequest(), bool *ok = nullptr, QgsVectorLayer::EditResult. Once you know how a handful of C++ conventions map to Python, the documentation becomes the fastest way to answer "what can this class do?" — faster than searching for examples that may be outdated.
This recipe belongs to QGIS API Architecture. It explains how to read a signature, how pointers, references and const translate, how out-parameters become returned tuples, how enums and flags are written in Python, how signals appear, and where Python-specific notes hide.
Prerequisites
- Access to the PyQGIS API reference for your version (qgis.org/pyqgis/) and, when needed, the C++ reference (api.qgis.org), which often has fuller descriptions.
- A Python console to try calls as you read.
Read a method signature
Every method entry gives a return type, a name, typed parameters with optional defaults, and qualifiers. Most of it maps directly to Python.
from qgis.core import QgsProject, QgsFeatureRequest
layer = QgsProject.instance().mapLayersByName("districts")[0]
# C++: QgsFeatureIterator getFeatures(const QgsFeatureRequest &request = QgsFeatureRequest()) const
it_all = layer.getFeatures() # default argument used
it_some = layer.getFeatures(QgsFeatureRequest().setLimit(5)) # explicit argument
# C++: QgsFeature getFeature(QgsFeatureId fid) const
f = layer.getFeature(3)
print(type(it_all).__name__, f.isValid())
help(layer.getFeatures) # the docstring shows the Python signature
Breakdown: const, & and * describe how C++ passes and protects values; in Python they disappear — you pass the object. Parameters with = default become optional. Type names map to Python classes of the same name, and simple types map to Python types: QString is str, double is float, int and qint64 are int, bool is bool, QgsFeatureId is int. A trailing const on the method means it does not modify the object. help() on a bound method prints the docstring generated from the same documentation, which is often quicker than the website for a quick check.
Pointers, ownership and "transfers ownership"
Pointers in signatures — QgsVectorLayer *layer, QgsSymbol *symbol — mean the object is passed by address. The documentation's ownership notes then matter: they say who deletes the object later.
from qgis.core import QgsMarkerSymbol, QgsSingleSymbolRenderer
# C++: QgsSingleSymbolRenderer(QgsSymbol *symbol SIP_TRANSFER)
symbol = QgsMarkerSymbol.createSimple({"color": "#2563eb"})
renderer = QgsSingleSymbolRenderer(symbol) # renderer now owns symbol
# C++: void setRenderer(QgsFeatureRenderer *r SIP_TRANSFER)
layer_pts = QgsProject.instance().mapLayersByName("stations")[0]
layer_pts.setRenderer(renderer) # layer now owns renderer
# do not reuse `symbol` for another renderer; clone it instead
other = QgsSingleSymbolRenderer(symbol.clone())
Breakdown: "Ownership of … is transferred" (shown as SIP_TRANSFER in sources) means the receiving object will delete it; after the call, the Python variable still refers to the object but you must not give it to anything else, or two owners will try to delete it — a classic crash. clone() makes an independent copy for reuse. Return values marked as "caller takes ownership" (SIP_FACTORY) are yours to keep. The full picture of ownership and its crashes is in object ownership and crashes.
Out-parameters become returned tuples
C++ methods often return extra values through pointer or reference parameters: bool *ok, QString &error. In Python those parameters disappear from the call, and the values come back as a tuple.
from qgis.core import QgsExpression
# C++: static bool checkExpression(const QString &text, const QgsExpressionContext *context,
# QString &errorMessage)
ok, message = QgsExpression.checkExpression('"name" ||', None)
print(ok, message)
from qgis.core import QgsGeometry, QgsPointXY
# C++: double closestSegmentWithContext(const QgsPointXY &point, QgsPointXY &minDistPoint,
# int &nextVertexIndex, int *leftOf = nullptr, double epsilon = ...) const
line = QgsGeometry.fromWkt("LineString (0 0, 10 0)")
sqr_dist, nearest, next_vertex, left_of = line.closestSegmentWithContext(QgsPointXY(3, 2))
print(sqr_dist, nearest, next_vertex, left_of)
Breakdown: checkExpression returns its boolean result first, then the error message the C++ version writes into its reference parameter. closestSegmentWithContext shows the same rule with several outs: the squared distance it returns, then the nearest point, the index of the next vertex and the side of the line, all of which C++ writes into reference and pointer parameters. The pattern is consistent across the API: return value first, then out-parameters in order. When a method has no return value but has out-parameters, Python returns just the out-parameters. The Python docstring — from help() or the PyQGIS reference — shows the tuple shape explicitly, so read it rather than guessing from the C++ signature.
Enums and flags
Enums appear in the docs as QgsWkbTypes::Type, Qgis::GeometryType, QgsVectorLayer::EditResult. In Python, :: becomes ., and recent releases use scoped names.
from qgis.core import Qgis, QgsWkbTypes, QgsMapLayer
print(Qgis.GeometryType.Polygon) # scoped enum (QGIS 3.30+ and QGIS 4)
print(QgsWkbTypes.displayString(Qgis.WkbType.MultiPolygon))
# flags combine with |
categories = QgsMapLayer.StyleCategory.Symbology | QgsMapLayer.StyleCategory.Labeling
print(bool(categories & QgsMapLayer.StyleCategory.Labeling))
Breakdown: Many enums moved to the Qgis class during the 3.2x–3.3x series, and QGIS 4 requires the scoped form Class.EnumName.Value. The documentation of each enum notes when it moved and lists the old name; the old unscoped names still work on 3.x LTRs, so code using the new names runs on both. Flags are enums designed to combine with | and test with &. When an enum value is an integer in older code — TYPE: 0 in Processing parameters, for example — the docs list the values in order.
Signals and slots
Signals appear in their own section of a class's documentation. In Python, a signal is an attribute you connect a function to; its C++ parameters become the arguments your function receives.
# C++ signal: void featureAdded(QgsFeatureId fid)
def on_added(fid):
print("feature added:", fid)
layer.featureAdded.connect(on_added)
# C++ signal: void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
layer.attributeValueChanged.connect(lambda fid, idx, value: print(fid, idx, value))
Breakdown: The signal's parameter list tells you what your handler receives, in order — here a feature id, then for attribute changes the field index and new value. Overloaded signals with several signatures are selected with square-bracket indexing in PyQt, which the docs show when it applies. Disconnect handlers when you no longer need them, especially in plugins, as explained in understanding QGIS signals and slots.
Find the Python-specific notes
Some methods behave differently in Python, and the documentation says so in notes that are easy to miss: "Not available in Python bindings", "In Python, this method returns…", "Since QGIS 3.x". Checking for them saves long debugging sessions.
import qgis.core as core
def api_info(cls_name, method):
cls = getattr(core, cls_name, None)
if cls is None:
return f"{cls_name} not in qgis.core"
m = getattr(cls, method, None)
if m is None:
return f"{cls_name}.{method} not available in Python (check version or bindings notes)"
return (m.__doc__ or "").strip().splitlines()[0]
print(api_info("QgsVectorLayer", "getFeatures"))
print(api_info("QgsGeometry", "asWkb"))
Breakdown: Checking that a class and method exist in the running bindings catches the two common cases: a method added in a newer QGIS than the one installed, and a C++ method deliberately not exposed to Python. The first line of the docstring is the Python signature, often more useful than the C++ one. Notes marked "since QGIS 3.x" tell you the minimum version — important for plugins that declare a minimum QGIS version, as in the Python version compatibility guide. Exploring with dir() and help() is covered in exploring the PyQGIS API with dir and help.
QGIS version compatibility
The documentation is versioned: qgis.org/pyqgis/3.34/ and later each describe their release. Read the version you target, and use the master documentation only for upcoming features. The mapping rules here — const and references vanish, out-parameters become tuples, :: becomes . — are stable across QGIS 3 and QGIS 4; QGIS 4 additionally requires scoped enum names.
Troubleshooting
TypeError: arguments did not match any overloaded call. An argument has the wrong type; compare with the Python signature inhelp().- A method returns a tuple you did not expect. It has out-parameters; unpack them.
AttributeErrorfor a documented method. The method is newer than your QGIS, or not exposed to Python; check the version note.- Crashes after passing objects around. Ownership was transferred; clone before reusing.
Conclusion
Read signatures by dropping const, & and *, treat defaults as optional arguments, watch ownership notes and clone transferred objects before reuse, unpack out-parameters from returned tuples, write enums with . and scoped names, connect signals with handlers matching their parameters, and check Python notes and version markers before relying on a method.
Frequently Asked Questions
Should I read the C++ or the Python docs? Start with the PyQGIS docs for Python signatures; consult the C++ docs for fuller descriptions and class diagrams.
What does SIP_SKIP mean in sources?
The method is not available in Python.
Are Qt classes documented on the QGIS site?
No — use the Qt documentation (PyQt or Qt for Python) for QColor, QDate and other Qt classes.
How do I find which class has a method I need?
Search the docs, or dir() likely classes in the console; the class hierarchy pages show inherited methods.