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 |
|| |
"first" || ' ' || "last" |
| 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, $currentfeature
Adding 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
1---2name: qgis-syntax-expressions3description: Use when writing QGIS expressions for filtering, labeling, symbology, or field calculations. Prevents expression syntax errors and context misconfiguration. Covers QgsExpression parsing, evaluation contexts, field calculator, data-defined properties, and custom functions. Keywords: QgsExpression, expression, field calculator, label expression, data-defined, @qgsfunction, filter, evaluate, calculate field, formula, conditional label, dynamic value.4license: MIT5---67# qgis-syntax-expressions89## Quick Reference1011### Expression Evaluation Pipeline1213| Step | Class | Purpose |14|------|-------|---------|15| 1. Parse | `QgsExpression(string)` | Validate syntax, build AST |16| 2. Context | `QgsExpressionContext()` | Provide variables, fields, scopes |17| 3. Scopes | `QgsExpressionContextUtils` | Add global/project/layer/feature scopes |18| 4. Evaluate | `exp.evaluate(context)` | Execute and return result |19| 5. Validate | `exp.hasEvalError()` | Check for runtime errors |2021### Expression Syntax Cheatsheet2223| Element | Syntax | Example |24|---------|--------|---------|25| Field reference | `"field_name"` (double quotes) | `"population"` |26| String literal | `'text'` (single quotes) | `'hello world'` |27| Number literal | `123`, `3.14` | `42` |28| NULL check | `IS NULL`, `IS NOT NULL` | `"name" IS NOT NULL` |29| Comparison | `=`, `!=`, `>`, `>=`, `<`, `<=` | `"area" > 100` |30| Logical | `AND`, `OR`, `NOT` | `"pop" > 1000 AND "type" = 'city'` |31| Arithmetic | `+`, `-`, `*`, `/`, `^`, `%` | `"population" * 1.05` |32| Pattern match | `LIKE`, `ILIKE`, `~` (regex) | `"name" ILIKE '%lake%'` |33| Concatenation | `\|\|` | `"first" \|\| ' ' \|\| "last"` |34| Conditional | `CASE WHEN ... THEN ... END` | `CASE WHEN "pop" > 1e6 THEN 'large' ELSE 'small' END` |35| Geometry vars | `$area`, `$length`, `$x`, `$y` | `$area / 1e6` |36| Current geometry | `$geometry` | `num_points($geometry)` |3738### Common Expression Functions3940| Category | Functions |41|----------|-----------|42| Math | `sqrt()`, `abs()`, `round()`, `floor()`, `ceil()`, `sin()`, `cos()`, `pi()` |43| String | `upper()`, `lower()`, `length()`, `trim()`, `replace()`, `regexp_replace()`, `substr()`, `left()`, `right()` |44| Conversion | `to_int()`, `to_real()`, `to_string()`, `to_date()`, `to_datetime()` |45| Date/Time | `now()`, `day()`, `month()`, `year()`, `hour()`, `minute()`, `age()`, `day_of_week()` |46| Geometry | `$area`, `$length`, `$x`, `$y`, `$perimeter`, `centroid()`, `buffer()`, `area()`, `length()`, `num_geometries()`, `num_points()` |47| Aggregates | `aggregate()`, `sum()`, `count()`, `mean()`, `min()`, `max()`, `concatenate()`, `array_agg()` |48| Conditionals | `if()`, `coalesce()`, `nullif()`, `try()` |49| Arrays | `array()`, `array_length()`, `array_contains()`, `array_append()`, `array_to_string()` |50| Map/Record | `map()`, `map_get()`, `hstore_to_map()` |51| Color | `color_rgb()`, `color_hsv()`, `ramp_color()`, `darker()`, `lighter()` |5253---5455## Critical Warnings5657**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.5859**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).6061**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.6263**NEVER** use double quotes for string literals in expressions. Double quotes reference field names: `"name"` reads the field, `'name'` is the string literal.6465**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.6667**NEVER** use `print()` inside custom expression functions (`@qgsfunction`). Use `QgsMessageLog` instead. Expression functions run during rendering and `print()` causes thread-safety issues.6869**ALWAYS** specify `referenced_columns` in `@qgsfunction` for performance. Use `[QgsFeatureRequest.ALL_ATTRIBUTES]` if the function reads arbitrary fields; use `[]` if it reads no fields.7071**ALWAYS** set `usesgeometry=True` in `@qgsfunction` if the function accesses `feature.geometry()`. Omitting this causes the geometry to be unavailable.7273---7475## Decision Tree: Choosing the Right Expression Approach7677```78Need to use an expression?79|80+-- Filter features? --> QgsFeatureRequest.setFilterExpression()81|82+-- Calculate field values? --> Field Calculator pattern (edit session + evaluate per feature)83|84+-- Drive labeling? --> QgsPalLayerSettings.fieldName + isExpression = True85|86+-- Drive symbology? --> QgsProperty.fromExpression() on symbol/renderer properties87|88+-- Evaluate standalone? --> QgsExpression.evaluate(context)89|90+-- Need custom logic? --> @qgsfunction decorator + QgsExpression.registerFunction()91```9293---9495## Essential Patterns9697### Pattern 1: Parse and Evaluate an Expression9899```python100from qgis.core import (101 QgsExpression, QgsExpressionContext, QgsExpressionContextUtils102)103104exp = QgsExpression('"population" * 1.05')105if exp.hasParserError():106 raise ValueError(f"Parse error: {exp.parserErrorString()}")107108context = QgsExpressionContext()109context.appendScopes(110 QgsExpressionContextUtils.globalProjectLayerScopes(layer)111)112113for feature in layer.getFeatures():114 context.setFeature(feature)115 value = exp.evaluate(context)116 if exp.hasEvalError():117 raise ValueError(f"Eval error: {exp.evalErrorString()}")118 print(f"Feature {feature.id()}: {value}")119```120121### Pattern 2: Expression-Based Feature Filtering122123```python124from qgis.core import QgsFeatureRequest125126# Method A: expression string directly127request = QgsFeatureRequest().setFilterExpression('"population" >= 50000')128features = list(layer.getFeatures(request))129130# Method B: QgsExpression object131exp = QgsExpression('"name" ILIKE '%lake%'')132request = QgsFeatureRequest(exp)133features = list(layer.getFeatures(request))134```135136### Pattern 3: Field Calculator (Update Attributes)137138```python139from qgis.core import (140 edit, QgsExpression, QgsExpressionContext,141 QgsExpressionContextUtils142)143144exp = QgsExpression('"population" * 2')145context = QgsExpressionContext()146context.appendScopes(147 QgsExpressionContextUtils.globalProjectLayerScopes(layer)148)149150with edit(layer):151 for f in layer.getFeatures():152 context.setFeature(f)153 f['doubled_pop'] = exp.evaluate(context)154 layer.updateFeature(f)155```156157### Pattern 4: Expression-Based Labeling158159```python160from qgis.core import QgsPalLayerSettings, QgsVectorLayerSimpleLabeling161162settings = QgsPalLayerSettings()163settings.fieldName = '"name" || ' (' || "population" || ')''164settings.isExpression = True165settings.enabled = True166167text_format = settings.format()168text_format.setSize(12)169settings.setFormat(text_format)170171labeling = QgsVectorLayerSimpleLabeling(settings)172layer.setLabeling(labeling)173layer.setLabelsEnabled(True)174layer.triggerRepaint()175```176177### Pattern 5: Data-Defined Symbology Properties178179```python180from qgis.core import QgsProperty181182symbol = layer.renderer().symbol()183184# Size driven by expression185symbol.setDataDefinedSize(186 QgsProperty.fromExpression('"population" / 1000')187)188189# Color driven by expression190symbol.setDataDefinedColor(191 QgsProperty.fromExpression(192 "CASE WHEN \"type\" = 'city' THEN '#ff0000' ELSE '#0000ff' END"193 )194)195196layer.triggerRepaint()197```198199### Pattern 6: Custom Expression Function200201```python202from qgis.core import qgsfunction, QgsExpression203204@qgsfunction(args='auto', group='Custom', referenced_columns=[])205def population_density(population, area_km2, feature, parent):206 """207 Calculate population density per square kilometer.208 <p>Usage: population_density("pop_field", "area_field")</p>209 """210 if area_km2 and area_km2 > 0:211 return population / area_km2212 return None213214# Register (call once, e.g., in plugin initGui)215QgsExpression.registerFunction(population_density)216217# Now usable: population_density("population", "area")218219# Unregister (call in plugin unload)220QgsExpression.unregisterFunction('population_density')221```222223---224225## Context Scope Hierarchy226227Scopes are stacked from generic to specific. When variable names collide, the most specific scope wins:228229```230Global scope → @qgis_version, @user_full_name, custom global vars231 Project scope → @project_title, @project_crs, custom project vars232 Layer scope → @layer_name, @layer_id, layer fields233 Feature scope → Feature attributes, $geometry, $id, $currentfeature234```235236### Adding Custom Variables237238```python239from qgis.core import QgsExpressionContextUtils240241# Set global variable (persists across sessions)242QgsExpressionContextUtils.setGlobalVariable('my_threshold', 42)243244# Set project variable (saved with project)245QgsExpressionContextUtils.setProjectVariable(246 QgsProject.instance(), 'analysis_year', 2024247)248249# Set layer variable (saved with layer in project)250QgsExpressionContextUtils.setLayerVariable(layer, 'source_date', '2024-01-15')251252# Access in expressions: @my_threshold, @analysis_year, @source_date253```254255---256257## Aggregate Expressions258259Aggregates compute values across features within a layer:260261```python262# In expression strings:263# aggregate('layer_name', 'sum', "population")264# aggregate('layer_name', 'mean', "area", filter:="type" = 'residential')265266# Common shorthand (current layer):267# sum("population")268# count("id", filter:="active" = 1)269# mean("temperature", group_by:="region")270```271272**ALWAYS** use the `filter` parameter in aggregates to limit the scope. Aggregating an entire large layer without a filter is a performance bottleneck.273274---275276## Reference Links277278- [references/methods.md](references/methods.md) -- API signatures for QgsExpression, QgsExpressionContext, QgsExpressionContextUtils, @qgsfunction279- [references/examples.md](references/examples.md) -- Complete expression examples for filtering, labeling, symbology, field calculator280- [references/anti-patterns.md](references/anti-patterns.md) -- Expression pitfalls and incorrect patterns281282### Official Sources283284- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/expressions.html285- https://docs.qgis.org/latest/en/docs/user_manual/expressions/expression.html286- https://qgis.org/pyqgis/master/core/QgsExpression.html287- https://qgis.org/pyqgis/master/core/QgsExpressionContext.html