[{"data":1,"prerenderedAt":2042},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fstyle-processing-algorithm-outputs-pyqgis":3},{"id":4,"title":5,"body":6,"description":2031,"extension":2032,"meta":2033,"navigation":239,"path":2038,"seo":2039,"stem":2040,"__hash__":2041},"docs\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fstyle-processing-algorithm-outputs-pyqgis\u002Findex.md","Style Processing Algorithm Outputs in PyQGIS",{"type":7,"value":8,"toc":2016},"minimark",[9,13,17,31,163,168,183,187,195,685,711,715,718,788,1023,1051,1055,1058,1291,1300,1304,1307,1356,1452,1461,1465,1473,1582,1604,1608,1611,1860,1870,1874,1888,1892,1904,1908,1937,1941,1947,1951,1957,1966,1972,1978,1982,2012],[10,11,5],"h1",{"id":12},"style-processing-algorithm-outputs-in-pyqgis",[14,15,16],"p",{},"A custom Processing algorithm that produces a risk classification, a heat surface or a network of flow lines is only half done if the result lands on the map in a random single colour. Users then have to restyle it by hand every time, and they will each do it differently. Processing lets an algorithm style its own outputs: once the algorithm has finished and its layers have been loaded into the project, a post-processor runs on the main thread and can apply a renderer, a saved QML style, a layer name, or anything else the result needs.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002F","Processing Provider Plugins"," and builds on ",[21,27,29],{"href":28},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002F","writing a custom Processing algorithm",". It styles outputs with a layer post-processor, applies QML files shipped with the plugin, builds renderers in code, names output layers, and makes sure styling never interferes with headless runs.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,96,102,106,110,115,119,124,128,131,134,140,145,148,155,159],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 196","img","Steps from processAlgorithm writing a file, through loading the layer, to a post-processor applying style before the layer appears","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"When output styling happens",[45,46,47],"desc",{},"processAlgorithm runs in a background thread and writes the output to a file. Processing then loads the file as a layer on the main thread. Any layer post-processor registered for that output runs next and applies a style, name or renderer. Finally the styled layer appears on the map. Styling must happen in the post-processor, not in processAlgorithm, because layers may only be touched on the main thread.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","196","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"soFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Style after loading, on the main thread",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"24","70","160","110","#eff3ff","#2563eb","stroke-width:2",[73,91,95],{"x":92,"y":93,"style":94,"fill":88,"textAnchor":79},"104","102.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","processAlgorithm",[73,97,101],{"x":92,"y":98,"style":99,"fill":100,"textAnchor":79},"128.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","#59645f","background",[73,103,105],{"x":92,"y":104,"style":99,"fill":100,"textAnchor":79},"154.6","writes file",[49,107],{"x":108,"y":84,"width":85,"height":86,"rx":63,"fill":109,"stroke":100,"style":89},"213","#fffdf7",[73,111,114],{"x":112,"y":113,"style":94,"fill":78,"textAnchor":79},"293","115.6","load layer",[73,116,118],{"x":112,"y":117,"style":99,"fill":100,"textAnchor":79},"141.6","main thread",[49,120],{"x":121,"y":84,"width":85,"height":86,"rx":63,"fill":122,"stroke":123,"style":89},"402","#eef7f4","#0f766e",[73,125,127],{"x":126,"y":93,"style":94,"fill":123,"textAnchor":79},"482","post-processor",[73,129,130],{"x":126,"y":98,"style":99,"fill":100,"textAnchor":79},"apply style",[73,132,133],{"x":126,"y":104,"style":99,"fill":100,"textAnchor":79},"name, renderer",[49,135],{"x":136,"y":84,"width":137,"height":86,"rx":63,"fill":138,"stroke":139,"style":89},"591","145","#e8efe6","#15803d",[73,141,144],{"x":142,"y":113,"style":94,"fill":143,"textAnchor":79},"663.5","#166534","on the map",[73,146,147],{"x":142,"y":117,"style":99,"fill":100,"textAnchor":79},"styled",[149,150],"line",{"x1":151,"y1":152,"x2":153,"y2":152,"stroke":71,"style":154},"184","125","211","stroke-width:1.8;marker-end:url(#soFlowArrow)",[149,156],{"x1":157,"y1":152,"x2":158,"y2":152,"stroke":71,"style":154},"373","400",[149,160],{"x1":161,"y1":152,"x2":162,"y2":152,"stroke":71,"style":154},"562","589",[164,165,167],"h2",{"id":166},"prerequisites","Prerequisites",[169,170,171,175],"ul",{},[172,173,174],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[172,176,177,178,182],{},"A Processing algorithm in a plugin provider, as in ",[21,179,181],{"href":180},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fregister-processing-provider-in-plugin\u002F","registering a Processing provider",".",[164,184,186],{"id":185},"attach-a-layer-post-processor","Attach a layer post-processor",[14,188,189,190,194],{},"A post-processor is a small class with a ",[191,192,193],"code",{},"postProcessLayer"," method. The algorithm creates one per output and registers it with the output's layer details in the context.",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsProcessingAlgorithm, QgsProcessingLayerPostProcessorInterface,\n                       QgsProcessingParameterFeatureSink, QgsProcessingParameterFeatureSource,\n                       QgsProcessing, QgsFeatureSink)\n\nclass RiskStyler(QgsProcessingLayerPostProcessorInterface):\n    instance = None\n\n    def postProcessLayer(self, layer, context, feedback):\n        if not layer.isValid():\n            return\n        layer.loadNamedStyle(STYLE_PATH)          # see below\n        layer.triggerRepaint()\n\n    @staticmethod\n    def create():\n        RiskStyler.instance = RiskStyler()        # keep a reference alive\n        return RiskStyler.instance\n\nclass ClassifyRisk(QgsProcessingAlgorithm):\n    INPUT, OUTPUT = \"INPUT\", \"OUTPUT\"\n\n    def initAlgorithm(self, config=None):\n        self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, \"Assets\",\n                                                              [QgsProcessing.TypeVectorPoint]))\n        self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, \"Risk classes\"))\n\n    def processAlgorithm(self, parameters, context, feedback):\n        source = self.parameterAsSource(parameters, self.INPUT, context)\n        sink, dest_id = self.parameterAsSink(parameters, self.OUTPUT, context,\n                                             source.fields(), source.wkbType(), source.sourceCrs())\n        for f in source.getFeatures():\n            sink.addFeature(f, QgsFeatureSink.FastInsert)\n\n        if context.willLoadLayerOnCompletion(dest_id):\n            details = context.layerToLoadOnCompletionDetails(dest_id)\n            details.setPostProcessor(RiskStyler.create())\n        return {self.OUTPUT: dest_id}\n\n    def name(self): return \"classify_risk\"\n    def displayName(self): return \"Classify risk\"\n    def createInstance(self): return ClassifyRisk()\n","python","",[191,203,204,222,228,234,241,260,273,278,290,302,308,324,330,335,344,355,369,378,383,398,422,427,445,470,476,498,503,514,537,559,565,580,586,591,599,610,616,633,638,655,670],{"__ignoreMap":201},[205,206,208,212,216,219],"span",{"class":149,"line":207},1,[205,209,211],{"class":210},"snl16","from",[205,213,215],{"class":214},"s95oV"," qgis.core ",[205,217,218],{"class":210},"import",[205,220,221],{"class":214}," (QgsProcessingAlgorithm, QgsProcessingLayerPostProcessorInterface,\n",[205,223,225],{"class":149,"line":224},2,[205,226,227],{"class":214},"                       QgsProcessingParameterFeatureSink, QgsProcessingParameterFeatureSource,\n",[205,229,231],{"class":149,"line":230},3,[205,232,233],{"class":214},"                       QgsProcessing, QgsFeatureSink)\n",[205,235,237],{"class":149,"line":236},4,[205,238,240],{"emptyLinePlaceholder":239},true,"\n",[205,242,244,247,251,254,257],{"class":149,"line":243},5,[205,245,246],{"class":210},"class",[205,248,250],{"class":249},"svObZ"," RiskStyler",[205,252,253],{"class":214},"(",[205,255,256],{"class":249},"QgsProcessingLayerPostProcessorInterface",[205,258,259],{"class":214},"):\n",[205,261,263,266,269],{"class":149,"line":262},6,[205,264,265],{"class":214},"    instance ",[205,267,268],{"class":210},"=",[205,270,272],{"class":271},"sDLfK"," None\n",[205,274,276],{"class":149,"line":275},7,[205,277,240],{"emptyLinePlaceholder":239},[205,279,281,284,287],{"class":149,"line":280},8,[205,282,283],{"class":210},"    def",[205,285,286],{"class":249}," postProcessLayer",[205,288,289],{"class":214},"(self, layer, context, feedback):\n",[205,291,293,296,299],{"class":149,"line":292},9,[205,294,295],{"class":210},"        if",[205,297,298],{"class":210}," not",[205,300,301],{"class":214}," layer.isValid():\n",[205,303,305],{"class":149,"line":304},10,[205,306,307],{"class":210},"            return\n",[205,309,311,314,317,320],{"class":149,"line":310},11,[205,312,313],{"class":214},"        layer.loadNamedStyle(",[205,315,316],{"class":271},"STYLE_PATH",[205,318,319],{"class":214},")          ",[205,321,323],{"class":322},"sjoCn","# see below\n",[205,325,327],{"class":149,"line":326},12,[205,328,329],{"class":214},"        layer.triggerRepaint()\n",[205,331,333],{"class":149,"line":332},13,[205,334,240],{"emptyLinePlaceholder":239},[205,336,338,341],{"class":149,"line":337},14,[205,339,340],{"class":249},"    @",[205,342,343],{"class":271},"staticmethod\n",[205,345,347,349,352],{"class":149,"line":346},15,[205,348,283],{"class":210},[205,350,351],{"class":249}," create",[205,353,354],{"class":214},"():\n",[205,356,358,361,363,366],{"class":149,"line":357},16,[205,359,360],{"class":214},"        RiskStyler.instance ",[205,362,268],{"class":210},[205,364,365],{"class":214}," RiskStyler()        ",[205,367,368],{"class":322},"# keep a reference alive\n",[205,370,372,375],{"class":149,"line":371},17,[205,373,374],{"class":210},"        return",[205,376,377],{"class":214}," RiskStyler.instance\n",[205,379,381],{"class":149,"line":380},18,[205,382,240],{"emptyLinePlaceholder":239},[205,384,386,388,391,393,396],{"class":149,"line":385},19,[205,387,246],{"class":210},[205,389,390],{"class":249}," ClassifyRisk",[205,392,253],{"class":214},[205,394,395],{"class":249},"QgsProcessingAlgorithm",[205,397,259],{"class":214},[205,399,401,404,407,410,413,417,419],{"class":149,"line":400},20,[205,402,403],{"class":271},"    INPUT",[205,405,406],{"class":214},", ",[205,408,409],{"class":271},"OUTPUT",[205,411,412],{"class":210}," =",[205,414,416],{"class":415},"sU2Wk"," \"INPUT\"",[205,418,406],{"class":214},[205,420,421],{"class":415},"\"OUTPUT\"\n",[205,423,425],{"class":149,"line":424},21,[205,426,240],{"emptyLinePlaceholder":239},[205,428,430,432,435,438,440,443],{"class":149,"line":429},22,[205,431,283],{"class":210},[205,433,434],{"class":249}," initAlgorithm",[205,436,437],{"class":214},"(self, config",[205,439,268],{"class":210},[205,441,442],{"class":271},"None",[205,444,259],{"class":214},[205,446,448,451,454,457,459,462,464,467],{"class":149,"line":447},23,[205,449,450],{"class":271},"        self",[205,452,453],{"class":214},".addParameter(QgsProcessingParameterFeatureSource(",[205,455,456],{"class":271},"self",[205,458,182],{"class":214},[205,460,461],{"class":271},"INPUT",[205,463,406],{"class":214},[205,465,466],{"class":415},"\"Assets\"",[205,468,469],{"class":214},",\n",[205,471,473],{"class":149,"line":472},24,[205,474,475],{"class":214},"                                                              [QgsProcessing.TypeVectorPoint]))\n",[205,477,479,481,484,486,488,490,492,495],{"class":149,"line":478},25,[205,480,450],{"class":271},[205,482,483],{"class":214},".addParameter(QgsProcessingParameterFeatureSink(",[205,485,456],{"class":271},[205,487,182],{"class":214},[205,489,409],{"class":271},[205,491,406],{"class":214},[205,493,494],{"class":415},"\"Risk classes\"",[205,496,497],{"class":214},"))\n",[205,499,501],{"class":149,"line":500},26,[205,502,240],{"emptyLinePlaceholder":239},[205,504,506,508,511],{"class":149,"line":505},27,[205,507,283],{"class":210},[205,509,510],{"class":249}," processAlgorithm",[205,512,513],{"class":214},"(self, parameters, context, feedback):\n",[205,515,517,520,522,525,528,530,532,534],{"class":149,"line":516},28,[205,518,519],{"class":214},"        source ",[205,521,268],{"class":210},[205,523,524],{"class":271}," self",[205,526,527],{"class":214},".parameterAsSource(parameters, ",[205,529,456],{"class":271},[205,531,182],{"class":214},[205,533,461],{"class":271},[205,535,536],{"class":214},", context)\n",[205,538,540,543,545,547,550,552,554,556],{"class":149,"line":539},29,[205,541,542],{"class":214},"        sink, dest_id ",[205,544,268],{"class":210},[205,546,524],{"class":271},[205,548,549],{"class":214},".parameterAsSink(parameters, ",[205,551,456],{"class":271},[205,553,182],{"class":214},[205,555,409],{"class":271},[205,557,558],{"class":214},", context,\n",[205,560,562],{"class":149,"line":561},30,[205,563,564],{"class":214},"                                             source.fields(), source.wkbType(), source.sourceCrs())\n",[205,566,568,571,574,577],{"class":149,"line":567},31,[205,569,570],{"class":210},"        for",[205,572,573],{"class":214}," f ",[205,575,576],{"class":210},"in",[205,578,579],{"class":214}," source.getFeatures():\n",[205,581,583],{"class":149,"line":582},32,[205,584,585],{"class":214},"            sink.addFeature(f, QgsFeatureSink.FastInsert)\n",[205,587,589],{"class":149,"line":588},33,[205,590,240],{"emptyLinePlaceholder":239},[205,592,594,596],{"class":149,"line":593},34,[205,595,295],{"class":210},[205,597,598],{"class":214}," context.willLoadLayerOnCompletion(dest_id):\n",[205,600,602,605,607],{"class":149,"line":601},35,[205,603,604],{"class":214},"            details ",[205,606,268],{"class":210},[205,608,609],{"class":214}," context.layerToLoadOnCompletionDetails(dest_id)\n",[205,611,613],{"class":149,"line":612},36,[205,614,615],{"class":214},"            details.setPostProcessor(RiskStyler.create())\n",[205,617,619,621,624,626,628,630],{"class":149,"line":618},37,[205,620,374],{"class":210},[205,622,623],{"class":214}," {",[205,625,456],{"class":271},[205,627,182],{"class":214},[205,629,409],{"class":271},[205,631,632],{"class":214},": dest_id}\n",[205,634,636],{"class":149,"line":635},38,[205,637,240],{"emptyLinePlaceholder":239},[205,639,641,643,646,649,652],{"class":149,"line":640},39,[205,642,283],{"class":210},[205,644,645],{"class":249}," name",[205,647,648],{"class":214},"(self): ",[205,650,651],{"class":210},"return",[205,653,654],{"class":415}," \"classify_risk\"\n",[205,656,658,660,663,665,667],{"class":149,"line":657},40,[205,659,283],{"class":210},[205,661,662],{"class":249}," displayName",[205,664,648],{"class":214},[205,666,651],{"class":210},[205,668,669],{"class":415}," \"Classify risk\"\n",[205,671,673,675,678,680,682],{"class":149,"line":672},41,[205,674,283],{"class":210},[205,676,677],{"class":249}," createInstance",[205,679,648],{"class":214},[205,681,651],{"class":210},[205,683,684],{"class":214}," ClassifyRisk()\n",[14,686,687,691,692,695,696,699,700,703,704,707,708,710],{},[688,689,690],"strong",{},"Breakdown:"," ",[191,693,694],{},"willLoadLayerOnCompletion"," is true only when Processing is going to add this output to the project — when run from the toolbox with \"Open output file\" checked, or from ",[191,697,698],{},"processing.runAndLoadResults",". In that case ",[191,701,702],{},"layerToLoadOnCompletionDetails"," returns the details for the output, and ",[191,705,706],{},"setPostProcessor"," attaches the styler. The post-processor object must stay alive until it runs; a class-level reference handles that, since Python would otherwise garbage collect it while the algorithm returns. ",[191,709,193],{}," receives the loaded layer on the main thread, where applying styles is safe.",[164,712,714],{"id":713},"ship-qml-styles-with-the-plugin","Ship QML styles with the plugin",[14,716,717],{},"Styles designed interactively in QGIS and saved as QML files are the easiest to maintain. Put them in the plugin folder and load them by path.",[14,719,720],{},[34,721,723,726,729,731,738,741,746,752,757,760,764,769,772,776,779,784],{"viewBox":36,"role":37,"ariaLabel":722,"xmlns":39},"A QML style saved from QGIS into the plugin's styles folder and loaded by the post-processor at runtime",[41,724,725],{},"QML styles inside the plugin",[45,727,728],{},"A designer styles a sample output in QGIS and saves the style as risk_classes.qml into the plugin's styles folder. At runtime the post-processor resolves the path relative to the plugin file and calls loadNamedStyle. Updating the look is a matter of replacing the QML file, without touching Python.",[49,730],{"x":51,"y":51,"width":52,"height":53,"fill":54},[56,732,733],{},[59,734,736],{"id":735,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"soQmlArrow",[68,737],{"d":70,"fill":71},[73,739,740],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Design in QGIS, ship as a file",[49,742],{"x":83,"y":743,"width":744,"height":745,"rx":63,"fill":109,"stroke":100,"style":89},"60","222","120",[73,747,751],{"x":748,"y":749,"style":750,"fill":78,"textAnchor":79},"135","110.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","style in QGIS",[73,753,756],{"x":748,"y":754,"style":755,"fill":100,"textAnchor":79},"136.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","save as QML",[49,758],{"x":759,"y":743,"width":744,"height":745,"rx":63,"fill":122,"stroke":123,"style":89},"269",[73,761,763],{"x":75,"y":762,"style":94,"fill":123,"textAnchor":79},"110.6","plugin\u002Fstyles\u002F",[73,765,768],{"x":75,"y":766,"style":767,"fill":71,"textAnchor":79},"136.6","text-anchor:middle;font-size:9.5px;font-family:monospace","risk_classes.qml",[49,770],{"x":771,"y":743,"width":744,"height":745,"rx":63,"fill":138,"stroke":139,"style":89},"514",[73,773,775],{"x":774,"y":762,"style":94,"fill":143,"textAnchor":79},"625","loadNamedStyle",[73,777,778],{"x":774,"y":766,"style":99,"fill":100,"textAnchor":79},"in post-processor",[149,780],{"x1":781,"y1":745,"x2":782,"y2":745,"stroke":71,"style":783},"246","267","stroke-width:1.8;marker-end:url(#soQmlArrow)",[149,785],{"x1":786,"y1":745,"x2":787,"y2":745,"stroke":71,"style":783},"491","512",[196,789,791],{"className":198,"code":790,"language":200,"meta":201,"style":201},"import os\n\nPLUGIN_DIR = os.path.dirname(__file__)\nSTYLE_PATH = os.path.join(PLUGIN_DIR, \"styles\", \"risk_classes.qml\")\n\nclass QmlStyler(QgsProcessingLayerPostProcessorInterface):\n    _alive = []\n\n    def __init__(self, qml):\n        super().__init__()\n        self.qml = qml\n\n    def postProcessLayer(self, layer, context, feedback):\n        message, ok = layer.loadNamedStyle(self.qml)\n        if not ok:\n            feedback.reportError(f\"Style not applied: {message}\")\n        layer.triggerRepaint()\n\n    @classmethod\n    def create(cls, qml):\n        obj = cls(qml)\n        cls._alive.append(obj)\n        return obj\n",[191,792,793,800,804,820,843,847,860,870,874,884,898,910,914,922,937,946,971,975,979,986,995,1008,1016],{"__ignoreMap":201},[205,794,795,797],{"class":149,"line":207},[205,796,218],{"class":210},[205,798,799],{"class":214}," os\n",[205,801,802],{"class":149,"line":224},[205,803,240],{"emptyLinePlaceholder":239},[205,805,806,809,811,814,817],{"class":149,"line":230},[205,807,808],{"class":271},"PLUGIN_DIR",[205,810,412],{"class":210},[205,812,813],{"class":214}," os.path.dirname(",[205,815,816],{"class":271},"__file__",[205,818,819],{"class":214},")\n",[205,821,822,824,826,829,831,833,836,838,841],{"class":149,"line":236},[205,823,316],{"class":271},[205,825,412],{"class":210},[205,827,828],{"class":214}," os.path.join(",[205,830,808],{"class":271},[205,832,406],{"class":214},[205,834,835],{"class":415},"\"styles\"",[205,837,406],{"class":214},[205,839,840],{"class":415},"\"risk_classes.qml\"",[205,842,819],{"class":214},[205,844,845],{"class":149,"line":243},[205,846,240],{"emptyLinePlaceholder":239},[205,848,849,851,854,856,858],{"class":149,"line":262},[205,850,246],{"class":210},[205,852,853],{"class":249}," QmlStyler",[205,855,253],{"class":214},[205,857,256],{"class":249},[205,859,259],{"class":214},[205,861,862,865,867],{"class":149,"line":275},[205,863,864],{"class":214},"    _alive ",[205,866,268],{"class":210},[205,868,869],{"class":214}," []\n",[205,871,872],{"class":149,"line":280},[205,873,240],{"emptyLinePlaceholder":239},[205,875,876,878,881],{"class":149,"line":292},[205,877,283],{"class":210},[205,879,880],{"class":271}," __init__",[205,882,883],{"class":214},"(self, qml):\n",[205,885,886,889,892,895],{"class":149,"line":304},[205,887,888],{"class":271},"        super",[205,890,891],{"class":214},"().",[205,893,894],{"class":271},"__init__",[205,896,897],{"class":214},"()\n",[205,899,900,902,905,907],{"class":149,"line":310},[205,901,450],{"class":271},[205,903,904],{"class":214},".qml ",[205,906,268],{"class":210},[205,908,909],{"class":214}," qml\n",[205,911,912],{"class":149,"line":326},[205,913,240],{"emptyLinePlaceholder":239},[205,915,916,918,920],{"class":149,"line":332},[205,917,283],{"class":210},[205,919,286],{"class":249},[205,921,289],{"class":214},[205,923,924,927,929,932,934],{"class":149,"line":337},[205,925,926],{"class":214},"        message, ok ",[205,928,268],{"class":210},[205,930,931],{"class":214}," layer.loadNamedStyle(",[205,933,456],{"class":271},[205,935,936],{"class":214},".qml)\n",[205,938,939,941,943],{"class":149,"line":346},[205,940,295],{"class":210},[205,942,298],{"class":210},[205,944,945],{"class":214}," ok:\n",[205,947,948,951,954,957,960,963,966,969],{"class":149,"line":357},[205,949,950],{"class":214},"            feedback.reportError(",[205,952,953],{"class":210},"f",[205,955,956],{"class":415},"\"Style not applied: ",[205,958,959],{"class":271},"{",[205,961,962],{"class":214},"message",[205,964,965],{"class":271},"}",[205,967,968],{"class":415},"\"",[205,970,819],{"class":214},[205,972,973],{"class":149,"line":371},[205,974,329],{"class":214},[205,976,977],{"class":149,"line":380},[205,978,240],{"emptyLinePlaceholder":239},[205,980,981,983],{"class":149,"line":385},[205,982,340],{"class":249},[205,984,985],{"class":271},"classmethod\n",[205,987,988,990,992],{"class":149,"line":400},[205,989,283],{"class":210},[205,991,351],{"class":249},[205,993,994],{"class":214},"(cls, qml):\n",[205,996,997,1000,1002,1005],{"class":149,"line":424},[205,998,999],{"class":214},"        obj ",[205,1001,268],{"class":210},[205,1003,1004],{"class":271}," cls",[205,1006,1007],{"class":214},"(qml)\n",[205,1009,1010,1013],{"class":149,"line":429},[205,1011,1012],{"class":271},"        cls",[205,1014,1015],{"class":214},"._alive.append(obj)\n",[205,1017,1018,1020],{"class":149,"line":447},[205,1019,374],{"class":210},[205,1021,1022],{"class":214}," obj\n",[14,1024,1025,1027,1028,1030,1031,1033,1034,1037,1038,1041,1042,1045,1046,1050],{},[688,1026,690],{}," Paths resolved from ",[191,1029,816],{}," work wherever the plugin is installed. ",[191,1032,775],{}," returns a message and a success flag; reporting failures through ",[191,1035,1036],{},"feedback"," puts them in the algorithm's log, where users will see them. A generic ",[191,1039,1040],{},"QmlStyler"," that takes the QML path serves every output of every algorithm in the provider. The QML must match the output's field names — a categorised renderer keyed on ",[191,1043,1044],{},"risk_class"," needs that field to exist. Styles can also be saved with ",[21,1047,1049],{"href":1048},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002F","layer styling code"," if you prefer them in Python.",[164,1052,1054],{"id":1053},"build-the-renderer-in-code","Build the renderer in code",[14,1056,1057],{},"When the style depends on the data — class breaks computed from the result, or categories that are only known after running — build the renderer in the post-processor.",[196,1059,1061],{"className":198,"code":1060,"language":200,"meta":201,"style":201},"from qgis.core import (QgsGraduatedSymbolRenderer, QgsClassificationQuantile,\n                       QgsStyle, QgsSymbol)\n\nclass GraduatedStyler(QgsProcessingLayerPostProcessorInterface):\n    _alive = []\n\n    def __init__(self, field, classes=5, ramp=\"Reds\"):\n        super().__init__()\n        self.field, self.classes, self.ramp = field, classes, ramp\n\n    def postProcessLayer(self, layer, context, feedback):\n        renderer = QgsGraduatedSymbolRenderer(self.field)\n        renderer.setClassificationMethod(QgsClassificationQuantile())\n        renderer.setSourceSymbol(QgsSymbol.defaultSymbol(layer.geometryType()))\n        renderer.setSourceColorRamp(QgsStyle.defaultStyle().colorRamp(self.ramp))\n        renderer.updateClasses(layer, self.classes)\n        layer.setRenderer(renderer)\n        layer.triggerRepaint()\n\n    @classmethod\n    def create(cls, *args, **kwargs):\n        obj = cls(*args, **kwargs)\n        cls._alive.append(obj)\n        return obj\n",[191,1062,1063,1074,1079,1083,1096,1104,1108,1131,1141,1163,1167,1175,1190,1195,1200,1210,1220,1225,1229,1233,1239,1260,1279,1285],{"__ignoreMap":201},[205,1064,1065,1067,1069,1071],{"class":149,"line":207},[205,1066,211],{"class":210},[205,1068,215],{"class":214},[205,1070,218],{"class":210},[205,1072,1073],{"class":214}," (QgsGraduatedSymbolRenderer, QgsClassificationQuantile,\n",[205,1075,1076],{"class":149,"line":224},[205,1077,1078],{"class":214},"                       QgsStyle, QgsSymbol)\n",[205,1080,1081],{"class":149,"line":230},[205,1082,240],{"emptyLinePlaceholder":239},[205,1084,1085,1087,1090,1092,1094],{"class":149,"line":236},[205,1086,246],{"class":210},[205,1088,1089],{"class":249}," GraduatedStyler",[205,1091,253],{"class":214},[205,1093,256],{"class":249},[205,1095,259],{"class":214},[205,1097,1098,1100,1102],{"class":149,"line":243},[205,1099,864],{"class":214},[205,1101,268],{"class":210},[205,1103,869],{"class":214},[205,1105,1106],{"class":149,"line":262},[205,1107,240],{"emptyLinePlaceholder":239},[205,1109,1110,1112,1114,1117,1119,1121,1124,1126,1129],{"class":149,"line":275},[205,1111,283],{"class":210},[205,1113,880],{"class":271},[205,1115,1116],{"class":214},"(self, field, classes",[205,1118,268],{"class":210},[205,1120,64],{"class":271},[205,1122,1123],{"class":214},", ramp",[205,1125,268],{"class":210},[205,1127,1128],{"class":415},"\"Reds\"",[205,1130,259],{"class":214},[205,1132,1133,1135,1137,1139],{"class":149,"line":280},[205,1134,888],{"class":271},[205,1136,891],{"class":214},[205,1138,894],{"class":271},[205,1140,897],{"class":214},[205,1142,1143,1145,1148,1150,1153,1155,1158,1160],{"class":149,"line":292},[205,1144,450],{"class":271},[205,1146,1147],{"class":214},".field, ",[205,1149,456],{"class":271},[205,1151,1152],{"class":214},".classes, ",[205,1154,456],{"class":271},[205,1156,1157],{"class":214},".ramp ",[205,1159,268],{"class":210},[205,1161,1162],{"class":214}," field, classes, ramp\n",[205,1164,1165],{"class":149,"line":304},[205,1166,240],{"emptyLinePlaceholder":239},[205,1168,1169,1171,1173],{"class":149,"line":310},[205,1170,283],{"class":210},[205,1172,286],{"class":249},[205,1174,289],{"class":214},[205,1176,1177,1180,1182,1185,1187],{"class":149,"line":326},[205,1178,1179],{"class":214},"        renderer ",[205,1181,268],{"class":210},[205,1183,1184],{"class":214}," QgsGraduatedSymbolRenderer(",[205,1186,456],{"class":271},[205,1188,1189],{"class":214},".field)\n",[205,1191,1192],{"class":149,"line":332},[205,1193,1194],{"class":214},"        renderer.setClassificationMethod(QgsClassificationQuantile())\n",[205,1196,1197],{"class":149,"line":337},[205,1198,1199],{"class":214},"        renderer.setSourceSymbol(QgsSymbol.defaultSymbol(layer.geometryType()))\n",[205,1201,1202,1205,1207],{"class":149,"line":346},[205,1203,1204],{"class":214},"        renderer.setSourceColorRamp(QgsStyle.defaultStyle().colorRamp(",[205,1206,456],{"class":271},[205,1208,1209],{"class":214},".ramp))\n",[205,1211,1212,1215,1217],{"class":149,"line":357},[205,1213,1214],{"class":214},"        renderer.updateClasses(layer, ",[205,1216,456],{"class":271},[205,1218,1219],{"class":214},".classes)\n",[205,1221,1222],{"class":149,"line":371},[205,1223,1224],{"class":214},"        layer.setRenderer(renderer)\n",[205,1226,1227],{"class":149,"line":380},[205,1228,329],{"class":214},[205,1230,1231],{"class":149,"line":385},[205,1232,240],{"emptyLinePlaceholder":239},[205,1234,1235,1237],{"class":149,"line":400},[205,1236,340],{"class":249},[205,1238,985],{"class":271},[205,1240,1241,1243,1245,1248,1251,1254,1257],{"class":149,"line":424},[205,1242,283],{"class":210},[205,1244,351],{"class":249},[205,1246,1247],{"class":214},"(cls, ",[205,1249,1250],{"class":210},"*",[205,1252,1253],{"class":214},"args, ",[205,1255,1256],{"class":210},"**",[205,1258,1259],{"class":214},"kwargs):\n",[205,1261,1262,1264,1266,1268,1270,1272,1274,1276],{"class":149,"line":429},[205,1263,999],{"class":214},[205,1265,268],{"class":210},[205,1267,1004],{"class":271},[205,1269,253],{"class":214},[205,1271,1250],{"class":210},[205,1273,1253],{"class":214},[205,1275,1256],{"class":210},[205,1277,1278],{"class":214},"kwargs)\n",[205,1280,1281,1283],{"class":149,"line":447},[205,1282,1012],{"class":271},[205,1284,1015],{"class":214},[205,1286,1287,1289],{"class":149,"line":472},[205,1288,374],{"class":210},[205,1290,1022],{"class":214},[14,1292,1293,1295,1296,182],{},[688,1294,690],{}," The graduated renderer classifies the chosen field into quantiles computed from the actual output, using a colour ramp from the default style library. Because the class breaks come from the result, each run is styled to its own data range. The same approach suits categorised renderers whose categories are the unique values found, as in ",[21,1297,1299],{"href":1298},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002F","graduated and categorised renderers",[164,1301,1303],{"id":1302},"name-output-layers","Name output layers",[14,1305,1306],{},"By default outputs load with the parameter's description as the layer name. The layer details let the algorithm give a more useful name.",[14,1308,1309],{},[34,1310,1313,1316,1319,1322,1325,1331,1336,1340,1343,1346,1350,1353],{"viewBox":1311,"role":37,"ariaLabel":1312,"xmlns":39},"0 0 760 182","Three identical default output layer names compared with descriptive names set by the algorithm",[41,1314,1315],{},"Default versus chosen layer names",[45,1317,1318],{},"Without naming, every run adds a layer called Risk classes, so three runs give three identical names in the Layers panel. With setName, the algorithm names each output after its input and threshold, such as Risk classes, north assets, 0.7, so runs are distinguishable.",[49,1320],{"x":51,"y":51,"width":52,"height":1321,"fill":54},"182",[73,1323,1324],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Make runs distinguishable",[49,1326],{"x":83,"y":1327,"width":1328,"height":86,"rx":63,"fill":1329,"stroke":1330,"style":89},"56","340","#fdf2e2","#b91c1c",[73,1332,1335],{"x":1333,"y":1334,"style":94,"fill":1330,"textAnchor":79},"194","90.6","default",[73,1337,1339],{"x":1333,"y":1338,"style":767,"fill":71,"textAnchor":79},"114.6","Risk classes",[73,1341,1339],{"x":1333,"y":1342,"style":767,"fill":71,"textAnchor":79},"138.6",[49,1344],{"x":1345,"y":1327,"width":1328,"height":86,"rx":63,"fill":138,"stroke":139,"style":89},"396",[73,1347,1349],{"x":1348,"y":1334,"style":94,"fill":143,"textAnchor":79},"566","named",[73,1351,1352],{"x":1348,"y":1338,"style":767,"fill":71,"textAnchor":79},"Risk – north assets – 0.7",[73,1354,1355],{"x":1348,"y":1342,"style":767,"fill":71,"textAnchor":79},"Risk – south assets – 0.5",[196,1357,1359],{"className":198,"code":1358,"language":200,"meta":201,"style":201},"    def processAlgorithm(self, parameters, context, feedback):\n        source = self.parameterAsSource(parameters, self.INPUT, context)\n        # ... create sink and write features as before ...\n        if context.willLoadLayerOnCompletion(dest_id):\n            details = context.layerToLoadOnCompletionDetails(dest_id)\n            details.name = f\"Risk – {source.sourceName()}\"\n            details.setPostProcessor(QmlStyler.create(STYLE_PATH))\n        return {self.OUTPUT: dest_id}\n",[191,1360,1361,1369,1387,1392,1398,1406,1429,1438],{"__ignoreMap":201},[205,1362,1363,1365,1367],{"class":149,"line":207},[205,1364,283],{"class":210},[205,1366,510],{"class":249},[205,1368,513],{"class":214},[205,1370,1371,1373,1375,1377,1379,1381,1383,1385],{"class":149,"line":224},[205,1372,519],{"class":214},[205,1374,268],{"class":210},[205,1376,524],{"class":271},[205,1378,527],{"class":214},[205,1380,456],{"class":271},[205,1382,182],{"class":214},[205,1384,461],{"class":271},[205,1386,536],{"class":214},[205,1388,1389],{"class":149,"line":230},[205,1390,1391],{"class":322},"        # ... create sink and write features as before ...\n",[205,1393,1394,1396],{"class":149,"line":236},[205,1395,295],{"class":210},[205,1397,598],{"class":214},[205,1399,1400,1402,1404],{"class":149,"line":243},[205,1401,604],{"class":214},[205,1403,268],{"class":210},[205,1405,609],{"class":214},[205,1407,1408,1411,1413,1416,1419,1421,1424,1426],{"class":149,"line":262},[205,1409,1410],{"class":214},"            details.name ",[205,1412,268],{"class":210},[205,1414,1415],{"class":210}," f",[205,1417,1418],{"class":415},"\"Risk – ",[205,1420,959],{"class":271},[205,1422,1423],{"class":214},"source.sourceName()",[205,1425,965],{"class":271},[205,1427,1428],{"class":415},"\"\n",[205,1430,1431,1434,1436],{"class":149,"line":275},[205,1432,1433],{"class":214},"            details.setPostProcessor(QmlStyler.create(",[205,1435,316],{"class":271},[205,1437,497],{"class":214},[205,1439,1440,1442,1444,1446,1448,1450],{"class":149,"line":280},[205,1441,374],{"class":210},[205,1443,623],{"class":214},[205,1445,456],{"class":271},[205,1447,182],{"class":214},[205,1449,409],{"class":271},[205,1451,632],{"class":214},[14,1453,1454,1456,1457,1460],{},[688,1455,690],{}," Setting ",[191,1458,1459],{},"details.name"," changes the name the layer receives when it is added. Including the input's name and key parameters makes several runs distinguishable in the Layers panel. Some users enable the Processing option to name outputs after their file names; an explicit name from the algorithm is clearer for most results.",[164,1462,1464],{"id":1463},"keep-styling-out-of-headless-runs","Keep styling out of headless runs",[14,1466,1467,1468,1472],{},"Styling only makes sense when a layer is being added to a project. In scripts, models and ",[21,1469,1471],{"href":1470},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","headless runs",", outputs are just files.",[196,1474,1476],{"className":198,"code":1475,"language":200,"meta":201,"style":201},"import processing\n\nresult = processing.run(\"assets:classify_risk\",\n                        {\"INPUT\": \"\u002Fdata\u002Fassets.gpkg\", \"OUTPUT\": \"\u002Fdata\u002Frisk.gpkg\"})\nprint(result[\"OUTPUT\"])          # a file path; no post-processor ran\n\nprocessing.runAndLoadResults(\"assets:classify_risk\",\n                             {\"INPUT\": \"\u002Fdata\u002Fassets.gpkg\", \"OUTPUT\": \"TEMPORARY_OUTPUT\"})\n",[191,1477,1478,1485,1489,1504,1531,1547,1551,1560],{"__ignoreMap":201},[205,1479,1480,1482],{"class":149,"line":207},[205,1481,218],{"class":210},[205,1483,1484],{"class":214}," processing\n",[205,1486,1487],{"class":149,"line":224},[205,1488,240],{"emptyLinePlaceholder":239},[205,1490,1491,1494,1496,1499,1502],{"class":149,"line":230},[205,1492,1493],{"class":214},"result ",[205,1495,268],{"class":210},[205,1497,1498],{"class":214}," processing.run(",[205,1500,1501],{"class":415},"\"assets:classify_risk\"",[205,1503,469],{"class":214},[205,1505,1506,1509,1512,1515,1518,1520,1523,1525,1528],{"class":149,"line":236},[205,1507,1508],{"class":214},"                        {",[205,1510,1511],{"class":415},"\"INPUT\"",[205,1513,1514],{"class":214},": ",[205,1516,1517],{"class":415},"\"\u002Fdata\u002Fassets.gpkg\"",[205,1519,406],{"class":214},[205,1521,1522],{"class":415},"\"OUTPUT\"",[205,1524,1514],{"class":214},[205,1526,1527],{"class":415},"\"\u002Fdata\u002Frisk.gpkg\"",[205,1529,1530],{"class":214},"})\n",[205,1532,1533,1536,1539,1541,1544],{"class":149,"line":243},[205,1534,1535],{"class":271},"print",[205,1537,1538],{"class":214},"(result[",[205,1540,1522],{"class":415},[205,1542,1543],{"class":214},"])          ",[205,1545,1546],{"class":322},"# a file path; no post-processor ran\n",[205,1548,1549],{"class":149,"line":262},[205,1550,240],{"emptyLinePlaceholder":239},[205,1552,1553,1556,1558],{"class":149,"line":275},[205,1554,1555],{"class":214},"processing.runAndLoadResults(",[205,1557,1501],{"class":415},[205,1559,469],{"class":214},[205,1561,1562,1565,1567,1569,1571,1573,1575,1577,1580],{"class":149,"line":280},[205,1563,1564],{"class":214},"                             {",[205,1566,1511],{"class":415},[205,1568,1514],{"class":214},[205,1570,1517],{"class":415},[205,1572,406],{"class":214},[205,1574,1522],{"class":415},[205,1576,1514],{"class":214},[205,1578,1579],{"class":415},"\"TEMPORARY_OUTPUT\"",[205,1581,1530],{"class":214},[14,1583,1584,691,1586,1589,1590,1592,1593,1596,1597,1599,1600,1603],{},[688,1585,690],{},[191,1587,1588],{},"processing.run"," does not load outputs, so ",[191,1591,694],{}," is false and no styling code runs — exactly right for batch jobs and servers. ",[191,1594,1595],{},"runAndLoadResults"," behaves like the toolbox and triggers the post-processor. Guarding with ",[191,1598,694],{}," is what keeps one algorithm correct in both situations. If headless users also want styles, write a sidecar ",[191,1601,1602],{},".qml"," next to file outputs, which QGIS picks up automatically when the file is opened later.",[164,1605,1607],{"id":1606},"place-outputs-in-a-group","Place outputs in a group",[14,1609,1610],{},"Algorithms that produce several layers — a result, a summary table and a flagged-errors layer, say — clutter the Layers panel when each lands at the top. A post-processor can move its layer into a named group after loading.",[196,1612,1614],{"className":198,"code":1613,"language":200,"meta":201,"style":201},"from qgis.core import QgsProject\n\nclass GroupPlacer(QgsProcessingLayerPostProcessorInterface):\n    _alive = []\n\n    def __init__(self, group_name, qml=None):\n        super().__init__()\n        self.group_name, self.qml = group_name, qml\n\n    def postProcessLayer(self, layer, context, feedback):\n        if self.qml:\n            layer.loadNamedStyle(self.qml)\n        root = QgsProject.instance().layerTreeRoot()\n        group = root.findGroup(self.group_name) or root.insertGroup(0, self.group_name)\n        node = root.findLayer(layer.id())\n        if node is not None:\n            group.insertChildNode(0, node.clone())\n            node.parent().removeChildNode(node)\n\n    @classmethod\n    def create(cls, *args):\n        obj = cls(*args)\n        cls._alive.append(obj)\n        return obj\n",[191,1615,1616,1627,1631,1644,1652,1656,1671,1681,1697,1701,1709,1718,1727,1737,1767,1777,1795,1805,1810,1814,1820,1833,1848,1854],{"__ignoreMap":201},[205,1617,1618,1620,1622,1624],{"class":149,"line":207},[205,1619,211],{"class":210},[205,1621,215],{"class":214},[205,1623,218],{"class":210},[205,1625,1626],{"class":214}," QgsProject\n",[205,1628,1629],{"class":149,"line":224},[205,1630,240],{"emptyLinePlaceholder":239},[205,1632,1633,1635,1638,1640,1642],{"class":149,"line":230},[205,1634,246],{"class":210},[205,1636,1637],{"class":249}," GroupPlacer",[205,1639,253],{"class":214},[205,1641,256],{"class":249},[205,1643,259],{"class":214},[205,1645,1646,1648,1650],{"class":149,"line":236},[205,1647,864],{"class":214},[205,1649,268],{"class":210},[205,1651,869],{"class":214},[205,1653,1654],{"class":149,"line":243},[205,1655,240],{"emptyLinePlaceholder":239},[205,1657,1658,1660,1662,1665,1667,1669],{"class":149,"line":262},[205,1659,283],{"class":210},[205,1661,880],{"class":271},[205,1663,1664],{"class":214},"(self, group_name, qml",[205,1666,268],{"class":210},[205,1668,442],{"class":271},[205,1670,259],{"class":214},[205,1672,1673,1675,1677,1679],{"class":149,"line":275},[205,1674,888],{"class":271},[205,1676,891],{"class":214},[205,1678,894],{"class":271},[205,1680,897],{"class":214},[205,1682,1683,1685,1688,1690,1692,1694],{"class":149,"line":280},[205,1684,450],{"class":271},[205,1686,1687],{"class":214},".group_name, ",[205,1689,456],{"class":271},[205,1691,904],{"class":214},[205,1693,268],{"class":210},[205,1695,1696],{"class":214}," group_name, qml\n",[205,1698,1699],{"class":149,"line":292},[205,1700,240],{"emptyLinePlaceholder":239},[205,1702,1703,1705,1707],{"class":149,"line":304},[205,1704,283],{"class":210},[205,1706,286],{"class":249},[205,1708,289],{"class":214},[205,1710,1711,1713,1715],{"class":149,"line":310},[205,1712,295],{"class":210},[205,1714,524],{"class":271},[205,1716,1717],{"class":214},".qml:\n",[205,1719,1720,1723,1725],{"class":149,"line":326},[205,1721,1722],{"class":214},"            layer.loadNamedStyle(",[205,1724,456],{"class":271},[205,1726,936],{"class":214},[205,1728,1729,1732,1734],{"class":149,"line":332},[205,1730,1731],{"class":214},"        root ",[205,1733,268],{"class":210},[205,1735,1736],{"class":214}," QgsProject.instance().layerTreeRoot()\n",[205,1738,1739,1742,1744,1747,1749,1752,1755,1758,1760,1762,1764],{"class":149,"line":337},[205,1740,1741],{"class":214},"        group ",[205,1743,268],{"class":210},[205,1745,1746],{"class":214}," root.findGroup(",[205,1748,456],{"class":271},[205,1750,1751],{"class":214},".group_name) ",[205,1753,1754],{"class":210},"or",[205,1756,1757],{"class":214}," root.insertGroup(",[205,1759,51],{"class":271},[205,1761,406],{"class":214},[205,1763,456],{"class":271},[205,1765,1766],{"class":214},".group_name)\n",[205,1768,1769,1772,1774],{"class":149,"line":346},[205,1770,1771],{"class":214},"        node ",[205,1773,268],{"class":210},[205,1775,1776],{"class":214}," root.findLayer(layer.id())\n",[205,1778,1779,1781,1784,1787,1789,1792],{"class":149,"line":357},[205,1780,295],{"class":210},[205,1782,1783],{"class":214}," node ",[205,1785,1786],{"class":210},"is",[205,1788,298],{"class":210},[205,1790,1791],{"class":271}," None",[205,1793,1794],{"class":214},":\n",[205,1796,1797,1800,1802],{"class":149,"line":371},[205,1798,1799],{"class":214},"            group.insertChildNode(",[205,1801,51],{"class":271},[205,1803,1804],{"class":214},", node.clone())\n",[205,1806,1807],{"class":149,"line":380},[205,1808,1809],{"class":214},"            node.parent().removeChildNode(node)\n",[205,1811,1812],{"class":149,"line":385},[205,1813,240],{"emptyLinePlaceholder":239},[205,1815,1816,1818],{"class":149,"line":400},[205,1817,340],{"class":249},[205,1819,985],{"class":271},[205,1821,1822,1824,1826,1828,1830],{"class":149,"line":424},[205,1823,283],{"class":210},[205,1825,351],{"class":249},[205,1827,1247],{"class":214},[205,1829,1250],{"class":210},[205,1831,1832],{"class":214},"args):\n",[205,1834,1835,1837,1839,1841,1843,1845],{"class":149,"line":429},[205,1836,999],{"class":214},[205,1838,268],{"class":210},[205,1840,1004],{"class":271},[205,1842,253],{"class":214},[205,1844,1250],{"class":210},[205,1846,1847],{"class":214},"args)\n",[205,1849,1850,1852],{"class":149,"line":447},[205,1851,1012],{"class":271},[205,1853,1015],{"class":214},[205,1855,1856,1858],{"class":149,"line":472},[205,1857,374],{"class":210},[205,1859,1022],{"class":214},[14,1861,1862,1864,1865,1869],{},[688,1863,690],{}," The group is created at the top of the tree on first use and reused afterwards, so repeated runs collect their results together. Moving a layer tree node is done by inserting a clone in the new parent and removing the original, the standard pattern for ",[21,1866,1868],{"href":1867},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002F","working with the layer tree",". Combining grouping with styling in one post-processor keeps each output's presentation in one place.",[164,1871,1873],{"id":1872},"test-the-styling","Test the styling",[14,1875,1876,1877,1879,1880,1882,1883,1887],{},"Post-processors are ordinary Python classes, so they can be tested without running the algorithm. Create a memory layer with the fields the output will have, call ",[191,1878,193],{}," directly, and assert on the renderer type, the number of classes, or the layer name. A test like this catches the most common regression — a field renamed in the algorithm but not in the QML — long before a user sees an unstyled result. Running the whole algorithm with ",[191,1881,1595],{}," in an integration test, as described in ",[21,1884,1886],{"href":1885},"\u002Fqgis-plugin-development\u002Ftesting-and-ci-for-plugins\u002Ftest-processing-algorithms-with-pytest-qgis\u002F","testing Processing algorithms with pytest",", then confirms that the post-processor is actually attached.",[164,1889,1891],{"id":1890},"qgis-version-compatibility","QGIS version compatibility",[14,1893,1894,406,1896,406,1898,1900,1901,1903],{},[191,1895,256],{},[191,1897,702],{},[191,1899,706],{}," and ",[191,1902,1459],{}," are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. Enum names may appear in scoped form in QGIS 4; the renderer and style APIs used here are unchanged.",[164,1905,1907],{"id":1906},"troubleshooting","Troubleshooting",[169,1909,1910,1916,1925,1931],{},[172,1911,1912,1915],{},[688,1913,1914],{},"The style is never applied."," The post-processor was garbage collected; keep a reference to it.",[172,1917,1918,1921,1922,1924],{},[688,1919,1920],{},"QGIS crashes when styling."," Styling was done in ",[191,1923,95],{},"; move it into the post-processor.",[172,1926,1927,1930],{},[688,1928,1929],{},"A categorised style shows everything as \"other\"."," The QML expects field names or values that the output does not have.",[172,1932,1933,1936],{},[688,1934,1935],{},"Styles apply in the toolbox but not in a model."," Intermediate model outputs are not loaded; only final outputs receive post-processors.",[164,1938,1940],{"id":1939},"conclusion","Conclusion",[14,1942,1943,1944,1946],{},"Attach a layer post-processor to each output that will be loaded, apply QML styles shipped with the plugin or build renderers from the result's data, name outputs meaningfully, keep references to post-processors alive, and guard everything with ",[191,1945,694],{}," so headless runs stay untouched.",[164,1948,1950],{"id":1949},"frequently-asked-questions","Frequently Asked Questions",[14,1952,1953,1956],{},[688,1954,1955],{},"Can one post-processor serve several outputs?","\nYes; attach a separate instance, or the same class with different parameters, to each.",[14,1958,1959,1962,1963,1965],{},[688,1960,1961],{},"Can I style raster outputs?","\nYes; the post-processor receives raster layers too, and ",[191,1964,775],{}," or a raster renderer works the same way.",[14,1967,1968,1971],{},[688,1969,1970],{},"Can I add the output to a specific group?","\nYes; in the post-processor, move the layer's tree node into a group of your choice.",[14,1973,1974,1977],{},[688,1975,1976],{},"Does this work for scripts using the @alg decorator?","\nYes; the decorator produces a normal algorithm with access to the same context.",[164,1979,1981],{"id":1980},"related","Related",[169,1983,1984,1989,1994,2000,2006],{},[172,1985,1986,1988],{},[21,1987,24],{"href":23}," — the guide this recipe belongs to",[172,1990,1991],{},[21,1992,1993],{"href":28},"Write a Custom Processing Algorithm in PyQGIS",[172,1995,1996],{},[21,1997,1999],{"href":1998},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fadd-parameters-to-processing-algorithm-pyqgis\u002F","Add Parameters to a Processing Algorithm",[172,2001,2002],{},[21,2003,2005],{"href":2004},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Freport-progress-and-cancel-processing-algorithm\u002F","Report Progress and Cancel a Processing Algorithm",[172,2007,2008],{},[21,2009,2011],{"href":2010},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-styles-to-database-pyqgis\u002F","Save Styles to a Database in PyQGIS",[2013,2014,2015],"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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);}",{"title":201,"searchDepth":224,"depth":224,"links":2017},[2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030],{"id":166,"depth":224,"text":167},{"id":185,"depth":224,"text":186},{"id":713,"depth":224,"text":714},{"id":1053,"depth":224,"text":1054},{"id":1302,"depth":224,"text":1303},{"id":1463,"depth":224,"text":1464},{"id":1606,"depth":224,"text":1607},{"id":1872,"depth":224,"text":1873},{"id":1890,"depth":224,"text":1891},{"id":1906,"depth":224,"text":1907},{"id":1939,"depth":224,"text":1940},{"id":1949,"depth":224,"text":1950},{"id":1980,"depth":224,"text":1981},"Make your Processing algorithms load results already styled — layer post-processors, applying QML styles and renderers from code, naming output layers, setting styles per output in models, and keeping styling out of headless runs.","md",{"slug":2034,"type":2035,"breadcrumb":2036,"datePublished":2037,"dateModified":2037},"style-processing-algorithm-outputs-pyqgis","article","Style Processing Algorithm Outputs","2026-10-02","\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fstyle-processing-algorithm-outputs-pyqgis",{"title":5,"description":2031},"qgis-plugin-development\u002Fprocessing-provider-plugins\u002Fstyle-processing-algorithm-outputs-pyqgis\u002Findex","yGor6S3_XWcSCTmq4rj0yba3xG1TNxb3zFVHx9bQ8Vk",1790966258839]