
Qgis Syntax Expressions
- 8 installs
- 29 repo stars
- Updated July 8, 2026
- openaec-foundation/qgis-claude-skill-package
Helps with ai & agent building tasks.
About
qgis-syntax-expressions is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- qgis-syntax-expressions
- AI & Agent Building
- AI-coding skill
Qgis Syntax Expressions by the numbers
- 8 all-time installs (skills.sh)
- Ranked #12,339 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/qgis-claude-skill-package --skill qgis-syntax-expressionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 29 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/qgis-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
qgis-syntax-expressions
Quick Reference
Expression Evaluation Pipeline
| Step | Class | Purpose |
|---|---|---|
| 1. Parse | QgsExpression(string) | Validate syntax, build AST |
| 2. Context | QgsExpressionContext() | Provide variables, fields, scopes |
| 3. Scopes | QgsExpressionContextUtils | Add global/project/layer/feature scopes |
| 4. Evaluate | exp.evaluate(context) | Execute and return result |
| 5. Validate | exp.hasEvalError() | Check for runtime errors |
Expression Syntax Cheatsheet
| Element | Syntax | Example |
|---|---|---|
| Field reference | "field_name" (double quotes) | "population" |
| String literal | 'text' (single quotes) | 'hello world' |
| Number literal | 123, 3.14 | 42 |
| NULL check | IS NULL, IS NOT NULL | "name" IS NOT NULL |
| Comparison | =, !=, >, >=, <, <= | "area" > 100 |
| Logical | AND, OR, NOT | "pop" > 1000 AND "type" = 'city' |
| Arithmetic | +, -, *, /, ^, % | "population" * 1.05 |
| Pattern match | LIKE, ILIKE, ~ (regex) | "name" ILIKE '%lake%' |
| Concatenation | `\ | \ |
| Conditional | CASE WHEN ... THEN ... END | CASE WHEN "pop" > 1e6 THEN 'large' ELSE 'small' END |
| Geometry vars | $area, $length, $x, $y | $area / 1e6 |
| Current geometry | $geometry | num_points($geometry) |
Common Expression Functions
| Category | Functions |
|---|---|
| Math | sqrt(), abs(), round(), floor(), ceil(), sin(), cos(), pi() |
| String | upper(), lower(), length(), trim(), replace(), regexp_replace(), substr(), left(), right() |
| Conversion | to_int(), to_real(), to_string(), to_date(), to_datetime() |
| Date/Time | now(), day(), month(), year(), hour(), minute(), age(), day_of_week() |
| Geometry | $area, $length, $x, $y, $perimeter, centroid(), buffer(), area(), length(), num_geometries(), num_points() |
| Aggregates | aggregate(), sum(), count(), mean(), min(), max(), concatenate(), array_agg() |
| Conditionals | if(), coalesce(), nullif(), try() |
| Arrays | array(), array_length(), array_contains(), array_append(), array_to_string() |
| Map/Record | map(), map_get(), hstore_to_map() |
| Color | color_rgb(), color_hsv(), ramp_color(), darker(), lighter() |
---
Critical Warnings
ALWAYS check exp.hasParserError() after creating a QgsExpression. A parser error means the expression string is syntactically invalid and evaluation will fail silently or return NULL.
ALWAYS check exp.hasEvalError() after calling exp.evaluate(). Evaluation errors occur when the expression is syntactically valid but fails at runtime (e.g., field not found, type mismatch).
ALWAYS set up a proper QgsExpressionContext with appropriate scopes when evaluating expressions that reference fields, project variables, or layer properties. Evaluating without context returns NULL for all field references.
NEVER use double quotes for string literals in expressions. Double quotes reference field names: "name" reads the field, 'name' is the string literal.
NEVER forget to call context.setFeature(feature) before evaluating feature-dependent expressions in a loop. Omitting this causes all features to evaluate against stale or empty data.
NEVER use print() inside custom expression functions (@qgsfunction). Use QgsMessageLog instead. Expression functions run during rendering and print() causes thread-safety issues.
ALWAYS specify referenced_columns in @qgsfunction for performance. Use [QgsFeatureRequest.ALL_ATTRIBUTES] if the function reads arbitrary fields; use [] if it reads no fields.
ALWAYS set usesgeometry=True in @qgsfunction if the function accesses feature.geometry(). Omitting this causes the geometry to be unavailable.
---
Decision Tree: Choosing the Right Expression Approach
Need to use an expression?
|
+-- Filter features? --> QgsFeatureRequest.setFilterExpression()
|
+-- Calculate field values? --> Field Calculator pattern (edit session + evaluate per feature)
|
+-- Drive labeling? --> QgsPalLayerSettings.fieldName + isExpression = True
|
+-- Drive symbology? --> QgsProperty.fromExpression() on symbol/renderer properties
|
+-- Evaluate standalone? --> QgsExpression.evaluate(context)
|
+-- Need custom logic? --> @qgsfunction decorator + QgsExpression.registerFunction()---
Essential Patterns
Pattern 1: Parse and Evaluate an Expression
from qgis.core import (
QgsExpression, QgsExpressionContext, QgsExpressionContextUtils
)
exp = QgsExpression('"population" * 1.05')
if exp.hasParserError():
raise ValueError(f"Parse error: {exp.parserErrorString()}")
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
for feature in layer.getFeatures():
context.setFeature(feature)
value = exp.evaluate(context)
if exp.hasEvalError():
raise ValueError(f"Eval error: {exp.evalErrorString()}")
print(f"Feature {feature.id()}: {value}")Pattern 2: Expression-Based Feature Filtering
from qgis.core import QgsFeatureRequest
# Method A: expression string directly
request = QgsFeatureRequest().setFilterExpression('"population" >= 50000')
features = list(layer.getFeatures(request))
# Method B: QgsExpression object
exp = QgsExpression('"name" ILIKE \'%lake%\'')
request = QgsFeatureRequest(exp)
features = list(layer.getFeatures(request))Pattern 3: Field Calculator (Update Attributes)
from qgis.core import (
edit, QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils
)
exp = QgsExpression('"population" * 2')
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
with edit(layer):
for f in layer.getFeatures():
context.setFeature(f)
f['doubled_pop'] = exp.evaluate(context)
layer.updateFeature(f)Pattern 4: Expression-Based Labeling
from qgis.core import QgsPalLayerSettings, QgsVectorLayerSimpleLabeling
settings = QgsPalLayerSettings()
settings.fieldName = '"name" || \' (\' || "population" || \')\''
settings.isExpression = True
settings.enabled = True
text_format = settings.format()
text_format.setSize(12)
settings.setFormat(text_format)
labeling = QgsVectorLayerSimpleLabeling(settings)
layer.setLabeling(labeling)
layer.setLabelsEnabled(True)
layer.triggerRepaint()Pattern 5: Data-Defined Symbology Properties
from qgis.core import QgsProperty
symbol = layer.renderer().symbol()
# Size driven by expression
symbol.setDataDefinedSize(
QgsProperty.fromExpression('"population" / 1000')
)
# Color driven by expression
symbol.setDataDefinedColor(
QgsProperty.fromExpression(
"CASE WHEN \"type\" = 'city' THEN '#ff0000' ELSE '#0000ff' END"
)
)
layer.triggerRepaint()Pattern 6: Custom Expression Function
from qgis.core import qgsfunction, QgsExpression
@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def population_density(population, area_km2, feature, parent):
"""
Calculate population density per square kilometer.
<p>Usage: population_density("pop_field", "area_field")</p>
"""
if area_km2 and area_km2 > 0:
return population / area_km2
return None
# Register (call once, e.g., in plugin initGui)
QgsExpression.registerFunction(population_density)
# Now usable: population_density("population", "area")
# Unregister (call in plugin unload)
QgsExpression.unregisterFunction('population_density')---
Context Scope Hierarchy
Scopes are stacked from generic to specific. When variable names collide, the most specific scope wins:
Global scope → @qgis_version, @user_full_name, custom global vars
Project scope → @project_title, @project_crs, custom project vars
Layer scope → @layer_name, @layer_id, layer fields
Feature scope → Feature attributes, $geometry, $id, $currentfeatureAdding Custom Variables
from qgis.core import QgsExpressionContextUtils
# Set global variable (persists across sessions)
QgsExpressionContextUtils.setGlobalVariable('my_threshold', 42)
# Set project variable (saved with project)
QgsExpressionContextUtils.setProjectVariable(
QgsProject.instance(), 'analysis_year', 2024
)
# Set layer variable (saved with layer in project)
QgsExpressionContextUtils.setLayerVariable(layer, 'source_date', '2024-01-15')
# Access in expressions: @my_threshold, @analysis_year, @source_date---
Aggregate Expressions
Aggregates compute values across features within a layer:
# In expression strings:
# aggregate('layer_name', 'sum', "population")
# aggregate('layer_name', 'mean', "area", filter:="type" = 'residential')
# Common shorthand (current layer):
# sum("population")
# count("id", filter:="active" = 1)
# mean("temperature", group_by:="region")ALWAYS use the filter parameter in aggregates to limit the scope. Aggregating an entire large layer without a filter is a performance bottleneck.
---
Reference Links
- references/methods.md -- API signatures for QgsExpression, QgsExpressionContext, QgsExpressionContextUtils, @qgsfunction
- references/examples.md -- Complete expression examples for filtering, labeling, symbology, field calculator
- references/anti-patterns.md -- Expression pitfalls and incorrect patterns
Official Sources
- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/expressions.html
- https://docs.qgis.org/latest/en/docs/user_manual/expressions/expression.html
- https://qgis.org/pyqgis/master/core/QgsExpression.html
- https://qgis.org/pyqgis/master/core/QgsExpressionContext.html
Anti-Patterns (QGIS Expression Engine)
1. Missing Parser Error Check
# WRONG: No error check after parsing
exp = QgsExpression('"nonexistent_field" + ') # syntax error
result = exp.evaluate(context) # Returns NULL silently
# CORRECT: ALWAYS check for parser errors
exp = QgsExpression('"nonexistent_field" + ')
if exp.hasParserError():
raise ValueError(f"Expression parse error: {exp.parserErrorString()}")WHY: QgsExpression does NOT raise exceptions on parse failure. It stores the error internally. Without checking, invalid expressions silently return NULL, making bugs invisible.
---
2. Missing Evaluation Error Check
# WRONG: Evaluate without error check
exp = QgsExpression('"population" / "area"')
value = exp.evaluate(context)
# If area is 0, division error occurs silently
# CORRECT: ALWAYS check for evaluation errors
value = exp.evaluate(context)
if exp.hasEvalError():
raise ValueError(f"Eval error: {exp.evalErrorString()}")WHY: Division by zero, type mismatches, and missing fields cause evaluation errors that return NULL. Without checking, calculations silently produce incorrect results.
---
3. Evaluating Without Context
# WRONG: No context when expression references fields
exp = QgsExpression('"population" * 2')
result = exp.evaluate() # Returns NULL — no fields available
# CORRECT: ALWAYS provide context with appropriate scopes
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
context.setFeature(feature)
result = exp.evaluate(context)WHY: Without a context, field references ("population") resolve to NULL. The expression engine has no way to know which layer or feature to read from.
---
4. Forgetting to Set Feature in Loop
# WRONG: Feature never set — all iterations use stale/empty data
exp = QgsExpression('"value" * 2')
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
for feature in layer.getFeatures():
result = exp.evaluate(context) # Same NULL/stale result every iteration
# CORRECT: ALWAYS set feature before evaluating
for feature in layer.getFeatures():
context.setFeature(feature)
result = exp.evaluate(context)WHY: The context does not automatically track which feature is current. You MUST call setFeature() for each feature in the loop.
---
5. Confusing Single and Double Quotes
# WRONG: Double quotes for string literal — reads field named "residential"
exp = QgsExpression('"type" = "residential"')
# This compares field "type" with field "residential" (probably NULL)
# CORRECT: Single quotes for string literals
exp = QgsExpression('"type" = \'residential\'')
# Or use Python's quoting to avoid escaping:
exp = QgsExpression("\"type\" = 'residential'")WHY: In QGIS expressions, double quotes ("...") ALWAYS reference field names. Single quotes ('...') ALWAYS denote string literals. Mixing them up causes silent mismatches.
---
6. Modifying Features Without Edit Session
# WRONG: Field calculator without edit session — changes are lost
exp = QgsExpression('"population" * 2')
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
for f in layer.getFeatures():
context.setFeature(f)
f['doubled'] = exp.evaluate(context)
layer.updateFeature(f) # Fails silently — no edit session
# CORRECT: ALWAYS wrap in edit session
with edit(layer):
for f in layer.getFeatures():
context.setFeature(f)
f['doubled'] = exp.evaluate(context)
layer.updateFeature(f)WHY: updateFeature() requires an active edit session. Without edit(layer) or startEditing(), changes are silently discarded.
---
7. Custom Function Missing referenced_columns
# WRONG: Empty referenced_columns but function reads fields
@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def classify(value, feature, parent):
# Reads 'category' field directly — but QGIS does not know this
return feature['category'] + '_' + str(value)
# CORRECT: Declare all accessed fields
@qgsfunction(
args='auto',
group='Custom',
referenced_columns=['category']
)
def classify(value, feature, parent):
return feature['category'] + '_' + str(value)
# Or use ALL_ATTRIBUTES if fields are dynamic
@qgsfunction(
args='auto',
group='Custom',
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES]
)
def classify(value, feature, parent):
return feature['category'] + '_' + str(value)WHY: QGIS uses referenced_columns to optimize which fields are loaded. If a field is accessed but not declared, it may be NULL because it was not fetched from the data provider.
---
8. Custom Function Missing usesgeometry Flag
# WRONG: Accesses geometry but does not declare it
@qgsfunction(args=0, group='Custom', referenced_columns=[])
def my_area(feature, parent):
return feature.geometry().area() # geometry may be NULL
# CORRECT: ALWAYS set usesgeometry=True when accessing geometry
@qgsfunction(
args=0,
group='Custom',
referenced_columns=[],
usesgeometry=True
)
def my_area(feature, parent):
geom = feature.geometry()
if geom.isNull():
return None
return geom.area()WHY: When usesgeometry=False (default), QGIS may skip loading geometries for performance. The function then receives a NULL geometry.
---
9. Using print() in Custom Expression Functions
# WRONG: print() in expression function causes thread-safety issues
@qgsfunction(args='auto', group='Debug', referenced_columns=[])
def debug_value(val, feature, parent):
print(f"Debug: {val}") # thread-unsafe during rendering
return val
# CORRECT: Use QgsMessageLog
from qgis.core import QgsMessageLog, Qgis
@qgsfunction(args='auto', group='Debug', referenced_columns=[])
def debug_value(val, feature, parent):
QgsMessageLog.logMessage(f"Debug: {val}", 'Custom', Qgis.Info)
return valWHY: Expression functions execute during rendering, which runs on multiple threads. print() is not thread-safe and causes crashes or garbled output.
---
10. Not Unregistering Custom Functions on Plugin Unload
# WRONG: Register in initGui but forget to unregister
class MyPlugin:
def initGui(self):
QgsExpression.registerFunction(my_custom_func)
def unload(self):
pass # Stale function reference remains
# CORRECT: ALWAYS unregister in unload()
class MyPlugin:
def initGui(self):
QgsExpression.registerFunction(my_custom_func)
def unload(self):
QgsExpression.unregisterFunction('my_custom_func')WHY: Registered functions persist globally. If the plugin is reloaded without unregistering, the old function reference becomes stale and causes errors or crashes.
---
11. Inefficient Expression Evaluation Without prepare()
# INEFFICIENT: Expression re-parsed for every feature
exp = QgsExpression('sqrt("area") * 100 + length("name")')
for feature in layer.getFeatures():
context.setFeature(feature)
result = exp.evaluate(context)
# BETTER: Prepare once, evaluate many
exp = QgsExpression('sqrt("area") * 100 + length("name")')
exp.prepare(context) # Resolves field indices once
for feature in layer.getFeatures():
context.setFeature(feature)
result = exp.evaluate(context)WHY: prepare() resolves field name lookups and function bindings once. Without it, these lookups happen on every evaluate() call, which is measurably slower on large datasets.
---
12. Aggregate Expression Without Filter on Large Datasets
# WRONG: Aggregates entire layer on every feature evaluation
exp = QgsExpression(
'"population" / aggregate(\'cities\', \'sum\', "population")'
)
# This recomputes sum(population) for every single feature
# CORRECT: Pre-compute the aggregate or use a filter
# Option A: Pre-compute
total_exp = QgsExpression('aggregate(\'cities\', \'sum\', "population")')
total_exp.prepare(context)
total = total_exp.evaluate(context)
# Then use the value directly
for feature in layer.getFeatures():
context.setFeature(feature)
ratio = feature['population'] / total
# Option B: Use with filter to limit scope
exp = QgsExpression(
'aggregate(\'cities\', \'sum\', "population", '
'filter:="region" = attribute(@parent, \'region\'))'
)WHY: Unfiltered aggregates on large layers are recomputed for each feature evaluation. This turns O(n) operations into O(n^2).
---
13. Labeling Expression Without isExpression Flag
# WRONG: Expression string but isExpression not set
settings = QgsPalLayerSettings()
settings.fieldName = '"name" || \' (\' || "population" || \')\''
# settings.isExpression not set — defaults to False
# QGIS treats this as a literal field name, not an expression
# CORRECT: ALWAYS set isExpression = True for expression-based labels
settings = QgsPalLayerSettings()
settings.fieldName = '"name" || \' (\' || "population" || \')\''
settings.isExpression = TrueWHY: QgsPalLayerSettings defaults to treating fieldName as a simple field reference. Without isExpression = True, the expression is interpreted as a literal field name that does not exist, producing empty labels.
Working Code Examples (QGIS Expression Engine)
Example 1: Basic Expression Parsing and Evaluation
from qgis.core import QgsExpression
# Simple arithmetic (no context needed)
exp = QgsExpression('2 + 3 * 4')
if exp.hasParserError():
raise ValueError(exp.parserErrorString())
result = exp.evaluate() # Returns 14
# Boolean expression
exp = QgsExpression('1 + 1 = 2')
result = exp.evaluate() # Returns 1 (True)
# String expression
exp = QgsExpression("'Hello' || ' ' || 'World'")
result = exp.evaluate() # Returns 'Hello World'---
Example 2: Evaluate Expression Against Features
from qgis.core import (
QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils, QgsProject
)
layer = QgsProject.instance().mapLayersByName('cities')[0]
exp = QgsExpression('"population" / "area_km2"')
if exp.hasParserError():
raise ValueError(exp.parserErrorString())
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
# Prepare for repeated evaluation (performance optimization)
exp.prepare(context)
results = {}
for feature in layer.getFeatures():
context.setFeature(feature)
density = exp.evaluate(context)
if exp.hasEvalError():
raise ValueError(f"Feature {feature.id()}: {exp.evalErrorString()}")
results[feature['name']] = density
print(results)---
Example 3: Filter Features with Expression
from qgis.core import QgsFeatureRequest, QgsProject
layer = QgsProject.instance().mapLayersByName('buildings')[0]
# Filter by attribute
request = QgsFeatureRequest().setFilterExpression(
'"building_type" = \'residential\' AND "floors" >= 3'
)
tall_residential = list(layer.getFeatures(request))
print(f"Found {len(tall_residential)} tall residential buildings")
# Filter by geometry expression
request = QgsFeatureRequest().setFilterExpression(
'$area > 500'
)
large_buildings = list(layer.getFeatures(request))
# Combine with attribute subset for performance
request = QgsFeatureRequest().setFilterExpression(
'"status" = \'active\''
).setSubsetOfAttributes(['name', 'status'], layer.fields())
active = list(layer.getFeatures(request))---
Example 4: Field Calculator — Compute New Values
from qgis.core import (
edit, QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils, QgsProject, QgsField
)
from qgis.PyQt.QtCore import QVariant
layer = QgsProject.instance().mapLayersByName('parcels')[0]
# Add new field if it does not exist
if layer.fields().indexOf('density') == -1:
layer.dataProvider().addAttributes([
QgsField('density', QVariant.Double)
])
layer.updateFields()
exp = QgsExpression('"population" / ($area / 1000000)')
if exp.hasParserError():
raise ValueError(exp.parserErrorString())
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
exp.prepare(context)
field_idx = layer.fields().indexOf('density')
with edit(layer):
for f in layer.getFeatures():
context.setFeature(f)
value = exp.evaluate(context)
layer.changeAttributeValue(f.id(), field_idx, value)---
Example 5: Expression-Based Labeling
from qgis.core import (
QgsPalLayerSettings, QgsVectorLayerSimpleLabeling,
QgsTextFormat, QgsProject
)
from qgis.PyQt.QtGui import QFont, QColor
layer = QgsProject.instance().mapLayersByName('cities')[0]
# Configure label expression
settings = QgsPalLayerSettings()
settings.fieldName = (
'"name" || \'\\n\' || '
'format_number("population", 0) || \' inhabitants\''
)
settings.isExpression = True
settings.enabled = True
# Style the text
text_format = QgsTextFormat()
text_format.setFont(QFont('Arial', 10))
text_format.setColor(QColor('#333333'))
text_format.setSize(10)
settings.setFormat(text_format)
# Apply to layer
labeling = QgsVectorLayerSimpleLabeling(settings)
layer.setLabeling(labeling)
layer.setLabelsEnabled(True)
layer.triggerRepaint()---
Example 6: Expression-Based Labeling with Conditional Formatting
from qgis.core import (
QgsPalLayerSettings, QgsVectorLayerSimpleLabeling,
QgsProperty, QgsProject
)
layer = QgsProject.instance().mapLayersByName('cities')[0]
settings = QgsPalLayerSettings()
settings.fieldName = '"name"'
settings.isExpression = True
settings.enabled = True
# Data-defined font size based on population
settings.dataDefinedProperties().setProperty(
QgsPalLayerSettings.Property.Size,
QgsProperty.fromExpression(
'CASE '
'WHEN "population" > 1000000 THEN 16 '
'WHEN "population" > 100000 THEN 12 '
'ELSE 8 END'
)
)
# Data-defined color based on type
settings.dataDefinedProperties().setProperty(
QgsPalLayerSettings.Property.Color,
QgsProperty.fromExpression(
"CASE WHEN \"capital\" = 1 THEN '#cc0000' ELSE '#333333' END"
)
)
labeling = QgsVectorLayerSimpleLabeling(settings)
layer.setLabeling(labeling)
layer.setLabelsEnabled(True)
layer.triggerRepaint()---
Example 7: Data-Defined Symbology
from qgis.core import (
QgsProperty, QgsSingleSymbolRenderer,
QgsMarkerSymbol, QgsProject
)
layer = QgsProject.instance().mapLayersByName('stations')[0]
# Create symbol with data-defined properties
symbol = QgsMarkerSymbol.createSimple({
'name': 'circle',
'color': '0,0,255',
'size': '4'
})
# Size proportional to value
symbol.setDataDefinedSize(
QgsProperty.fromExpression(
'scale_linear("passenger_count", 0, 100000, 2, 20)'
)
)
# Color based on expression
symbol.setDataDefinedColor(
QgsProperty.fromExpression(
'ramp_color(\'RdYlGn\', scale_linear("score", 0, 100, 0, 1))'
)
)
renderer = QgsSingleSymbolRenderer(symbol)
layer.setRenderer(renderer)
layer.triggerRepaint()---
Example 8: Custom Expression Function
from qgis.core import (
qgsfunction, QgsExpression, QgsFeatureRequest, QgsMessageLog, Qgis
)
@qgsfunction(args='auto', group='Analysis', referenced_columns=['population', 'area_km2'])
def pop_density_class(population, area_km2, feature, parent):
"""
Classify population density into categories.
<h3>Usage</h3>
<p>pop_density_class("population", "area_km2")</p>
<h3>Returns</h3>
<p>'high', 'medium', or 'low'</p>
"""
if area_km2 is None or area_km2 <= 0:
return None
density = population / area_km2
if density > 1000:
return 'high'
elif density > 200:
return 'medium'
else:
return 'low'
# Register
QgsExpression.registerFunction(pop_density_class)
# Now usable in any expression:
# pop_density_class("population", "area_km2")---
Example 9: Custom Function Using Geometry
from qgis.core import qgsfunction, QgsExpression
@qgsfunction(
args=0,
group='Geometry',
referenced_columns=[],
usesgeometry=True
)
def vertex_count(feature, parent):
"""
Returns the total number of vertices in the feature geometry.
<p>Usage: vertex_count()</p>
"""
geom = feature.geometry()
if geom.isNull():
return 0
return len(geom.asGeometryCollection()) if geom.isMultipart() else geom.constGet().nCoordinates()
QgsExpression.registerFunction(vertex_count)---
Example 10: Expression Templates with [% %] Tags
from qgis.core import (
QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils, QgsProject
)
layer = QgsProject.instance().mapLayersByName('parcels')[0]
template = 'Parcel [% "parcel_id" %] has area [% round($area, 2) %] m2'
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
for feature in layer.getFeatures():
context.setFeature(feature)
text = QgsExpression.replaceExpressionText(template, context)
print(text)
# Output: "Parcel A-123 has area 542.31 m2"---
Example 11: Aggregate Expressions
from qgis.core import (
QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils, QgsProject
)
layer = QgsProject.instance().mapLayersByName('sales')[0]
# Total sales per region (using aggregate function)
exp = QgsExpression(
'aggregate(\'sales\', \'sum\', "amount", '
'filter:="region" = attribute(@parent, \'region\'))'
)
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
# Standalone aggregate without feature context
exp_total = QgsExpression('aggregate(\'sales\', \'sum\', "amount")')
exp_total.prepare(context)
total = exp_total.evaluate(context)
print(f"Total sales: {total}")---
Example 12: Using Expression Context with Custom Variables
from qgis.core import (
QgsExpression, QgsExpressionContext,
QgsExpressionContextScope, QgsExpressionContextUtils,
QgsProject
)
layer = QgsProject.instance().mapLayersByName('measurements')[0]
context = QgsExpressionContext()
context.appendScopes(
QgsExpressionContextUtils.globalProjectLayerScopes(layer)
)
# Add custom scope with analysis parameters
custom_scope = QgsExpressionContextScope('analysis')
custom_scope.setVariable('threshold', 50.0, isStatic=True)
custom_scope.setVariable('multiplier', 1.15, isStatic=True)
context.appendScope(custom_scope)
# Expression using custom variables
exp = QgsExpression('"value" * @multiplier > @threshold')
exp.prepare(context)
for feature in layer.getFeatures():
context.setFeature(feature)
if exp.evaluate(context):
print(f"Feature {feature.id()} exceeds threshold")API Signatures Reference (QGIS Expression Engine)
QgsExpression
The core class for parsing and evaluating QGIS expressions.
class QgsExpression:
def __init__(self, expr: str)
# Parse the expression string. Check hasParserError() after construction.
# --- Parsing ---
def hasParserError(self) -> bool
# Returns True if the expression string has syntax errors.
def parserErrorString(self) -> str
# Returns the parser error message. Empty string if no error.
# --- Evaluation ---
def evaluate(self, context: QgsExpressionContext = None) -> Any
# Evaluate the expression. Returns the result value.
# Pass a context for expressions referencing fields/variables.
# Returns None/NULL on error.
def hasEvalError(self) -> bool
# Returns True if the last evaluate() call had an error.
def evalErrorString(self) -> str
# Returns the evaluation error message.
def isValid(self) -> bool
# Returns True if expression was parsed successfully.
# --- Prepare (optimization for repeated evaluation) ---
def prepare(self, context: QgsExpressionContext) -> bool
# Prepare expression for repeated evaluation against a context.
# Call once before looping over features for better performance.
# Returns True on success.
# --- Inspection ---
def expression(self) -> str
# Returns the original expression string.
def dump(self) -> str
# Returns a human-readable representation of the parsed expression.
def referencedColumns(self) -> set[str]
# Returns field names referenced by the expression.
def referencedVariables(self) -> set[str]
# Returns variable names referenced by the expression.
def referencedFunctions(self) -> set[str]
# Returns function names used in the expression.
def needsGeometry(self) -> bool
# Returns True if the expression uses geometry ($geometry, $area, etc.).
def isField(self) -> bool
# Returns True if the expression is a simple field reference.
# --- Static methods ---
@staticmethod
def registerFunction(function) -> bool
# Register a custom @qgsfunction with the expression engine.
# Returns True on success.
@staticmethod
def unregisterFunction(name: str) -> bool
# Unregister a custom function by name.
# Returns True on success.
@staticmethod
def isFunctionName(name: str) -> bool
# Returns True if name is a registered function.
@staticmethod
def functionIndex(name: str) -> int
# Returns the index of a function in the functions list, or -1.
@staticmethod
def quotedColumnRef(name: str) -> str
# Returns a properly quoted column reference: "field_name"
@staticmethod
def quotedString(text: str) -> str
# Returns a properly quoted string literal: 'text'
@staticmethod
def createFieldEqualityExpression(fieldName: str, value) -> str
# Creates "fieldName" = value with proper quoting.
@staticmethod
def replaceExpressionText(
action: str,
context: QgsExpressionContext,
distanceArea: QgsDistanceArea = None
) -> str
# Evaluates [% expression %] tags within a text template.
# Used for expression-based text templates in layouts and labels.---
QgsExpressionContext
Provides the evaluation context with variables, scopes, and the current feature.
class QgsExpressionContext:
def __init__(self, scopes: list[QgsExpressionContextScope] = None)
# Create a context, optionally with pre-built scopes.
# --- Scope management ---
def appendScope(self, scope: QgsExpressionContextScope)
# Add a scope to the end (highest priority).
def appendScopes(self, scopes: list[QgsExpressionContextScope])
# Add multiple scopes at once.
def removeLastScope(self) -> QgsExpressionContextScope
# Remove and return the last (highest priority) scope.
def lastScope(self) -> QgsExpressionContextScope
# Return the last scope without removing it.
def scopeCount(self) -> int
# Returns the number of scopes.
# --- Feature ---
def setFeature(self, feature: QgsFeature)
# Set the current feature for evaluation.
# ALWAYS call this before evaluating feature-dependent expressions.
def feature(self) -> QgsFeature
# Returns the current feature.
def hasFeature(self) -> bool
# Returns True if a feature is set.
# --- Variables ---
def variable(self, name: str) -> Any
# Returns the value of a variable (e.g., 'layer_name').
def hasVariable(self, name: str) -> bool
# Returns True if the variable exists in any scope.
def variableNames(self) -> list[str]
# Returns all variable names across all scopes.
def setFields(self, fields: QgsFields)
# Set available fields for field reference resolution.
def fields(self) -> QgsFields
# Returns the fields available in this context.---
QgsExpressionContextUtils
Static utility class for creating standard scopes.
class QgsExpressionContextUtils:
@staticmethod
def globalScope() -> QgsExpressionContextScope
# Scope with global variables: @qgis_version, @user_full_name, etc.
@staticmethod
def projectScope(project: QgsProject) -> QgsExpressionContextScope
# Scope with project variables: @project_title, @project_crs, etc.
@staticmethod
def layerScope(layer: QgsMapLayer) -> QgsExpressionContextScope
# Scope with layer variables: @layer_name, @layer_id, fields, etc.
@staticmethod
def globalProjectLayerScopes(layer: QgsMapLayer) -> list[QgsExpressionContextScope]
# Convenience: returns [globalScope, projectScope, layerScope] in one call.
# ALWAYS use this as the starting point for feature-based evaluation.
@staticmethod
def setGlobalVariable(name: str, value)
# Set a global variable (persists across sessions in QGIS settings).
@staticmethod
def setGlobalVariables(variables: dict)
# Set multiple global variables at once.
@staticmethod
def removeGlobalVariable(name: str)
# Remove a global variable.
@staticmethod
def setProjectVariable(project: QgsProject, name: str, value)
# Set a project variable (saved with the .qgz/.qgs file).
@staticmethod
def setProjectVariables(project: QgsProject, variables: dict)
# Set multiple project variables at once.
@staticmethod
def removeProjectVariable(project: QgsProject, name: str)
# Remove a project variable.
@staticmethod
def setLayerVariable(layer: QgsMapLayer, name: str, value)
# Set a layer variable (saved with the layer in the project).
@staticmethod
def setLayerVariables(layer: QgsMapLayer, variables: dict)
# Set multiple layer variables at once.
@staticmethod
def removeLayerVariable(layer: QgsMapLayer, name: str)
# Remove a layer variable.---
QgsExpressionContextScope
A single scope within an expression context, holding variables and functions.
class QgsExpressionContextScope:
def __init__(self, name: str = '')
# Create a named scope.
def setVariable(self, name: str, value, isStatic: bool = False)
# Set a variable in this scope.
# isStatic=True means value does not change per feature (optimization).
def variable(self, name: str) -> Any
# Returns the variable value.
def hasVariable(self, name: str) -> bool
# Returns True if the variable exists in this scope.
def variableNames(self) -> list[str]
# Returns all variable names in this scope.
def removeVariable(self, name: str) -> bool
# Remove a variable. Returns True if it existed.
def setFeature(self, feature: QgsFeature)
# Set the feature for this scope.
def name(self) -> str
# Returns the scope name.---
@qgsfunction Decorator
Decorator for creating custom expression functions callable from QGIS expressions.
from qgis.core import qgsfunction
@qgsfunction(
args='auto', # int or 'auto' — number of arguments
group='Custom', # str — category in expression builder UI
referenced_columns=[], # list[str] — field names accessed, or
# [QgsFeatureRequest.ALL_ATTRIBUTES]
usesgeometry=False, # bool — True if function accesses geometry
handlesnull=False, # bool — True if function handles NULL inputs
register=True # bool — auto-register on import (default True)
)
def my_function(value1, value2, feature, parent):
"""
Function description shown in expression builder.
<p>HTML markup is supported for detailed help.</p>
"""
return value1 + value2Parameter Rules
- When
args='auto': the decorator counts parameters excludingfeatureandparent featureparameter: the current QgsFeature (ALWAYS include as second-to-last param)parentparameter: the parent QgsExpression node (ALWAYS include as last param)- Both
featureandparentare injected automatically; do NOT pass them when calling
Registration
# Manual registration (if register=False in decorator)
QgsExpression.registerFunction(my_function)
# ALWAYS unregister in plugin unload() to prevent stale references
QgsExpression.unregisterFunction('my_function')---
QgsProperty
Binds an expression (or field) to a symbology/rendering property.
class QgsProperty:
@staticmethod
def fromExpression(expression: str, isActive: bool = True) -> QgsProperty
# Create a property driven by an expression string.
@staticmethod
def fromField(fieldName: str, isActive: bool = True) -> QgsProperty
# Create a property driven by a field value.
@staticmethod
def fromValue(value, isActive: bool = True) -> QgsProperty
# Create a property with a static value.
def expressionString(self) -> str
# Returns the expression string (if expression-based).
def field(self) -> str
# Returns the field name (if field-based).
def isActive(self) -> bool
# Returns True if this property is active.
def setActive(self, active: bool)
# Enable or disable this property.
def valueAsString(self, context: QgsExpressionContext, defaultString: str = '') -> tuple[str, bool]
# Evaluate and return (value, isValid).
def valueAsDouble(self, context: QgsExpressionContext, defaultValue: float = 0.0) -> tuple[float, bool]
# Evaluate and return (value, isValid).
def valueAsInt(self, context: QgsExpressionContext, defaultValue: int = 0) -> tuple[int, bool]
# Evaluate and return (value, isValid).
def valueAsColor(self, context: QgsExpressionContext, defaultColor: QColor = QColor()) -> tuple[QColor, bool]
# Evaluate and return (color, isValid).---
QgsFeatureRequest (Expression Filtering)
class QgsFeatureRequest:
def setFilterExpression(self, expression: str) -> QgsFeatureRequest
# Filter features using an expression string.
# Returns self for method chaining.
def filterExpression(self) -> QgsExpression
# Returns the current filter expression (or None).
def setExpressionContext(self, context: QgsExpressionContext) -> QgsFeatureRequest
# Set the expression context for filter evaluation.
# Required when expressions reference project/global variables.