[{"data":1,"prerenderedAt":1884},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fuse-expression-context-and-scopes-pyqgis":3},{"id":4,"title":5,"body":6,"description":1873,"extension":1874,"meta":1875,"navigation":211,"path":1880,"seo":1881,"stem":1882,"__hash__":1883},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fuse-expression-context-and-scopes-pyqgis\u002Findex.md","Use Expression Context and Scopes in PyQGIS",{"type":7,"value":8,"toc":1858},"minimark",[9,13,22,31,143,148,163,167,173,333,364,368,371,511,537,541,544,608,713,722,726,729,808,1010,1015,1019,1022,1333,1353,1357,1360,1516,1529,1533,1536,1703,1721,1725,1737,1741,1769,1773,1776,1780,1794,1800,1806,1812,1821,1825,1854],[10,11,5],"h1",{"id":12},"use-expression-context-and-scopes-in-pyqgis",[14,15,16,17,21],"p",{},"An expression such as ",[18,19,20],"code",{},"@project_title || ' — ' || \"name\""," works in a label, but evaluated in a script it returns NULL for the project title and fails on the field. The difference is the expression context: the set of variables, fields and the current feature that an expression can see. Inside QGIS, every label, style and layout item builds a context automatically. In scripts, you build it — and understanding its layered scopes is what makes scripted expressions behave exactly like the ones in the interface.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002F","Working with QGIS Expressions",". It explains the scope stack, builds contexts for layer evaluation, inspects which variables are available, adds custom scopes and variables, and uses scopes to make scripts and layouts configurable.",[14,32,33],{},[34,35,40,44,48,56,73,82,91,97,102,106,111,115,120,124,131,138],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"168 0 539 290","img","A stack of expression scopes from global through project and layer to feature, with later scopes overriding earlier ones","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"The scope stack",[45,46,47],"desc",{},"An expression context is a stack of scopes. The global scope holds application-wide variables such as qgis_version and user_account_name. The project scope adds project_title, project_crs and project variables. The layer scope adds layer_name, layer_id and layer variables. A feature scope holds the current feature and its fields. Later scopes override earlier ones, so a layer variable can override a project variable of the same name.",[49,50],"rect",{"x":51,"y":52,"width":53,"height":54,"fill":55},"168","0","539","290","#f6f3ea",[57,58,59],"defs",{},[60,61,68],"marker",{"id":62,"viewBox":63,"refX":64,"refY":65,"markerWidth":66,"markerHeight":66,"orient":67},"ctxStackArrow","0 0 10 10","8","5","7","auto-start-reverse",[69,70],"path",{"d":71,"fill":72},"M0 0 L10 5 L0 10 z","#2f3b35",[74,75,81],"text",{"x":76,"y":77,"style":78,"fill":79,"textAnchor":80},"437.5","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Later scopes override earlier ones",[49,83],{"x":84,"y":85,"width":86,"height":87,"rx":64,"fill":88,"stroke":89,"style":90},"200","230","360","44","#fffdf7","#59645f","stroke-width:2",[74,92,96],{"x":93,"y":94,"style":95,"fill":72,"textAnchor":80},"380","255.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","global: @qgis_version, @user_account_name",[49,98],{"x":84,"y":99,"width":86,"height":87,"rx":64,"fill":100,"stroke":101,"style":90},"178","#eef7f4","#0f766e",[74,103,105],{"x":93,"y":104,"style":95,"fill":72,"textAnchor":80},"203.6","project: @project_title, @project_crs, vars",[49,107],{"x":84,"y":108,"width":86,"height":87,"rx":64,"fill":109,"stroke":110,"style":90},"126","#eff3ff","#2563eb",[74,112,114],{"x":93,"y":113,"style":95,"fill":72,"textAnchor":80},"151.6","layer: @layer_name, @layer_id, layer vars",[49,116],{"x":84,"y":117,"width":86,"height":87,"rx":64,"fill":118,"stroke":119,"style":90},"74","#fdf2e2","#b45309",[74,121,123],{"x":93,"y":122,"style":95,"fill":72,"textAnchor":80},"99.6","feature: \"fields\", $geometry, @id",[125,126],"line",{"x1":127,"y1":128,"x2":127,"y2":129,"stroke":72,"style":130},"600","260","80","stroke-width:1.8;marker-end:url(#ctxStackArrow)",[74,132,137],{"x":133,"y":134,"style":135,"fill":89,"textAnchor":136},"616","170","text-anchor:start;font-size:10.5px;font-family:sans-serif","start","lookup order",[74,139,142],{"x":93,"y":140,"style":141,"fill":89,"textAnchor":80},"56","text-anchor:middle;font-size:10.5px;font-family:sans-serif","the expression sees the top-most value of each variable",[144,145,147],"h2",{"id":146},"prerequisites","Prerequisites",[149,150,151,155],"ul",{},[152,153,154],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[152,156,157,158,162],{},"Basic familiarity with evaluating expressions, as in ",[26,159,161],{"href":160},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fevaluate-qgis-expression-in-pyqgis\u002F","evaluating a QGIS expression in PyQGIS",".",[144,164,166],{"id":165},"build-a-context-like-qgis-does","Build a context like QGIS does",[14,168,169,172],{},[18,170,171],{},"QgsExpressionContextUtils"," creates the standard scopes. For an expression evaluated against a layer's features, the global, project and layer scopes plus the feature are what QGIS itself uses.",[174,175,180],"pre",{"className":176,"code":177,"language":178,"meta":179,"style":179},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsProject, QgsExpression, QgsExpressionContext,\n                       QgsExpressionContextUtils)\n\nlayer = QgsProject.instance().mapLayersByName(\"districts\")[0]\ncontext = QgsExpressionContext()\ncontext.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n\nexpr = QgsExpression(\"@project_title || ': ' || \\\"name\\\" || ' (' || @layer_name || ')'\")\nexpr.prepare(context)\nfor f in list(layer.getFeatures())[:3]:\n    context.setFeature(f)\n    print(expr.evaluate(context))\n","python","",[18,181,182,200,206,213,238,249,255,260,288,294,318,324],{"__ignoreMap":179},[183,184,186,190,194,197],"span",{"class":125,"line":185},1,[183,187,189],{"class":188},"snl16","from",[183,191,193],{"class":192},"s95oV"," qgis.core ",[183,195,196],{"class":188},"import",[183,198,199],{"class":192}," (QgsProject, QgsExpression, QgsExpressionContext,\n",[183,201,203],{"class":125,"line":202},2,[183,204,205],{"class":192},"                       QgsExpressionContextUtils)\n",[183,207,209],{"class":125,"line":208},3,[183,210,212],{"emptyLinePlaceholder":211},true,"\n",[183,214,216,219,222,225,229,232,235],{"class":125,"line":215},4,[183,217,218],{"class":192},"layer ",[183,220,221],{"class":188},"=",[183,223,224],{"class":192}," QgsProject.instance().mapLayersByName(",[183,226,228],{"class":227},"sU2Wk","\"districts\"",[183,230,231],{"class":192},")[",[183,233,52],{"class":234},"sDLfK",[183,236,237],{"class":192},"]\n",[183,239,241,244,246],{"class":125,"line":240},5,[183,242,243],{"class":192},"context ",[183,245,221],{"class":188},[183,247,248],{"class":192}," QgsExpressionContext()\n",[183,250,252],{"class":125,"line":251},6,[183,253,254],{"class":192},"context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n",[183,256,258],{"class":125,"line":257},7,[183,259,212],{"emptyLinePlaceholder":211},[183,261,263,266,268,271,274,277,280,282,285],{"class":125,"line":262},8,[183,264,265],{"class":192},"expr ",[183,267,221],{"class":188},[183,269,270],{"class":192}," QgsExpression(",[183,272,273],{"class":227},"\"@project_title || ': ' || ",[183,275,276],{"class":234},"\\\"",[183,278,279],{"class":227},"name",[183,281,276],{"class":234},[183,283,284],{"class":227}," || ' (' || @layer_name || ')'\"",[183,286,287],{"class":192},")\n",[183,289,291],{"class":125,"line":290},9,[183,292,293],{"class":192},"expr.prepare(context)\n",[183,295,297,300,303,306,309,312,315],{"class":125,"line":296},10,[183,298,299],{"class":188},"for",[183,301,302],{"class":192}," f ",[183,304,305],{"class":188},"in",[183,307,308],{"class":234}," list",[183,310,311],{"class":192},"(layer.getFeatures())[:",[183,313,314],{"class":234},"3",[183,316,317],{"class":192},"]:\n",[183,319,321],{"class":125,"line":320},11,[183,322,323],{"class":192},"    context.setFeature(f)\n",[183,325,327,330],{"class":125,"line":326},12,[183,328,329],{"class":234},"    print",[183,331,332],{"class":192},"(expr.evaluate(context))\n",[14,334,335,339,340,343,344,347,348,351,352,355,356,359,360,363],{},[336,337,338],"strong",{},"Breakdown:"," ",[18,341,342],{},"globalProjectLayerScopes"," returns the three scopes in order, and ",[18,345,346],{},"appendScopes"," stacks them. ",[18,349,350],{},"setFeature"," puts the current feature into the context, which makes field references and ",[18,353,354],{},"$geometry"," work. Preparing once and setting the feature per iteration is the efficient pattern. Without the project scope, ",[18,357,358],{},"@project_title"," is NULL; without the layer scope, ",[18,361,362],{},"@layer_name"," is NULL and — because the fields come from the layer — field names may not resolve during preparation.",[144,365,367],{"id":366},"see-which-variables-are-available","See which variables are available",[14,369,370],{},"When an expression returns NULL unexpectedly, the first question is whether the variable exists in the context. The context can list them.",[174,372,374],{"className":176,"code":373,"language":178,"meta":179,"style":179},"for name in sorted(context.variableNames()):\n    value = context.variable(name)\n    if not name.startswith(\"_\"):\n        print(f\"@{name:\u003C28} {str(value)[:60]}\")\n\nprint(\"highest scope defining project_title:\",\n      context.indexOfScope(context.scopeForVariable(\"project_title\").name())\n      if context.scopeForVariable(\"project_title\") else None)\n",[18,375,376,391,401,418,462,466,479,490],{"__ignoreMap":179},[183,377,378,380,383,385,388],{"class":125,"line":185},[183,379,299],{"class":188},[183,381,382],{"class":192}," name ",[183,384,305],{"class":188},[183,386,387],{"class":234}," sorted",[183,389,390],{"class":192},"(context.variableNames()):\n",[183,392,393,396,398],{"class":125,"line":202},[183,394,395],{"class":192},"    value ",[183,397,221],{"class":188},[183,399,400],{"class":192}," context.variable(name)\n",[183,402,403,406,409,412,415],{"class":125,"line":208},[183,404,405],{"class":188},"    if",[183,407,408],{"class":188}," not",[183,410,411],{"class":192}," name.startswith(",[183,413,414],{"class":227},"\"_\"",[183,416,417],{"class":192},"):\n",[183,419,420,423,426,429,432,435,437,440,443,446,449,452,455,457,460],{"class":125,"line":215},[183,421,422],{"class":234},"        print",[183,424,425],{"class":192},"(",[183,427,428],{"class":188},"f",[183,430,431],{"class":227},"\"@",[183,433,434],{"class":234},"{",[183,436,279],{"class":192},[183,438,439],{"class":188},":\u003C28",[183,441,442],{"class":234},"}",[183,444,445],{"class":234}," {str",[183,447,448],{"class":192},"(value)[:",[183,450,451],{"class":234},"60",[183,453,454],{"class":192},"]",[183,456,442],{"class":234},[183,458,459],{"class":227},"\"",[183,461,287],{"class":192},[183,463,464],{"class":125,"line":240},[183,465,212],{"emptyLinePlaceholder":211},[183,467,468,471,473,476],{"class":125,"line":251},[183,469,470],{"class":234},"print",[183,472,425],{"class":192},[183,474,475],{"class":227},"\"highest scope defining project_title:\"",[183,477,478],{"class":192},",\n",[183,480,481,484,487],{"class":125,"line":257},[183,482,483],{"class":192},"      context.indexOfScope(context.scopeForVariable(",[183,485,486],{"class":227},"\"project_title\"",[183,488,489],{"class":192},").name())\n",[183,491,492,495,498,500,503,506,509],{"class":125,"line":262},[183,493,494],{"class":188},"      if",[183,496,497],{"class":192}," context.scopeForVariable(",[183,499,486],{"class":227},[183,501,502],{"class":192},") ",[183,504,505],{"class":188},"else",[183,507,508],{"class":234}," None",[183,510,287],{"class":192},[14,512,513,339,515,518,519,522,523,522,526,522,529,532,533,536],{},[336,514,338],{},[18,516,517],{},"variableNames"," lists every variable visible in the context, from all scopes. Printing them with values is the quickest way to discover what is on offer — ",[18,520,521],{},"@project_crs",", ",[18,524,525],{},"@project_folder",[18,527,528],{},"@user_full_name",[18,530,531],{},"@layer_crs"," and many more. ",[18,534,535],{},"scopeForVariable"," returns the scope that provides the value actually used, which explains surprises when the same name is defined at several levels. Variables starting with an underscore are internal and best ignored.",[144,538,540],{"id":539},"set-project-and-layer-variables","Set project and layer variables",[14,542,543],{},"Variables are a configuration mechanism: values defined once at project or layer level and used in labels, styles, layouts and expressions across the project.",[14,545,546],{},[34,547,550,553,556,560,563,568,574,579,583,586,589,592,595,598,602,605],{"viewBox":548,"role":37,"ariaLabel":549,"xmlns":39},"0 0 760 240","Global, project and layer variables used across labels, styles and layouts, with a layer variable overriding a project variable",[41,551,552],{},"Variables at different levels",[45,554,555],{},"A project variable report_year is set to 2026 and used in every layout title and filter. A layer variable on one layer sets a threshold used only in that layer's style. A global variable, organisation_name, set once in user settings, appears in every project. Overriding a project variable with a layer variable of the same name lets one layer use a different value.",[49,557],{"x":52,"y":52,"width":558,"height":559,"fill":55},"760","240",[74,561,562],{"x":93,"y":77,"style":78,"fill":79,"textAnchor":80},"Define once, use everywhere below",[49,564],{"x":565,"y":140,"width":566,"height":567,"rx":64,"fill":88,"stroke":89,"style":90},"24","222","160",[74,569,573],{"x":570,"y":571,"style":572,"fill":79,"textAnchor":80},"135","111.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","global",[74,575,578],{"x":570,"y":576,"style":577,"fill":72,"textAnchor":80},"139.78","text-anchor:middle;font-size:10.0px;font-family:monospace","organisation_name",[74,580,582],{"x":570,"y":581,"style":141,"fill":89,"textAnchor":80},"167.78","every project",[49,584],{"x":585,"y":140,"width":566,"height":567,"rx":64,"fill":100,"stroke":101,"style":90},"269",[74,587,588],{"x":93,"y":571,"style":572,"fill":101,"textAnchor":80},"project",[74,590,591],{"x":93,"y":576,"style":577,"fill":72,"textAnchor":80},"report_year = 2026",[74,593,594],{"x":93,"y":581,"style":141,"fill":89,"textAnchor":80},"all layers, layouts",[49,596],{"x":597,"y":140,"width":566,"height":567,"rx":64,"fill":109,"stroke":110,"style":90},"514",[74,599,601],{"x":600,"y":571,"style":572,"fill":110,"textAnchor":80},"625","layer",[74,603,604],{"x":600,"y":576,"style":577,"fill":72,"textAnchor":80},"alert_threshold",[74,606,607],{"x":600,"y":581,"style":141,"fill":89,"textAnchor":80},"this layer only",[174,609,611],{"className":176,"code":610,"language":178,"meta":179,"style":179},"project = QgsProject.instance()\nQgsExpressionContextUtils.setProjectVariable(project, \"report_year\", 2026)\nQgsExpressionContextUtils.setProjectVariable(project, \"data_folder\", \"\u002Fdata\u002F2026\")\nQgsExpressionContextUtils.setLayerVariable(layer, \"alert_threshold\", 1500)\nQgsExpressionContextUtils.setGlobalVariable(\"organisation_name\", \"City Planning Office\")\n\nctx = QgsExpressionContext()\nctx.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\nprint(QgsExpression(\"@organisation_name || ' ' || @report_year\").evaluate(ctx))\n",[18,612,613,623,638,652,667,682,686,695,700],{"__ignoreMap":179},[183,614,615,618,620],{"class":125,"line":185},[183,616,617],{"class":192},"project ",[183,619,221],{"class":188},[183,621,622],{"class":192}," QgsProject.instance()\n",[183,624,625,628,631,633,636],{"class":125,"line":202},[183,626,627],{"class":192},"QgsExpressionContextUtils.setProjectVariable(project, ",[183,629,630],{"class":227},"\"report_year\"",[183,632,522],{"class":192},[183,634,635],{"class":234},"2026",[183,637,287],{"class":192},[183,639,640,642,645,647,650],{"class":125,"line":208},[183,641,627],{"class":192},[183,643,644],{"class":227},"\"data_folder\"",[183,646,522],{"class":192},[183,648,649],{"class":227},"\"\u002Fdata\u002F2026\"",[183,651,287],{"class":192},[183,653,654,657,660,662,665],{"class":125,"line":215},[183,655,656],{"class":192},"QgsExpressionContextUtils.setLayerVariable(layer, ",[183,658,659],{"class":227},"\"alert_threshold\"",[183,661,522],{"class":192},[183,663,664],{"class":234},"1500",[183,666,287],{"class":192},[183,668,669,672,675,677,680],{"class":125,"line":240},[183,670,671],{"class":192},"QgsExpressionContextUtils.setGlobalVariable(",[183,673,674],{"class":227},"\"organisation_name\"",[183,676,522],{"class":192},[183,678,679],{"class":227},"\"City Planning Office\"",[183,681,287],{"class":192},[183,683,684],{"class":125,"line":251},[183,685,212],{"emptyLinePlaceholder":211},[183,687,688,691,693],{"class":125,"line":257},[183,689,690],{"class":192},"ctx ",[183,692,221],{"class":188},[183,694,248],{"class":192},[183,696,697],{"class":125,"line":262},[183,698,699],{"class":192},"ctx.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n",[183,701,702,704,707,710],{"class":125,"line":290},[183,703,470],{"class":234},[183,705,706],{"class":192},"(QgsExpression(",[183,708,709],{"class":227},"\"@organisation_name || ' ' || @report_year\"",[183,711,712],{"class":192},").evaluate(ctx))\n",[14,714,715,717,718,721],{},[336,716,338],{}," Project variables are saved in the project file; layer variables are saved with the layer in the project and in its QML style; global variables live in the user's settings and apply to every project on that machine. A label using ",[18,719,720],{},"@report_year"," updates everywhere when the project variable changes — one edit instead of hunting through layouts. A layer variable with the same name as a project variable overrides it for that layer only, which is how one layer can use a stricter threshold than the rest.",[144,723,725],{"id":724},"add-a-custom-scope","Add a custom scope",[14,727,728],{},"Scripts sometimes need their own variables — a run date, a batch id, the current tile in a loop — without writing them into the project. A custom scope added to the context provides them for the duration of the evaluation.",[14,730,731],{},[34,732,735,738,741,744,751,754,757,761,765,769,774,777,781,784,787,791,797,802,805],{"viewBox":733,"role":37,"ariaLabel":734,"xmlns":39},"0 0 760 196","A custom script scope appended on top of the standard scopes, providing temporary variables that override lower scopes",[41,736,737],{},"A temporary scope on top",[45,739,740],{},"A script appends its own scope, named batch run, on top of the standard stack. Its variables, such as run_id and min_area, are visible to expressions and override project or layer variables with the same names, but exist only in this context and are never written to the project.",[49,742],{"x":52,"y":52,"width":558,"height":743,"fill":55},"196",[57,745,746],{},[60,747,749],{"id":748,"viewBox":63,"refX":64,"refY":65,"markerWidth":66,"markerHeight":66,"orient":67},"ctxCustomArrow",[69,750],{"d":71,"fill":72},[74,752,753],{"x":93,"y":77,"style":78,"fill":79,"textAnchor":80},"Script variables without touching the project",[49,755],{"x":565,"y":451,"width":566,"height":756,"rx":64,"fill":88,"stroke":89,"style":90},"120",[74,758,760],{"x":570,"y":759,"style":572,"fill":79,"textAnchor":80},"99.78","standard scopes",[74,762,764],{"x":570,"y":763,"style":141,"fill":89,"textAnchor":80},"123.78","global, project,",[74,766,768],{"x":570,"y":767,"style":141,"fill":89,"textAnchor":80},"147.78","layer, feature",[125,770],{"x1":771,"y1":756,"x2":772,"y2":756,"stroke":72,"style":773},"246","288","stroke-width:1.8;marker-end:url(#ctxCustomArrow)",[49,775],{"x":776,"y":451,"width":84,"height":756,"rx":64,"fill":118,"stroke":119,"style":90},"292",[74,778,780],{"x":779,"y":759,"style":572,"fill":119,"textAnchor":80},"392","+ batch run scope",[74,782,783],{"x":779,"y":763,"style":577,"fill":72,"textAnchor":80},"run_id",[74,785,786],{"x":779,"y":767,"style":577,"fill":72,"textAnchor":80},"min_area",[125,788],{"x1":789,"y1":756,"x2":790,"y2":756,"stroke":72,"style":773},"492","534",[49,792],{"x":793,"y":451,"width":794,"height":756,"rx":64,"fill":795,"stroke":796,"style":90},"538","198","#e8efe6","#15803d",[74,798,801],{"x":799,"y":759,"style":572,"fill":800,"textAnchor":80},"637","#166534","expressions",[74,803,804],{"x":799,"y":763,"style":141,"fill":89,"textAnchor":80},"see run values",[74,806,807],{"x":799,"y":767,"style":141,"fill":89,"textAnchor":80},"project unchanged",[174,809,811],{"className":176,"code":810,"language":178,"meta":179,"style":179},"from qgis.core import QgsExpressionContextScope\n\nrun_scope = QgsExpressionContextScope(\"batch run\")\nrun_scope.setVariable(\"run_id\", \"2026-10-02-A\")\nrun_scope.setVariable(\"tile_name\", \"T_571_5934\")\nrun_scope.setVariable(\"min_area\", 500, isStatic=True)\n\nctx = QgsExpressionContext()\nctx.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\nctx.appendScope(run_scope)\n\nflt = QgsExpression('$area >= @min_area')\nflt.prepare(ctx)\nkept = 0\nfor f in layer.getFeatures():\n    ctx.setFeature(f)\n    if flt.evaluate(ctx):\n        kept += 1\nprint(kept, \"features above @min_area in run\", ctx.variable(\"run_id\"))\n",[18,812,813,824,828,843,858,872,897,901,909,913,918,922,936,942,953,965,971,979,991],{"__ignoreMap":179},[183,814,815,817,819,821],{"class":125,"line":185},[183,816,189],{"class":188},[183,818,193],{"class":192},[183,820,196],{"class":188},[183,822,823],{"class":192}," QgsExpressionContextScope\n",[183,825,826],{"class":125,"line":202},[183,827,212],{"emptyLinePlaceholder":211},[183,829,830,833,835,838,841],{"class":125,"line":208},[183,831,832],{"class":192},"run_scope ",[183,834,221],{"class":188},[183,836,837],{"class":192}," QgsExpressionContextScope(",[183,839,840],{"class":227},"\"batch run\"",[183,842,287],{"class":192},[183,844,845,848,851,853,856],{"class":125,"line":215},[183,846,847],{"class":192},"run_scope.setVariable(",[183,849,850],{"class":227},"\"run_id\"",[183,852,522],{"class":192},[183,854,855],{"class":227},"\"2026-10-02-A\"",[183,857,287],{"class":192},[183,859,860,862,865,867,870],{"class":125,"line":240},[183,861,847],{"class":192},[183,863,864],{"class":227},"\"tile_name\"",[183,866,522],{"class":192},[183,868,869],{"class":227},"\"T_571_5934\"",[183,871,287],{"class":192},[183,873,874,876,879,881,884,886,890,892,895],{"class":125,"line":251},[183,875,847],{"class":192},[183,877,878],{"class":227},"\"min_area\"",[183,880,522],{"class":192},[183,882,883],{"class":234},"500",[183,885,522],{"class":192},[183,887,889],{"class":888},"s9osk","isStatic",[183,891,221],{"class":188},[183,893,894],{"class":234},"True",[183,896,287],{"class":192},[183,898,899],{"class":125,"line":257},[183,900,212],{"emptyLinePlaceholder":211},[183,902,903,905,907],{"class":125,"line":262},[183,904,690],{"class":192},[183,906,221],{"class":188},[183,908,248],{"class":192},[183,910,911],{"class":125,"line":290},[183,912,699],{"class":192},[183,914,915],{"class":125,"line":296},[183,916,917],{"class":192},"ctx.appendScope(run_scope)\n",[183,919,920],{"class":125,"line":320},[183,921,212],{"emptyLinePlaceholder":211},[183,923,924,927,929,931,934],{"class":125,"line":326},[183,925,926],{"class":192},"flt ",[183,928,221],{"class":188},[183,930,270],{"class":192},[183,932,933],{"class":227},"'$area >= @min_area'",[183,935,287],{"class":192},[183,937,939],{"class":125,"line":938},13,[183,940,941],{"class":192},"flt.prepare(ctx)\n",[183,943,945,948,950],{"class":125,"line":944},14,[183,946,947],{"class":192},"kept ",[183,949,221],{"class":188},[183,951,952],{"class":234}," 0\n",[183,954,956,958,960,962],{"class":125,"line":955},15,[183,957,299],{"class":188},[183,959,302],{"class":192},[183,961,305],{"class":188},[183,963,964],{"class":192}," layer.getFeatures():\n",[183,966,968],{"class":125,"line":967},16,[183,969,970],{"class":192},"    ctx.setFeature(f)\n",[183,972,974,976],{"class":125,"line":973},17,[183,975,405],{"class":188},[183,977,978],{"class":192}," flt.evaluate(ctx):\n",[183,980,982,985,988],{"class":125,"line":981},18,[183,983,984],{"class":192},"        kept ",[183,986,987],{"class":188},"+=",[183,989,990],{"class":234}," 1\n",[183,992,994,996,999,1002,1005,1007],{"class":125,"line":993},19,[183,995,470],{"class":234},[183,997,998],{"class":192},"(kept, ",[183,1000,1001],{"class":227},"\"features above @min_area in run\"",[183,1003,1004],{"class":192},", ctx.variable(",[183,1006,850],{"class":227},[183,1008,1009],{"class":192},"))\n",[14,1011,1012,1014],{},[336,1013,338],{}," A scope appended last sits at the top of the stack, so its variables override any project or layer variables with the same names — useful for temporarily changing a threshold without editing the project. Marking a variable static tells QGIS its value will not change during evaluation, which allows some optimisations. The context owns appended scopes, so create a new scope object for each context rather than reusing one. Custom scopes are also how plugins pass values to expressions in their own dialogs.",[144,1016,1018],{"id":1017},"debug-expressions-that-return-null","Debug expressions that return NULL",[14,1020,1021],{},"Most expression problems show up as NULL. A short diagnostic separates the three usual causes — a parse error, a missing variable or field, an evaluation error — so you know which to fix.",[174,1023,1025],{"className":176,"code":1024,"language":178,"meta":179,"style":179},"def diagnose(text, layer, feature=None):\n    ctx = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n    e = QgsExpression(text)\n    if e.hasParserError():\n        return f\"parse error: {e.parserErrorString()}\"\n    missing_vars = [v for v in e.referencedVariables() if v not in ctx.variableNames() and v]\n    missing_fields = [c for c in e.referencedColumns()\n                      if c and c != QgsFeatureRequest.ALL_ATTRIBUTES and c not in layer.fields().names()]\n    if missing_vars or missing_fields:\n        return f\"missing variables {missing_vars} \u002F fields {missing_fields}\"\n    ctx.setFeature(feature or next(layer.getFeatures()))\n    value = e.evaluate(ctx)\n    if e.hasEvalError():\n        return f\"evaluation error: {e.evalErrorString()}\"\n    return f\"value: {value!r}\"\n\nfrom qgis.core import QgsFeatureRequest\nprint(diagnose(\"@report_yr || ' ' || \\\"nme\\\"\", layer))\n",[18,1026,1027,1046,1056,1066,1073,1094,1134,1154,1186,1199,1227,1240,1249,1256,1274,1296,1300,1311],{"__ignoreMap":179},[183,1028,1029,1032,1036,1039,1041,1044],{"class":125,"line":185},[183,1030,1031],{"class":188},"def",[183,1033,1035],{"class":1034},"svObZ"," diagnose",[183,1037,1038],{"class":192},"(text, layer, feature",[183,1040,221],{"class":188},[183,1042,1043],{"class":234},"None",[183,1045,417],{"class":192},[183,1047,1048,1051,1053],{"class":125,"line":202},[183,1049,1050],{"class":192},"    ctx ",[183,1052,221],{"class":188},[183,1054,1055],{"class":192}," QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n",[183,1057,1058,1061,1063],{"class":125,"line":208},[183,1059,1060],{"class":192},"    e ",[183,1062,221],{"class":188},[183,1064,1065],{"class":192}," QgsExpression(text)\n",[183,1067,1068,1070],{"class":125,"line":215},[183,1069,405],{"class":188},[183,1071,1072],{"class":192}," e.hasParserError():\n",[183,1074,1075,1078,1081,1084,1086,1089,1091],{"class":125,"line":240},[183,1076,1077],{"class":188},"        return",[183,1079,1080],{"class":188}," f",[183,1082,1083],{"class":227},"\"parse error: ",[183,1085,434],{"class":234},[183,1087,1088],{"class":192},"e.parserErrorString()",[183,1090,442],{"class":234},[183,1092,1093],{"class":227},"\"\n",[183,1095,1096,1099,1101,1104,1106,1109,1111,1114,1117,1119,1122,1125,1128,1131],{"class":125,"line":251},[183,1097,1098],{"class":192},"    missing_vars ",[183,1100,221],{"class":188},[183,1102,1103],{"class":192}," [v ",[183,1105,299],{"class":188},[183,1107,1108],{"class":192}," v ",[183,1110,305],{"class":188},[183,1112,1113],{"class":192}," e.referencedVariables() ",[183,1115,1116],{"class":188},"if",[183,1118,1108],{"class":192},[183,1120,1121],{"class":188},"not",[183,1123,1124],{"class":188}," in",[183,1126,1127],{"class":192}," ctx.variableNames() ",[183,1129,1130],{"class":188},"and",[183,1132,1133],{"class":192}," v]\n",[183,1135,1136,1139,1141,1144,1146,1149,1151],{"class":125,"line":257},[183,1137,1138],{"class":192},"    missing_fields ",[183,1140,221],{"class":188},[183,1142,1143],{"class":192}," [c ",[183,1145,299],{"class":188},[183,1147,1148],{"class":192}," c ",[183,1150,305],{"class":188},[183,1152,1153],{"class":192}," e.referencedColumns()\n",[183,1155,1156,1159,1161,1163,1165,1168,1171,1174,1177,1179,1181,1183],{"class":125,"line":262},[183,1157,1158],{"class":188},"                      if",[183,1160,1148],{"class":192},[183,1162,1130],{"class":188},[183,1164,1148],{"class":192},[183,1166,1167],{"class":188},"!=",[183,1169,1170],{"class":192}," QgsFeatureRequest.",[183,1172,1173],{"class":234},"ALL_ATTRIBUTES",[183,1175,1176],{"class":188}," and",[183,1178,1148],{"class":192},[183,1180,1121],{"class":188},[183,1182,1124],{"class":188},[183,1184,1185],{"class":192}," layer.fields().names()]\n",[183,1187,1188,1190,1193,1196],{"class":125,"line":290},[183,1189,405],{"class":188},[183,1191,1192],{"class":192}," missing_vars ",[183,1194,1195],{"class":188},"or",[183,1197,1198],{"class":192}," missing_fields:\n",[183,1200,1201,1203,1205,1208,1210,1213,1215,1218,1220,1223,1225],{"class":125,"line":296},[183,1202,1077],{"class":188},[183,1204,1080],{"class":188},[183,1206,1207],{"class":227},"\"missing variables ",[183,1209,434],{"class":234},[183,1211,1212],{"class":192},"missing_vars",[183,1214,442],{"class":234},[183,1216,1217],{"class":227}," \u002F fields ",[183,1219,434],{"class":234},[183,1221,1222],{"class":192},"missing_fields",[183,1224,442],{"class":234},[183,1226,1093],{"class":227},[183,1228,1229,1232,1234,1237],{"class":125,"line":320},[183,1230,1231],{"class":192},"    ctx.setFeature(feature ",[183,1233,1195],{"class":188},[183,1235,1236],{"class":234}," next",[183,1238,1239],{"class":192},"(layer.getFeatures()))\n",[183,1241,1242,1244,1246],{"class":125,"line":326},[183,1243,395],{"class":192},[183,1245,221],{"class":188},[183,1247,1248],{"class":192}," e.evaluate(ctx)\n",[183,1250,1251,1253],{"class":125,"line":938},[183,1252,405],{"class":188},[183,1254,1255],{"class":192}," e.hasEvalError():\n",[183,1257,1258,1260,1262,1265,1267,1270,1272],{"class":125,"line":944},[183,1259,1077],{"class":188},[183,1261,1080],{"class":188},[183,1263,1264],{"class":227},"\"evaluation error: ",[183,1266,434],{"class":234},[183,1268,1269],{"class":192},"e.evalErrorString()",[183,1271,442],{"class":234},[183,1273,1093],{"class":227},[183,1275,1276,1279,1281,1284,1286,1289,1292,1294],{"class":125,"line":955},[183,1277,1278],{"class":188},"    return",[183,1280,1080],{"class":188},[183,1282,1283],{"class":227},"\"value: ",[183,1285,434],{"class":234},[183,1287,1288],{"class":192},"value",[183,1290,1291],{"class":188},"!r",[183,1293,442],{"class":234},[183,1295,1093],{"class":227},[183,1297,1298],{"class":125,"line":967},[183,1299,212],{"emptyLinePlaceholder":211},[183,1301,1302,1304,1306,1308],{"class":125,"line":973},[183,1303,189],{"class":188},[183,1305,193],{"class":192},[183,1307,196],{"class":188},[183,1309,1310],{"class":192}," QgsFeatureRequest\n",[183,1312,1313,1315,1318,1321,1323,1326,1328,1330],{"class":125,"line":981},[183,1314,470],{"class":234},[183,1316,1317],{"class":192},"(diagnose(",[183,1319,1320],{"class":227},"\"@report_yr || ' ' || ",[183,1322,276],{"class":234},[183,1324,1325],{"class":227},"nme",[183,1327,276],{"class":234},[183,1329,459],{"class":227},[183,1331,1332],{"class":192},", layer))\n",[14,1334,1335,1337,1338,1341,1342,1345,1346,1341,1349,1352],{},[336,1336,338],{}," Checking the parser first catches syntax mistakes. ",[18,1339,1340],{},"referencedVariables"," and ",[18,1343,1344],{},"referencedColumns"," list what the expression needs; comparing them with what the context and layer provide catches typos — here ",[18,1347,1348],{},"@report_yr",[18,1350,1351],{},"\"nme\""," — before evaluation silently returns NULL. Only then is the expression evaluated against a real feature, where type errors such as adding text to a number appear as evaluation errors. Wrapping this in a helper makes it a one-line check in any script.",[144,1354,1356],{"id":1355},"pass-scopes-to-processing-and-layouts","Pass scopes to Processing and layouts",[14,1358,1359],{},"Processing algorithms and layouts build their own contexts. Variables set at project level reach them automatically; for per-run values, set them where those components will look.",[174,1361,1363],{"className":176,"code":1362,"language":178,"meta":179,"style":179},"import processing\nfrom qgis.core import QgsProcessingContext\n\npctx = QgsProcessingContext()\npctx.setProject(project)\nexp_ctx = pctx.expressionContext()\nexp_ctx.appendScope(QgsExpressionContextScope(\"run\"))\nexp_ctx.lastScope().setVariable(\"min_area\", 500)\npctx.setExpressionContext(exp_ctx)\n\nbig = processing.run(\"native:extractbyexpression\", {\n    \"INPUT\": layer, \"EXPRESSION\": \"$area >= @min_area\", \"OUTPUT\": \"memory:\"},\n    context=pctx)[\"OUTPUT\"]\nprint(big.featureCount(), \"large districts\")\n",[18,1364,1365,1372,1383,1387,1397,1402,1412,1422,1435,1440,1444,1460,1490,1504],{"__ignoreMap":179},[183,1366,1367,1369],{"class":125,"line":185},[183,1368,196],{"class":188},[183,1370,1371],{"class":192}," processing\n",[183,1373,1374,1376,1378,1380],{"class":125,"line":202},[183,1375,189],{"class":188},[183,1377,193],{"class":192},[183,1379,196],{"class":188},[183,1381,1382],{"class":192}," QgsProcessingContext\n",[183,1384,1385],{"class":125,"line":208},[183,1386,212],{"emptyLinePlaceholder":211},[183,1388,1389,1392,1394],{"class":125,"line":215},[183,1390,1391],{"class":192},"pctx ",[183,1393,221],{"class":188},[183,1395,1396],{"class":192}," QgsProcessingContext()\n",[183,1398,1399],{"class":125,"line":240},[183,1400,1401],{"class":192},"pctx.setProject(project)\n",[183,1403,1404,1407,1409],{"class":125,"line":251},[183,1405,1406],{"class":192},"exp_ctx ",[183,1408,221],{"class":188},[183,1410,1411],{"class":192}," pctx.expressionContext()\n",[183,1413,1414,1417,1420],{"class":125,"line":257},[183,1415,1416],{"class":192},"exp_ctx.appendScope(QgsExpressionContextScope(",[183,1418,1419],{"class":227},"\"run\"",[183,1421,1009],{"class":192},[183,1423,1424,1427,1429,1431,1433],{"class":125,"line":262},[183,1425,1426],{"class":192},"exp_ctx.lastScope().setVariable(",[183,1428,878],{"class":227},[183,1430,522],{"class":192},[183,1432,883],{"class":234},[183,1434,287],{"class":192},[183,1436,1437],{"class":125,"line":290},[183,1438,1439],{"class":192},"pctx.setExpressionContext(exp_ctx)\n",[183,1441,1442],{"class":125,"line":296},[183,1443,212],{"emptyLinePlaceholder":211},[183,1445,1446,1449,1451,1454,1457],{"class":125,"line":320},[183,1447,1448],{"class":192},"big ",[183,1450,221],{"class":188},[183,1452,1453],{"class":192}," processing.run(",[183,1455,1456],{"class":227},"\"native:extractbyexpression\"",[183,1458,1459],{"class":192},", {\n",[183,1461,1462,1465,1468,1471,1474,1477,1479,1482,1484,1487],{"class":125,"line":326},[183,1463,1464],{"class":227},"    \"INPUT\"",[183,1466,1467],{"class":192},": layer, ",[183,1469,1470],{"class":227},"\"EXPRESSION\"",[183,1472,1473],{"class":192},": ",[183,1475,1476],{"class":227},"\"$area >= @min_area\"",[183,1478,522],{"class":192},[183,1480,1481],{"class":227},"\"OUTPUT\"",[183,1483,1473],{"class":192},[183,1485,1486],{"class":227},"\"memory:\"",[183,1488,1489],{"class":192},"},\n",[183,1491,1492,1495,1497,1500,1502],{"class":125,"line":938},[183,1493,1494],{"class":888},"    context",[183,1496,221],{"class":188},[183,1498,1499],{"class":192},"pctx)[",[183,1501,1481],{"class":227},[183,1503,237],{"class":192},[183,1505,1506,1508,1511,1514],{"class":125,"line":944},[183,1507,470],{"class":234},[183,1509,1510],{"class":192},"(big.featureCount(), ",[183,1512,1513],{"class":227},"\"large districts\"",[183,1515,287],{"class":192},[14,1517,1518,1520,1521,1524,1525,162],{},[336,1519,338],{}," A Processing context carries an expression context that algorithms use when they evaluate expression parameters. Adding a scope to it makes run-specific variables available to expressions inside the algorithm, exactly as a model's variables would be. For layouts, set layout variables with ",[18,1522,1523],{},"setLayoutVariable",", which add a layout scope between project and item scopes, as used in ",[26,1526,1528],{"href":1527},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fload-layout-from-qpt-template-pyqgis\u002F","loading a layout from a template",[144,1530,1532],{"id":1531},"read-variables-in-custom-functions","Read variables in custom functions",[14,1534,1535],{},"Custom expression functions receive the context too, so they can read variables — letting one function behave differently per project or layer without extra arguments.",[174,1537,1539],{"className":176,"code":1538,"language":178,"meta":179,"style":179},"from qgis.core import qgsfunction\n\n@qgsfunction(args=\"auto\", group=\"Custom\", usesgeometry=False, referenced_columns=[])\ndef above_threshold(value, feature, parent, context):\n    threshold = context.variable(\"alert_threshold\") or 1000\n    return value is not None and value > threshold\n\nexpr = QgsExpression('above_threshold(\"population\")')\nctx = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\nf = next(layer.getFeatures())\nctx.setFeature(f)\nprint(expr.evaluate(ctx))\n",[18,1540,1541,1552,1556,1601,1611,1630,1654,1658,1671,1679,1691,1696],{"__ignoreMap":179},[183,1542,1543,1545,1547,1549],{"class":125,"line":185},[183,1544,189],{"class":188},[183,1546,193],{"class":192},[183,1548,196],{"class":188},[183,1550,1551],{"class":192}," qgsfunction\n",[183,1553,1554],{"class":125,"line":202},[183,1555,212],{"emptyLinePlaceholder":211},[183,1557,1558,1561,1563,1566,1568,1571,1573,1576,1578,1581,1583,1586,1588,1591,1593,1596,1598],{"class":125,"line":208},[183,1559,1560],{"class":1034},"@qgsfunction",[183,1562,425],{"class":192},[183,1564,1565],{"class":888},"args",[183,1567,221],{"class":188},[183,1569,1570],{"class":227},"\"auto\"",[183,1572,522],{"class":192},[183,1574,1575],{"class":888},"group",[183,1577,221],{"class":188},[183,1579,1580],{"class":227},"\"Custom\"",[183,1582,522],{"class":192},[183,1584,1585],{"class":888},"usesgeometry",[183,1587,221],{"class":188},[183,1589,1590],{"class":234},"False",[183,1592,522],{"class":192},[183,1594,1595],{"class":888},"referenced_columns",[183,1597,221],{"class":188},[183,1599,1600],{"class":192},"[])\n",[183,1602,1603,1605,1608],{"class":125,"line":215},[183,1604,1031],{"class":188},[183,1606,1607],{"class":1034}," above_threshold",[183,1609,1610],{"class":192},"(value, feature, parent, context):\n",[183,1612,1613,1616,1618,1621,1623,1625,1627],{"class":125,"line":240},[183,1614,1615],{"class":192},"    threshold ",[183,1617,221],{"class":188},[183,1619,1620],{"class":192}," context.variable(",[183,1622,659],{"class":227},[183,1624,502],{"class":192},[183,1626,1195],{"class":188},[183,1628,1629],{"class":234}," 1000\n",[183,1631,1632,1634,1637,1640,1642,1644,1646,1648,1651],{"class":125,"line":251},[183,1633,1278],{"class":188},[183,1635,1636],{"class":192}," value ",[183,1638,1639],{"class":188},"is",[183,1641,408],{"class":188},[183,1643,508],{"class":234},[183,1645,1176],{"class":188},[183,1647,1636],{"class":192},[183,1649,1650],{"class":188},">",[183,1652,1653],{"class":192}," threshold\n",[183,1655,1656],{"class":125,"line":257},[183,1657,212],{"emptyLinePlaceholder":211},[183,1659,1660,1662,1664,1666,1669],{"class":125,"line":262},[183,1661,265],{"class":192},[183,1663,221],{"class":188},[183,1665,270],{"class":192},[183,1667,1668],{"class":227},"'above_threshold(\"population\")'",[183,1670,287],{"class":192},[183,1672,1673,1675,1677],{"class":125,"line":290},[183,1674,690],{"class":192},[183,1676,221],{"class":188},[183,1678,1055],{"class":192},[183,1680,1681,1684,1686,1688],{"class":125,"line":296},[183,1682,1683],{"class":192},"f ",[183,1685,221],{"class":188},[183,1687,1236],{"class":234},[183,1689,1690],{"class":192},"(layer.getFeatures())\n",[183,1692,1693],{"class":125,"line":320},[183,1694,1695],{"class":192},"ctx.setFeature(f)\n",[183,1697,1698,1700],{"class":125,"line":326},[183,1699,470],{"class":234},[183,1701,1702],{"class":192},"(expr.evaluate(ctx))\n",[14,1704,1705,1707,1708,1711,1712,1715,1716,1720],{},[336,1706,338],{}," Declaring a ",[18,1709,1710],{},"context"," parameter gives the function access to the evaluation context, from which it reads the layer variable set earlier; a default applies where the variable is not defined. This keeps expressions short — ",[18,1713,1714],{},"above_threshold(\"population\")"," — while letting each layer set its own threshold. ",[26,1717,1719],{"href":1718},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fregister-custom-expression-function-pyqgis\u002F","Registering a custom expression function"," covers registration and distribution.",[144,1722,1724],{"id":1723},"qgis-version-compatibility","QGIS version compatibility",[14,1726,1727,1730,1731,1733,1734,1736],{},[18,1728,1729],{},"QgsExpressionContext",", the scope utilities and custom scopes work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. The ",[18,1732,1710],{}," argument for ",[18,1735,1560],{}," functions is available on all current releases. Variable names are stable; new built-in variables are added in most releases, so list them on your version.",[144,1738,1740],{"id":1739},"troubleshooting","Troubleshooting",[149,1742,1743,1749,1755,1763],{},[152,1744,1745,1748],{},[336,1746,1747],{},"Variables evaluate to NULL in scripts."," The project or layer scope is missing from the context.",[152,1750,1751,1754],{},[336,1752,1753],{},"Field references fail during preparation."," The layer scope was not added, so field names are unknown.",[152,1756,1757,1760,1761,162],{},[336,1758,1759],{},"A variable has an unexpected value."," A higher scope overrides it; check ",[18,1762,535],{},[152,1764,1765,1768],{},[336,1766,1767],{},"Project variables are lost."," The project was not saved after setting them.",[144,1770,1772],{"id":1771},"conclusion","Conclusion",[14,1774,1775],{},"Build contexts with the global, project and layer scopes plus the feature, inspect available variables when results are NULL, use project, layer and global variables as configuration, add custom scopes for per-run values, pass scopes into Processing contexts, and read variables in custom functions to keep expressions short.",[144,1777,1779],{"id":1778},"frequently-asked-questions","Frequently Asked Questions",[14,1781,1782,1785,1786,1789,1790,1793],{},[336,1783,1784],{},"What is the difference between @id and $id?","\nBoth give the feature id; ",[18,1787,1788],{},"@id"," is the variable form, ",[18,1791,1792],{},"$id"," the older function form.",[14,1795,1796,1799],{},[336,1797,1798],{},"Can variables hold geometries?","\nYes — scopes can hold any value, including geometries, as atlas and map scopes do.",[14,1801,1802,1805],{},[336,1803,1804],{},"Are variables available in the field calculator?","\nYes; the calculator builds the same global, project and layer context.",[14,1807,1808,1811],{},[336,1809,1810],{},"Do layer variables travel with a QML style?","\nYes. Layer variables are saved in the layer's style, so loading the QML on another layer brings them along — handy for thresholds that belong to a style.",[14,1813,1814,1817,1818,162],{},[336,1815,1816],{},"How do I remove a project variable?","\nUse ",[18,1819,1820],{},"QgsExpressionContextUtils.removeProjectVariable(project, name)",[144,1822,1824],{"id":1823},"related","Related",[149,1826,1827,1832,1837,1842,1848],{},[152,1828,1829,1831],{},[26,1830,29],{"href":28}," — the guide this recipe belongs to",[152,1833,1834],{},[26,1835,1836],{"href":160},"Evaluate a QGIS Expression in PyQGIS",[152,1838,1839],{},[26,1840,1841],{"href":1718},"Register a Custom Expression Function in PyQGIS",[152,1843,1844],{},[26,1845,1847],{"href":1846},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fuse-project-variables-and-metadata-pyqgis\u002F","Use Project Variables and Metadata in PyQGIS",[152,1849,1850],{},[26,1851,1853],{"href":1852},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fatlas-expressions-and-dynamic-text-pyqgis\u002F","Atlas Expressions and Dynamic Text in PyQGIS",[1855,1856,1857],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":179,"searchDepth":202,"depth":202,"links":1859},[1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872],{"id":146,"depth":202,"text":147},{"id":165,"depth":202,"text":166},{"id":366,"depth":202,"text":367},{"id":539,"depth":202,"text":540},{"id":724,"depth":202,"text":725},{"id":1017,"depth":202,"text":1018},{"id":1355,"depth":202,"text":1356},{"id":1531,"depth":202,"text":1532},{"id":1723,"depth":202,"text":1724},{"id":1739,"depth":202,"text":1740},{"id":1771,"depth":202,"text":1772},{"id":1778,"depth":202,"text":1779},{"id":1823,"depth":202,"text":1824},"Understand where expression variables come from — the global, project, layer and feature scopes of QgsExpressionContext, how later scopes override earlier ones, creating custom scopes with your own variables, and evaluating expressions in scripts with exactly the context QGIS would use.","md",{"slug":1876,"type":1877,"breadcrumb":1878,"datePublished":1879,"dateModified":1879},"use-expression-context-and-scopes-pyqgis","article","Use Expression Context and Scopes","2026-10-02","\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fuse-expression-context-and-scopes-pyqgis",{"title":5,"description":1873},"pyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fuse-expression-context-and-scopes-pyqgis\u002Findex","6U0Lp2xI3cqEBzKO63XIic11QttHMyhSdFaOkKbOu_g",1790966256279]