[{"data":1,"prerenderedAt":2345},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis":3},{"id":4,"title":5,"body":6,"description":2333,"extension":2334,"meta":2335,"navigation":508,"path":2341,"seo":2342,"stem":2343,"__hash__":2344},"docs\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002Findex.md","Write a Custom Processing Algorithm in PyQGIS",{"type":7,"value":8,"toc":2317},"minimark",[9,13,34,46,62,327,337,342,365,369,383,387,390,430,434,446,779,805,809,825,1262,1299,1303,1313,1488,1518,1522,1525,1674,1693,1696,1812,1828,1832,1839,1952,1956,1962,2034,2038,2048,2110,2121,2125,2143,2160,2180,2204,2216,2220,2235,2239,2249,2275,2284,2295,2299,2313],[10,11,5],"h1",{"id":12},"write-a-custom-processing-algorithm-in-pyqgis",[14,15,16],"p",{},[17,18,19,24,25,24,29,33],"em",{},[20,21,23],"a",{"href":22},"\u002F","Home"," → ",[20,26,28],{"href":27},"\u002Fqgis-plugin-development\u002F","QGIS Plugin Development",[20,30,32],{"href":31},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002F","Processing Provider Plugins"," → Write a Custom Processing Algorithm",[14,35,36,37,41,42,45],{},"Subclassing ",[38,39,40],"code",{},"QgsProcessingAlgorithm"," lets you turn an ordinary PyQGIS function into a first-class Processing tool: it gets an auto-generated dialog, batch support, model integration, and a stable id you can call from any script. This page walks through a single complete, runnable algorithm — one that buffers an input vector layer, computes a new attribute, and reports progress through ",[38,43,44],{},"feedback"," — so you can see every required method working together rather than in isolated fragments.",[14,47,48,49,52,53,56,57,61],{},"This task sits directly under ",[20,50,51],{"href":31},"Processing Provider Plugins for QGIS",". The algorithm class you build here is exactly what a provider's ",[38,54,55],{},"loadAlgorithms()"," registers, so once it runs in the ",[20,58,60],{"href":59},"\u002Fpyqgis-fundamentals-environment-setup\u002F","QGIS Python Console"," you can drop it straight into a plugin without touching the class body.",[63,64,69,73,77,84,92,110,120,126,136,140,145,149,152,156,159,163,169,173,180,186,191,196,201,209,213,216,220,224,228,232,237,240,243,248,253,255,259,263,267,271,278,282,286,288,292,300,303,308,311,315,320,323],"svg",{"viewBox":65,"role":66,"ariaLabel":67,"xmlns":68},"0 0 840 470","img","Data flow through a custom Processing algorithm: the INPUT feature-source and DISTANCE number parameters declared in initAlgorithm are resolved in processAlgorithm, then a loop buffers each feature and adds it to the OUTPUT feature-sink, while a feedback object channels cancel checks, progress, and log messages to the Toolbox dialog","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[70,71,72],"title",{},"Data flow through processAlgorithm()",[74,75,76],"desc",{},"On the left, initAlgorithm declares two parameters: an INPUT QgsProcessingParameterFeatureSource and a DISTANCE QgsProcessingParameterNumber. Arrows carry them into processAlgorithm, where parameterAsSource and parameterAsSink resolve them, then a for-loop over getFeatures buffers each geometry, builds a QgsFeature with a buff_area field, and adds it to the sink. The sink flows to the OUTPUT QgsProcessingParameterFeatureSink, which is returned as dest_id. A separate feedback object routes isCanceled, setProgress, and pushInfo calls to the Toolbox dialog's progress bar and log panel.",[78,79],"rect",{"x":80,"y":80,"width":81,"height":82,"fill":83},"0","840","470","#f6f3ea",[85,86,72],"text",{"x":87,"y":88,"style":89,"fill":90,"textAnchor":91},"420","30","text-anchor:middle;font-size:15px;font-weight:bold;font-family:sans-serif","#17211d","middle",[93,94,95],"defs",{},[96,97,105],"marker",{"id":98,"viewBox":99,"refX":100,"refY":101,"markerWidth":102,"markerHeight":102,"orient":103,"markerUnits":104},"wca-arrow","0 0 10 10","9","5","7","auto","strokeWidth",[106,107],"path",{"d":108,"fill":109},"M0,0 L10,5 L0,10 z","#2f3b35",[78,111],{"x":112,"y":113,"width":114,"height":115,"rx":116,"fill":117,"stroke":118,"style":119},"20","52","232","188","10","none","#0f766e","stroke-width:1.5;stroke-dasharray:6 5",[85,121,125],{"x":122,"y":123,"style":124,"fill":118,"textAnchor":91},"136","72","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","initAlgorithm() declares",[78,127],{"x":128,"y":129,"width":130,"height":131,"rx":132,"fill":133,"stroke":134,"style":135},"36","84","200","68","8","#fffdf7","#2563eb","stroke-width:2.5",[85,137,139],{"x":122,"y":138,"style":124,"fill":90,"textAnchor":91},"106","INPUT",[85,141,144],{"x":122,"y":142,"style":143,"fill":109,"textAnchor":91},"125","text-anchor:middle;font-size:11px;font-family:monospace","QgsProcessingParameter",[85,146,148],{"x":122,"y":147,"style":143,"fill":109,"textAnchor":91},"141","FeatureSource",[78,150],{"x":128,"y":151,"width":130,"height":131,"rx":132,"fill":133,"stroke":118,"style":135},"164",[85,153,155],{"x":122,"y":154,"style":124,"fill":90,"textAnchor":91},"186","DISTANCE",[85,157,144],{"x":122,"y":158,"style":143,"fill":109,"textAnchor":91},"205",[85,160,162],{"x":122,"y":161,"style":143,"fill":109,"textAnchor":91},"221","Number · Double",[78,164],{"x":165,"y":113,"width":166,"height":167,"rx":116,"fill":117,"stroke":168,"style":119},"300","284","270","#b45309",[85,170,172],{"x":171,"y":123,"style":124,"fill":168,"textAnchor":91},"442","processAlgorithm()",[78,174],{"x":175,"y":176,"width":177,"height":178,"rx":132,"fill":179},"316","82","252","46","#26322d",[85,181,185],{"x":171,"y":182,"style":183,"fill":184,"textAnchor":91},"101","text-anchor:middle;font-size:11.5px;font-family:monospace","#d9f99d","parameterAsSource()",[85,187,190],{"x":171,"y":188,"style":189,"fill":184,"textAnchor":91},"118","text-anchor:middle;font-size:10.5px;font-family:monospace","parameterAsSink(fields, wkb, crs)",[78,192],{"x":175,"y":193,"width":177,"height":194,"rx":132,"fill":117,"stroke":168,"style":195},"140","172","stroke-width:1.5;stroke-dasharray:5 4",[85,197,200],{"x":171,"y":198,"style":199,"fill":168,"textAnchor":91},"158","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","for feature in getFeatures()",[78,202],{"x":203,"y":204,"width":205,"height":206,"rx":207,"fill":133,"stroke":118,"style":208},"330","166","224","40","6","stroke-width:2",[85,210,212],{"x":171,"y":211,"style":143,"fill":90,"textAnchor":91},"190","geometry().buffer(distance, 5)",[78,214],{"x":203,"y":215,"width":205,"height":206,"rx":207,"fill":133,"stroke":134,"style":208},"214",[85,217,219],{"x":171,"y":114,"style":218,"fill":90,"textAnchor":91},"text-anchor:middle;font-size:11.5px;font-family:sans-serif","QgsFeature + buff_area",[85,221,223],{"x":171,"y":222,"style":189,"fill":109,"textAnchor":91},"247","= geom.area()",[78,225],{"x":203,"y":226,"width":205,"height":206,"rx":207,"fill":133,"stroke":227,"style":208},"262","#15803d",[85,229,231],{"x":171,"y":230,"style":143,"fill":90,"textAnchor":91},"286","sink.addFeature(FastInsert)",[233,234],"line",{"x1":171,"y1":235,"x2":171,"y2":204,"stroke":109,"style":236},"128","stroke-width:2;marker-end:url(#wca-arrow)",[233,238],{"x1":171,"y1":239,"x2":171,"y2":215,"stroke":109,"style":236},"206",[233,241],{"x1":171,"y1":242,"x2":171,"y2":226,"stroke":109,"style":236},"254",[78,244],{"x":245,"y":246,"width":247,"height":129,"rx":132,"fill":133,"stroke":227,"style":135},"640","96","180",[85,249,252],{"x":250,"y":251,"style":124,"fill":90,"textAnchor":91},"730","120","OUTPUT",[85,254,144],{"x":250,"y":193,"style":143,"fill":109,"textAnchor":91},[85,256,258],{"x":250,"y":257,"style":143,"fill":109,"textAnchor":91},"156","FeatureSink",[85,260,262],{"x":250,"y":261,"style":189,"fill":109,"textAnchor":91},"210","return {OUTPUT: dest_id}",[78,264],{"x":165,"y":265,"width":166,"height":266,"rx":116,"fill":117,"stroke":134,"style":119},"350","104",[85,268,270],{"x":171,"y":269,"style":124,"fill":134,"textAnchor":91},"370","feedback (QgsProcessingFeedback)",[85,272,277],{"x":273,"y":274,"style":275,"fill":90,"textAnchor":276},"320","393","text-anchor:start;font-size:11.5px;font-family:sans-serif","start","isCanceled()  →  stop the run",[85,279,281],{"x":273,"y":280,"style":275,"fill":90,"textAnchor":276},"415","setProgress(int)  →  progress bar",[85,283,285],{"x":273,"y":284,"style":275,"fill":90,"textAnchor":276},"437","pushInfo(text)  →  log panel",[78,287],{"x":245,"y":265,"width":247,"height":266,"rx":132,"fill":133,"stroke":168,"style":135},[85,289,291],{"x":250,"y":290,"style":124,"fill":90,"textAnchor":91},"374","Toolbox dialog",[78,293],{"x":294,"y":295,"width":296,"height":297,"rx":102,"fill":298,"stroke":168,"style":299},"656","388","148","14","#efe9da","stroke-width:1",[78,301],{"x":294,"y":295,"width":302,"height":297,"rx":102,"fill":168},"92",[85,304,307],{"x":250,"y":305,"style":306,"fill":109,"textAnchor":91},"430","text-anchor:middle;font-size:11px;font-family:sans-serif","log \u002F history panel",[233,309],{"x1":177,"y1":188,"x2":165,"y2":266,"stroke":109,"style":310},"stroke-width:2.5;marker-end:url(#wca-arrow)",[233,312],{"x1":177,"y1":313,"x2":165,"y2":314,"stroke":109,"style":310},"198","182",[233,316],{"x1":317,"y1":166,"x2":318,"y2":319,"stroke":109,"style":310},"568","648","176",[233,321],{"x1":171,"y1":322,"x2":171,"y2":265,"stroke":109,"style":310},"322",[233,324],{"x1":325,"y1":326,"x2":245,"y2":326,"stroke":109,"style":310},"584","400",[14,328,329,330,333,334,336],{},"The two lifecycle methods — ",[38,331,332],{},"initAlgorithm()"," and ",[38,335,172],{}," — are where all the work happens; the rest are short identity methods. The diagram above traces how a parameter declared in the first method becomes a resolved object consumed by the second.",[338,339,341],"h2",{"id":340},"prerequisites","Prerequisites",[343,344,345,353,356,359],"ul",{},[346,347,348,352],"li",{},[349,350,351],"strong",{},"QGIS 3.34 LTR"," (Python 3.12) with the Processing plugin active.",[346,354,355],{},"A vector layer to test against (any polygon or point layer in the project).",[346,357,358],{},"Basic Python class knowledge and the QGIS Python Console for quick iteration.",[346,360,361,362,364],{},"Optional: the provider scaffolding from ",[20,363,51],{"href":31}," if you intend to ship the algorithm in a plugin.",[338,366,368],{"id":367},"how-a-processing-algorithm-differs-from-a-plain-function","How a Processing Algorithm Differs from a Plain Function",[14,370,371,372,375,376,378,379,382],{},"It is tempting to think of an algorithm as just a function wrapped in a class, but the framework imposes a contract that buys you a great deal. By declaring parameters instead of reading arguments, you let Processing build the dialog, validate types, support batch mode, and expose the tool to the Model Designer. By writing to a ",[17,373,374],{},"sink"," instead of a hardcoded file, you let the user pick GeoPackage, Shapefile, or an in-memory layer at run time. By reporting through ",[17,377,44],{}," instead of ",[38,380,381],{},"print()",", your progress appears in the dialog and your log lines land in the Processing history. None of this requires extra UI code — it falls out of implementing the contract correctly, which is why a Processing algorithm is almost always preferable to a custom dialog for data transformations.",[338,384,386],{"id":385},"the-methods-every-algorithm-must-implement","The Methods Every Algorithm Must Implement",[14,388,389],{},"A Processing algorithm is defined by a small, fixed set of overrides. Identity methods describe the tool; the two lifecycle methods do the work.",[343,391,392,398,404,414,420,425],{},[346,393,394,397],{},[38,395,396],{},"name()"," — the machine id (lowercase, no spaces); combined with the provider id to address the algorithm.",[346,399,400,403],{},[38,401,402],{},"displayName()"," — the human-readable label in the Toolbox and dialog.",[346,405,406,409,410,413],{},[38,407,408],{},"group()"," \u002F ",[38,411,412],{},"groupId()"," — optional sub-grouping under the provider.",[346,415,416,419],{},[38,417,418],{},"createInstance()"," — returns a fresh copy of the algorithm; required by the framework.",[346,421,422,424],{},[38,423,332],{}," — declares input and output parameters.",[346,426,427,429],{},[38,428,172],{}," — reads the source, transforms features, writes the sink, reports progress.",[338,431,433],{"id":432},"declaring-parameters-in-initalgorithm","Declaring Parameters in initAlgorithm",[14,435,436,438,439,442,443,445],{},[38,437,332],{}," runs once when QGIS builds the dialog. Each ",[38,440,441],{},"addParameter()"," call adds one widget. Here we take a vector feature source as input, a numeric buffer distance, and a feature sink as output. Defining string constants for parameter keys keeps ",[38,444,172],{}," readable and typo-proof.",[447,448,453],"pre",{"className":449,"code":450,"language":451,"meta":452,"style":452},"language-python shiki shiki-themes github-dark","from qgis.core import (\n    QgsProcessingAlgorithm,\n    QgsProcessingParameterFeatureSource,\n    QgsProcessingParameterFeatureSink,\n    QgsProcessingParameterNumber,\n)\n\n\nclass BufferAndMeasureAlgorithm(QgsProcessingAlgorithm):\n    INPUT = \"INPUT\"\n    DISTANCE = \"DISTANCE\"\n    OUTPUT = \"OUTPUT\"\n\n    def initAlgorithm(self, config=None):\n        self.addParameter(\n            QgsProcessingParameterFeatureSource(\n                self.INPUT,\n                self.tr(\"Input layer\"),\n            )\n        )\n        self.addParameter(\n            QgsProcessingParameterNumber(\n                self.DISTANCE,\n                self.tr(\"Buffer distance (layer units)\"),\n                type=QgsProcessingParameterNumber.Double,\n                defaultValue=100.0,\n                minValue=0.0,\n            )\n        )\n        self.addParameter(\n            QgsProcessingParameterFeatureSink(\n                self.OUTPUT,\n                self.tr(\"Buffered output\"),\n            )\n        )\n","python","",[38,454,455,473,479,485,491,497,503,510,515,533,547,558,569,574,594,603,609,623,637,643,649,656,662,673,685,697,710,723,728,733,740,746,757,769,774],{"__ignoreMap":452},[456,457,459,463,467,470],"span",{"class":233,"line":458},1,[456,460,462],{"class":461},"snl16","from",[456,464,466],{"class":465},"s95oV"," qgis.core ",[456,468,469],{"class":461},"import",[456,471,472],{"class":465}," (\n",[456,474,476],{"class":233,"line":475},2,[456,477,478],{"class":465},"    QgsProcessingAlgorithm,\n",[456,480,482],{"class":233,"line":481},3,[456,483,484],{"class":465},"    QgsProcessingParameterFeatureSource,\n",[456,486,488],{"class":233,"line":487},4,[456,489,490],{"class":465},"    QgsProcessingParameterFeatureSink,\n",[456,492,494],{"class":233,"line":493},5,[456,495,496],{"class":465},"    QgsProcessingParameterNumber,\n",[456,498,500],{"class":233,"line":499},6,[456,501,502],{"class":465},")\n",[456,504,506],{"class":233,"line":505},7,[456,507,509],{"emptyLinePlaceholder":508},true,"\n",[456,511,513],{"class":233,"line":512},8,[456,514,509],{"emptyLinePlaceholder":508},[456,516,518,521,525,528,530],{"class":233,"line":517},9,[456,519,520],{"class":461},"class",[456,522,524],{"class":523},"svObZ"," BufferAndMeasureAlgorithm",[456,526,527],{"class":465},"(",[456,529,40],{"class":523},[456,531,532],{"class":465},"):\n",[456,534,536,540,543],{"class":233,"line":535},10,[456,537,539],{"class":538},"sDLfK","    INPUT",[456,541,542],{"class":461}," =",[456,544,546],{"class":545},"sU2Wk"," \"INPUT\"\n",[456,548,550,553,555],{"class":233,"line":549},11,[456,551,552],{"class":538},"    DISTANCE",[456,554,542],{"class":461},[456,556,557],{"class":545}," \"DISTANCE\"\n",[456,559,561,564,566],{"class":233,"line":560},12,[456,562,563],{"class":538},"    OUTPUT",[456,565,542],{"class":461},[456,567,568],{"class":545}," \"OUTPUT\"\n",[456,570,572],{"class":233,"line":571},13,[456,573,509],{"emptyLinePlaceholder":508},[456,575,577,580,583,586,589,592],{"class":233,"line":576},14,[456,578,579],{"class":461},"    def",[456,581,582],{"class":523}," initAlgorithm",[456,584,585],{"class":465},"(self, config",[456,587,588],{"class":461},"=",[456,590,591],{"class":538},"None",[456,593,532],{"class":465},[456,595,597,600],{"class":233,"line":596},15,[456,598,599],{"class":538},"        self",[456,601,602],{"class":465},".addParameter(\n",[456,604,606],{"class":233,"line":605},16,[456,607,608],{"class":465},"            QgsProcessingParameterFeatureSource(\n",[456,610,612,615,618,620],{"class":233,"line":611},17,[456,613,614],{"class":538},"                self",[456,616,617],{"class":465},".",[456,619,139],{"class":538},[456,621,622],{"class":465},",\n",[456,624,626,628,631,634],{"class":233,"line":625},18,[456,627,614],{"class":538},[456,629,630],{"class":465},".tr(",[456,632,633],{"class":545},"\"Input layer\"",[456,635,636],{"class":465},"),\n",[456,638,640],{"class":233,"line":639},19,[456,641,642],{"class":465},"            )\n",[456,644,646],{"class":233,"line":645},20,[456,647,648],{"class":465},"        )\n",[456,650,652,654],{"class":233,"line":651},21,[456,653,599],{"class":538},[456,655,602],{"class":465},[456,657,659],{"class":233,"line":658},22,[456,660,661],{"class":465},"            QgsProcessingParameterNumber(\n",[456,663,665,667,669,671],{"class":233,"line":664},23,[456,666,614],{"class":538},[456,668,617],{"class":465},[456,670,155],{"class":538},[456,672,622],{"class":465},[456,674,676,678,680,683],{"class":233,"line":675},24,[456,677,614],{"class":538},[456,679,630],{"class":465},[456,681,682],{"class":545},"\"Buffer distance (layer units)\"",[456,684,636],{"class":465},[456,686,688,692,694],{"class":233,"line":687},25,[456,689,691],{"class":690},"s9osk","                type",[456,693,588],{"class":461},[456,695,696],{"class":465},"QgsProcessingParameterNumber.Double,\n",[456,698,700,703,705,708],{"class":233,"line":699},26,[456,701,702],{"class":690},"                defaultValue",[456,704,588],{"class":461},[456,706,707],{"class":538},"100.0",[456,709,622],{"class":465},[456,711,713,716,718,721],{"class":233,"line":712},27,[456,714,715],{"class":690},"                minValue",[456,717,588],{"class":461},[456,719,720],{"class":538},"0.0",[456,722,622],{"class":465},[456,724,726],{"class":233,"line":725},28,[456,727,642],{"class":465},[456,729,731],{"class":233,"line":730},29,[456,732,648],{"class":465},[456,734,736,738],{"class":233,"line":735},30,[456,737,599],{"class":538},[456,739,602],{"class":465},[456,741,743],{"class":233,"line":742},31,[456,744,745],{"class":465},"            QgsProcessingParameterFeatureSink(\n",[456,747,749,751,753,755],{"class":233,"line":748},32,[456,750,614],{"class":538},[456,752,617],{"class":465},[456,754,252],{"class":538},[456,756,622],{"class":465},[456,758,760,762,764,767],{"class":233,"line":759},33,[456,761,614],{"class":538},[456,763,630],{"class":465},[456,765,766],{"class":545},"\"Buffered output\"",[456,768,636],{"class":465},[456,770,772],{"class":233,"line":771},34,[456,773,642],{"class":465},[456,775,777],{"class":233,"line":776},35,[456,778,648],{"class":465},[14,780,781,784,785,788,789,792,793,796,797,800,801,804],{},[349,782,783],{},"Breakdown:"," ",[38,786,787],{},"QgsProcessingParameterFeatureSource"," produces a layer picker that also accepts a \"selected features only\" toggle and on-the-fly reprojection. ",[38,790,791],{},"QgsProcessingParameterNumber"," with ",[38,794,795],{},"type=Double"," renders a spin box; ",[38,798,799],{},"minValue"," enforces a non-negative distance in the dialog. ",[38,802,803],{},"QgsProcessingParameterFeatureSink"," is the output target — Processing resolves it to a memory layer, a GeoPackage, or a Shapefile depending on what the user chooses, so you never hardcode a path.",[338,806,808],{"id":807},"implementing-processalgorithm","Implementing processAlgorithm",[14,810,811,813,814,817,818,821,822,824],{},[38,812,172],{}," receives the resolved ",[38,815,816],{},"parameters",", a ",[38,819,820],{},"context",", and a ",[38,823,44],{}," object. The standard shape is: resolve the source, create the sink, iterate features, push each transformed feature into the sink, and update progress. Buffering each geometry and recording its area demonstrates a real transformation rather than a passthrough.",[447,826,828],{"className":449,"code":827,"language":451,"meta":452,"style":452},"    def processAlgorithm(self, parameters, context, feedback):\n        source = self.parameterAsSource(parameters, self.INPUT, context)\n        if source is None:\n            raise QgsProcessingException(\n                self.invalidSourceError(parameters, self.INPUT))\n\n        distance = self.parameterAsDouble(parameters, self.DISTANCE, context)\n\n        # Build the output field set: input fields plus a new area field.\n        out_fields = source.fields()\n        out_fields.append(QgsField(\"buff_area\", QVariant.Double))\n\n        sink, dest_id = self.parameterAsSink(\n            parameters,\n            self.OUTPUT,\n            context,\n            out_fields,\n            QgsWkbTypes.Polygon,\n            source.sourceCrs(),\n        )\n        if sink is None:\n            raise QgsProcessingException(\n                self.invalidSinkError(parameters, self.OUTPUT))\n\n        total = source.featureCount()\n        step = 100.0 \u002F total if total else 0\n\n        for current, feature in enumerate(source.getFeatures()):\n            if feedback.isCanceled():\n                break\n\n            geom = feature.geometry().buffer(distance, 5)\n            new_feature = QgsFeature(out_fields)\n            new_feature.setGeometry(geom)\n            new_feature.setAttributes(\n                feature.attributes() + [geom.area()])\n            sink.addFeature(new_feature, QgsFeatureSink.FastInsert)\n\n            feedback.setProgress(int(current * step))\n\n        feedback.pushInfo(f\"Buffered {total} features by {distance} units.\")\n        return {self.OUTPUT: dest_id}\n",[38,829,830,840,863,880,888,904,908,928,932,938,948,959,963,975,980,991,996,1001,1006,1011,1015,1028,1034,1049,1053,1063,1090,1094,1111,1119,1124,1128,1142,1152,1157,1162,1174,1180,1185,1203,1208,1244],{"__ignoreMap":452},[456,831,832,834,837],{"class":233,"line":458},[456,833,579],{"class":461},[456,835,836],{"class":523}," processAlgorithm",[456,838,839],{"class":465},"(self, parameters, context, feedback):\n",[456,841,842,845,847,850,853,856,858,860],{"class":233,"line":475},[456,843,844],{"class":465},"        source ",[456,846,588],{"class":461},[456,848,849],{"class":538}," self",[456,851,852],{"class":465},".parameterAsSource(parameters, ",[456,854,855],{"class":538},"self",[456,857,617],{"class":465},[456,859,139],{"class":538},[456,861,862],{"class":465},", context)\n",[456,864,865,868,871,874,877],{"class":233,"line":481},[456,866,867],{"class":461},"        if",[456,869,870],{"class":465}," source ",[456,872,873],{"class":461},"is",[456,875,876],{"class":538}," None",[456,878,879],{"class":465},":\n",[456,881,882,885],{"class":233,"line":487},[456,883,884],{"class":461},"            raise",[456,886,887],{"class":465}," QgsProcessingException(\n",[456,889,890,892,895,897,899,901],{"class":233,"line":493},[456,891,614],{"class":538},[456,893,894],{"class":465},".invalidSourceError(parameters, ",[456,896,855],{"class":538},[456,898,617],{"class":465},[456,900,139],{"class":538},[456,902,903],{"class":465},"))\n",[456,905,906],{"class":233,"line":499},[456,907,509],{"emptyLinePlaceholder":508},[456,909,910,913,915,917,920,922,924,926],{"class":233,"line":505},[456,911,912],{"class":465},"        distance ",[456,914,588],{"class":461},[456,916,849],{"class":538},[456,918,919],{"class":465},".parameterAsDouble(parameters, ",[456,921,855],{"class":538},[456,923,617],{"class":465},[456,925,155],{"class":538},[456,927,862],{"class":465},[456,929,930],{"class":233,"line":512},[456,931,509],{"emptyLinePlaceholder":508},[456,933,934],{"class":233,"line":517},[456,935,937],{"class":936},"sjoCn","        # Build the output field set: input fields plus a new area field.\n",[456,939,940,943,945],{"class":233,"line":535},[456,941,942],{"class":465},"        out_fields ",[456,944,588],{"class":461},[456,946,947],{"class":465}," source.fields()\n",[456,949,950,953,956],{"class":233,"line":549},[456,951,952],{"class":465},"        out_fields.append(QgsField(",[456,954,955],{"class":545},"\"buff_area\"",[456,957,958],{"class":465},", QVariant.Double))\n",[456,960,961],{"class":233,"line":560},[456,962,509],{"emptyLinePlaceholder":508},[456,964,965,968,970,972],{"class":233,"line":571},[456,966,967],{"class":465},"        sink, dest_id ",[456,969,588],{"class":461},[456,971,849],{"class":538},[456,973,974],{"class":465},".parameterAsSink(\n",[456,976,977],{"class":233,"line":576},[456,978,979],{"class":465},"            parameters,\n",[456,981,982,985,987,989],{"class":233,"line":596},[456,983,984],{"class":538},"            self",[456,986,617],{"class":465},[456,988,252],{"class":538},[456,990,622],{"class":465},[456,992,993],{"class":233,"line":605},[456,994,995],{"class":465},"            context,\n",[456,997,998],{"class":233,"line":611},[456,999,1000],{"class":465},"            out_fields,\n",[456,1002,1003],{"class":233,"line":625},[456,1004,1005],{"class":465},"            QgsWkbTypes.Polygon,\n",[456,1007,1008],{"class":233,"line":639},[456,1009,1010],{"class":465},"            source.sourceCrs(),\n",[456,1012,1013],{"class":233,"line":645},[456,1014,648],{"class":465},[456,1016,1017,1019,1022,1024,1026],{"class":233,"line":651},[456,1018,867],{"class":461},[456,1020,1021],{"class":465}," sink ",[456,1023,873],{"class":461},[456,1025,876],{"class":538},[456,1027,879],{"class":465},[456,1029,1030,1032],{"class":233,"line":658},[456,1031,884],{"class":461},[456,1033,887],{"class":465},[456,1035,1036,1038,1041,1043,1045,1047],{"class":233,"line":664},[456,1037,614],{"class":538},[456,1039,1040],{"class":465},".invalidSinkError(parameters, ",[456,1042,855],{"class":538},[456,1044,617],{"class":465},[456,1046,252],{"class":538},[456,1048,903],{"class":465},[456,1050,1051],{"class":233,"line":675},[456,1052,509],{"emptyLinePlaceholder":508},[456,1054,1055,1058,1060],{"class":233,"line":687},[456,1056,1057],{"class":465},"        total ",[456,1059,588],{"class":461},[456,1061,1062],{"class":465}," source.featureCount()\n",[456,1064,1065,1068,1070,1073,1076,1079,1082,1084,1087],{"class":233,"line":699},[456,1066,1067],{"class":465},"        step ",[456,1069,588],{"class":461},[456,1071,1072],{"class":538}," 100.0",[456,1074,1075],{"class":461}," \u002F",[456,1077,1078],{"class":465}," total ",[456,1080,1081],{"class":461},"if",[456,1083,1078],{"class":465},[456,1085,1086],{"class":461},"else",[456,1088,1089],{"class":538}," 0\n",[456,1091,1092],{"class":233,"line":712},[456,1093,509],{"emptyLinePlaceholder":508},[456,1095,1096,1099,1102,1105,1108],{"class":233,"line":725},[456,1097,1098],{"class":461},"        for",[456,1100,1101],{"class":465}," current, feature ",[456,1103,1104],{"class":461},"in",[456,1106,1107],{"class":538}," enumerate",[456,1109,1110],{"class":465},"(source.getFeatures()):\n",[456,1112,1113,1116],{"class":233,"line":730},[456,1114,1115],{"class":461},"            if",[456,1117,1118],{"class":465}," feedback.isCanceled():\n",[456,1120,1121],{"class":233,"line":735},[456,1122,1123],{"class":461},"                break\n",[456,1125,1126],{"class":233,"line":742},[456,1127,509],{"emptyLinePlaceholder":508},[456,1129,1130,1133,1135,1138,1140],{"class":233,"line":748},[456,1131,1132],{"class":465},"            geom ",[456,1134,588],{"class":461},[456,1136,1137],{"class":465}," feature.geometry().buffer(distance, ",[456,1139,101],{"class":538},[456,1141,502],{"class":465},[456,1143,1144,1147,1149],{"class":233,"line":759},[456,1145,1146],{"class":465},"            new_feature ",[456,1148,588],{"class":461},[456,1150,1151],{"class":465}," QgsFeature(out_fields)\n",[456,1153,1154],{"class":233,"line":771},[456,1155,1156],{"class":465},"            new_feature.setGeometry(geom)\n",[456,1158,1159],{"class":233,"line":776},[456,1160,1161],{"class":465},"            new_feature.setAttributes(\n",[456,1163,1165,1168,1171],{"class":233,"line":1164},36,[456,1166,1167],{"class":465},"                feature.attributes() ",[456,1169,1170],{"class":461},"+",[456,1172,1173],{"class":465}," [geom.area()])\n",[456,1175,1177],{"class":233,"line":1176},37,[456,1178,1179],{"class":465},"            sink.addFeature(new_feature, QgsFeatureSink.FastInsert)\n",[456,1181,1183],{"class":233,"line":1182},38,[456,1184,509],{"emptyLinePlaceholder":508},[456,1186,1188,1191,1194,1197,1200],{"class":233,"line":1187},39,[456,1189,1190],{"class":465},"            feedback.setProgress(",[456,1192,1193],{"class":538},"int",[456,1195,1196],{"class":465},"(current ",[456,1198,1199],{"class":461},"*",[456,1201,1202],{"class":465}," step))\n",[456,1204,1206],{"class":233,"line":1205},40,[456,1207,509],{"emptyLinePlaceholder":508},[456,1209,1211,1214,1217,1220,1223,1226,1229,1232,1234,1237,1239,1242],{"class":233,"line":1210},41,[456,1212,1213],{"class":465},"        feedback.pushInfo(",[456,1215,1216],{"class":461},"f",[456,1218,1219],{"class":545},"\"Buffered ",[456,1221,1222],{"class":538},"{",[456,1224,1225],{"class":465},"total",[456,1227,1228],{"class":538},"}",[456,1230,1231],{"class":545}," features by ",[456,1233,1222],{"class":538},[456,1235,1236],{"class":465},"distance",[456,1238,1228],{"class":538},[456,1240,1241],{"class":545}," units.\"",[456,1243,502],{"class":465},[456,1245,1247,1250,1253,1255,1257,1259],{"class":233,"line":1246},42,[456,1248,1249],{"class":461},"        return",[456,1251,1252],{"class":465}," {",[456,1254,855],{"class":538},[456,1256,617],{"class":465},[456,1258,252],{"class":538},[456,1260,1261],{"class":465},": dest_id}\n",[14,1263,1264,784,1266,333,1269,1272,1273,22,1276,1279,1280,1283,1284,1287,1288,1291,1292,1295,1296,617],{},[349,1265,783],{},[38,1267,1268],{},"parameterAsSource",[38,1270,1271],{},"parameterAsSink"," convert the raw parameter values into usable objects, raising a clear error through the ",[38,1274,1275],{},"invalidSourceError",[38,1277,1278],{},"invalidSinkError"," helpers when resolution fails. The output sink needs an explicit field set, geometry type, and CRS up front — here we copy the source fields and append ",[38,1281,1282],{},"buff_area",". ",[38,1285,1286],{},"feedback.isCanceled()"," lets the user stop a long run from the dialog; ",[38,1289,1290],{},"feedback.setProgress()"," drives the progress bar; ",[38,1293,1294],{},"feedback.pushInfo()"," writes to the log panel. The method must return a dictionary keyed by output name, mapping to the sink's ",[38,1297,1298],{},"dest_id",[338,1300,1302],{"id":1301},"identity-and-createinstance","Identity and createInstance",[14,1304,1305,1306,1308,1309,1312],{},"The remaining methods describe the algorithm and let the framework clone it. ",[38,1307,418],{}," must return a new object every time so concurrent batch runs do not share state. The ",[38,1310,1311],{},"tr()"," helper marks strings for translation.",[447,1314,1316],{"className":449,"code":1315,"language":451,"meta":452,"style":452},"    def name(self):\n        return \"bufferandmeasure\"\n\n    def displayName(self):\n        return self.tr(\"Buffer and Measure\")\n\n    def group(self):\n        return self.tr(\"Vector geometry\")\n\n    def groupId(self):\n        return \"vectorgeometry\"\n\n    def shortHelpString(self):\n        return self.tr(\n            \"Buffers each input feature by a fixed distance and records \"\n            \"the resulting polygon area in a new 'buff_area' field.\")\n\n    def tr(self, string):\n        return QCoreApplication.translate(\"Processing\", string)\n\n    def createInstance(self):\n        return BufferAndMeasureAlgorithm()\n",[38,1317,1318,1328,1335,1339,1348,1361,1365,1374,1387,1391,1400,1407,1411,1420,1429,1434,1441,1445,1455,1468,1472,1481],{"__ignoreMap":452},[456,1319,1320,1322,1325],{"class":233,"line":458},[456,1321,579],{"class":461},[456,1323,1324],{"class":523}," name",[456,1326,1327],{"class":465},"(self):\n",[456,1329,1330,1332],{"class":233,"line":475},[456,1331,1249],{"class":461},[456,1333,1334],{"class":545}," \"bufferandmeasure\"\n",[456,1336,1337],{"class":233,"line":481},[456,1338,509],{"emptyLinePlaceholder":508},[456,1340,1341,1343,1346],{"class":233,"line":487},[456,1342,579],{"class":461},[456,1344,1345],{"class":523}," displayName",[456,1347,1327],{"class":465},[456,1349,1350,1352,1354,1356,1359],{"class":233,"line":493},[456,1351,1249],{"class":461},[456,1353,849],{"class":538},[456,1355,630],{"class":465},[456,1357,1358],{"class":545},"\"Buffer and Measure\"",[456,1360,502],{"class":465},[456,1362,1363],{"class":233,"line":499},[456,1364,509],{"emptyLinePlaceholder":508},[456,1366,1367,1369,1372],{"class":233,"line":505},[456,1368,579],{"class":461},[456,1370,1371],{"class":523}," group",[456,1373,1327],{"class":465},[456,1375,1376,1378,1380,1382,1385],{"class":233,"line":512},[456,1377,1249],{"class":461},[456,1379,849],{"class":538},[456,1381,630],{"class":465},[456,1383,1384],{"class":545},"\"Vector geometry\"",[456,1386,502],{"class":465},[456,1388,1389],{"class":233,"line":517},[456,1390,509],{"emptyLinePlaceholder":508},[456,1392,1393,1395,1398],{"class":233,"line":535},[456,1394,579],{"class":461},[456,1396,1397],{"class":523}," groupId",[456,1399,1327],{"class":465},[456,1401,1402,1404],{"class":233,"line":549},[456,1403,1249],{"class":461},[456,1405,1406],{"class":545}," \"vectorgeometry\"\n",[456,1408,1409],{"class":233,"line":560},[456,1410,509],{"emptyLinePlaceholder":508},[456,1412,1413,1415,1418],{"class":233,"line":571},[456,1414,579],{"class":461},[456,1416,1417],{"class":523}," shortHelpString",[456,1419,1327],{"class":465},[456,1421,1422,1424,1426],{"class":233,"line":576},[456,1423,1249],{"class":461},[456,1425,849],{"class":538},[456,1427,1428],{"class":465},".tr(\n",[456,1430,1431],{"class":233,"line":596},[456,1432,1433],{"class":545},"            \"Buffers each input feature by a fixed distance and records \"\n",[456,1435,1436,1439],{"class":233,"line":605},[456,1437,1438],{"class":545},"            \"the resulting polygon area in a new 'buff_area' field.\"",[456,1440,502],{"class":465},[456,1442,1443],{"class":233,"line":611},[456,1444,509],{"emptyLinePlaceholder":508},[456,1446,1447,1449,1452],{"class":233,"line":625},[456,1448,579],{"class":461},[456,1450,1451],{"class":523}," tr",[456,1453,1454],{"class":465},"(self, string):\n",[456,1456,1457,1459,1462,1465],{"class":233,"line":639},[456,1458,1249],{"class":461},[456,1460,1461],{"class":465}," QCoreApplication.translate(",[456,1463,1464],{"class":545},"\"Processing\"",[456,1466,1467],{"class":465},", string)\n",[456,1469,1470],{"class":233,"line":645},[456,1471,509],{"emptyLinePlaceholder":508},[456,1473,1474,1476,1479],{"class":233,"line":651},[456,1475,579],{"class":461},[456,1477,1478],{"class":523}," createInstance",[456,1480,1327],{"class":465},[456,1482,1483,1485],{"class":233,"line":658},[456,1484,1249],{"class":461},[456,1486,1487],{"class":465}," BufferAndMeasureAlgorithm()\n",[14,1489,1490,784,1492,1494,1495,1283,1498,1500,1501,22,1503,1505,1506,1283,1508,1511,1512,1514,1515,1517],{},[349,1491,783],{},[38,1493,396],{}," is the permanent id used as ",[38,1496,1497],{},"provider_id:bufferandmeasure",[38,1499,402],{}," is cosmetic and translatable. ",[38,1502,408],{},[38,1504,412],{}," slot the tool under a \"Vector geometry\" node, matching the grouping conventions described in ",[20,1507,51],{"href":31},[38,1509,1510],{},"shortHelpString()"," populates the help panel beside the dialog. ",[38,1513,418],{}," returning a brand-new object is mandatory — returning ",[38,1516,855],{}," causes subtle bugs under batch and model execution.",[338,1519,1521],{"id":1520},"complete-imports-and-a-test-run","Complete Imports and a Test Run",[14,1523,1524],{},"Put the imports at the top of the file, then test the class directly in the Python Console without a full plugin. Registering it temporarily on a throwaway provider is the fastest way to confirm it loads.",[447,1526,1528],{"className":449,"code":1527,"language":451,"meta":452,"style":452},"from qgis.PyQt.QtCore import QCoreApplication, QVariant\nfrom qgis.core import (\n    QgsField,\n    QgsFeature,\n    QgsFeatureSink,\n    QgsWkbTypes,\n    QgsProcessingException,\n)\nimport processing\n\n# Run the algorithm headlessly once it is registered on a provider:\nresult = processing.run(\"mytools:bufferandmeasure\", {\n    \"INPUT\": \"\u002Fdata\u002Fsites.gpkg|layername=sites\",\n    \"DISTANCE\": 250.0,\n    \"OUTPUT\": \"memory:buffered_sites\",\n})\nprint(result[\"OUTPUT\"].featureCount(), \"features written\")\n",[38,1529,1530,1542,1552,1557,1562,1567,1572,1577,1581,1588,1592,1597,1613,1626,1638,1650,1655],{"__ignoreMap":452},[456,1531,1532,1534,1537,1539],{"class":233,"line":458},[456,1533,462],{"class":461},[456,1535,1536],{"class":465}," qgis.PyQt.QtCore ",[456,1538,469],{"class":461},[456,1540,1541],{"class":465}," QCoreApplication, QVariant\n",[456,1543,1544,1546,1548,1550],{"class":233,"line":475},[456,1545,462],{"class":461},[456,1547,466],{"class":465},[456,1549,469],{"class":461},[456,1551,472],{"class":465},[456,1553,1554],{"class":233,"line":481},[456,1555,1556],{"class":465},"    QgsField,\n",[456,1558,1559],{"class":233,"line":487},[456,1560,1561],{"class":465},"    QgsFeature,\n",[456,1563,1564],{"class":233,"line":493},[456,1565,1566],{"class":465},"    QgsFeatureSink,\n",[456,1568,1569],{"class":233,"line":499},[456,1570,1571],{"class":465},"    QgsWkbTypes,\n",[456,1573,1574],{"class":233,"line":505},[456,1575,1576],{"class":465},"    QgsProcessingException,\n",[456,1578,1579],{"class":233,"line":512},[456,1580,502],{"class":465},[456,1582,1583,1585],{"class":233,"line":517},[456,1584,469],{"class":461},[456,1586,1587],{"class":465}," processing\n",[456,1589,1590],{"class":233,"line":535},[456,1591,509],{"emptyLinePlaceholder":508},[456,1593,1594],{"class":233,"line":549},[456,1595,1596],{"class":936},"# Run the algorithm headlessly once it is registered on a provider:\n",[456,1598,1599,1602,1604,1607,1610],{"class":233,"line":560},[456,1600,1601],{"class":465},"result ",[456,1603,588],{"class":461},[456,1605,1606],{"class":465}," processing.run(",[456,1608,1609],{"class":545},"\"mytools:bufferandmeasure\"",[456,1611,1612],{"class":465},", {\n",[456,1614,1615,1618,1621,1624],{"class":233,"line":571},[456,1616,1617],{"class":545},"    \"INPUT\"",[456,1619,1620],{"class":465},": ",[456,1622,1623],{"class":545},"\"\u002Fdata\u002Fsites.gpkg|layername=sites\"",[456,1625,622],{"class":465},[456,1627,1628,1631,1633,1636],{"class":233,"line":576},[456,1629,1630],{"class":545},"    \"DISTANCE\"",[456,1632,1620],{"class":465},[456,1634,1635],{"class":538},"250.0",[456,1637,622],{"class":465},[456,1639,1640,1643,1645,1648],{"class":233,"line":596},[456,1641,1642],{"class":545},"    \"OUTPUT\"",[456,1644,1620],{"class":465},[456,1646,1647],{"class":545},"\"memory:buffered_sites\"",[456,1649,622],{"class":465},[456,1651,1652],{"class":233,"line":605},[456,1653,1654],{"class":465},"})\n",[456,1656,1657,1660,1663,1666,1669,1672],{"class":233,"line":611},[456,1658,1659],{"class":538},"print",[456,1661,1662],{"class":465},"(result[",[456,1664,1665],{"class":545},"\"OUTPUT\"",[456,1667,1668],{"class":465},"].featureCount(), ",[456,1670,1671],{"class":545},"\"features written\"",[456,1673,502],{"class":465},[14,1675,1676,1678,1679,1682,1683,1685,1686,1688,1689,617],{},[349,1677,783],{}," The first import block lists every class the algorithm references. The ",[38,1680,1681],{},"processing.run()"," call exercises the algorithm exactly as the Toolbox dialog would, using the parameter keys you defined. Passing ",[38,1684,1647],{}," as ",[38,1687,252],{}," keeps the result in RAM for inspection. For the broader patterns around invoking algorithms programmatically, see ",[20,1690,1692],{"href":1691},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script",[14,1694,1695],{},"To register the class on a temporary provider purely for testing — before you commit to the full plugin scaffolding — you can create a one-off provider and add it to the registry directly from the console:",[447,1697,1699],{"className":449,"code":1698,"language":451,"meta":452,"style":452},"from qgis.core import QgsApplication, QgsProcessingProvider\n\n\nclass _TempProvider(QgsProcessingProvider):\n    def loadAlgorithms(self):\n        self.addAlgorithm(BufferAndMeasureAlgorithm())\n\n    def id(self):\n        return \"mytools\"\n\n    def name(self):\n        return \"My Tools (dev)\"\n\n\nprovider = _TempProvider()\nQgsApplication.processingRegistry().addProvider(provider)\n",[38,1700,1701,1712,1716,1720,1734,1743,1750,1754,1763,1770,1774,1782,1789,1793,1797,1807],{"__ignoreMap":452},[456,1702,1703,1705,1707,1709],{"class":233,"line":458},[456,1704,462],{"class":461},[456,1706,466],{"class":465},[456,1708,469],{"class":461},[456,1710,1711],{"class":465}," QgsApplication, QgsProcessingProvider\n",[456,1713,1714],{"class":233,"line":475},[456,1715,509],{"emptyLinePlaceholder":508},[456,1717,1718],{"class":233,"line":481},[456,1719,509],{"emptyLinePlaceholder":508},[456,1721,1722,1724,1727,1729,1732],{"class":233,"line":487},[456,1723,520],{"class":461},[456,1725,1726],{"class":523}," _TempProvider",[456,1728,527],{"class":465},[456,1730,1731],{"class":523},"QgsProcessingProvider",[456,1733,532],{"class":465},[456,1735,1736,1738,1741],{"class":233,"line":493},[456,1737,579],{"class":461},[456,1739,1740],{"class":523}," loadAlgorithms",[456,1742,1327],{"class":465},[456,1744,1745,1747],{"class":233,"line":499},[456,1746,599],{"class":538},[456,1748,1749],{"class":465},".addAlgorithm(BufferAndMeasureAlgorithm())\n",[456,1751,1752],{"class":233,"line":505},[456,1753,509],{"emptyLinePlaceholder":508},[456,1755,1756,1758,1761],{"class":233,"line":512},[456,1757,579],{"class":461},[456,1759,1760],{"class":538}," id",[456,1762,1327],{"class":465},[456,1764,1765,1767],{"class":233,"line":517},[456,1766,1249],{"class":461},[456,1768,1769],{"class":545}," \"mytools\"\n",[456,1771,1772],{"class":233,"line":535},[456,1773,509],{"emptyLinePlaceholder":508},[456,1775,1776,1778,1780],{"class":233,"line":549},[456,1777,579],{"class":461},[456,1779,1324],{"class":523},[456,1781,1327],{"class":465},[456,1783,1784,1786],{"class":233,"line":560},[456,1785,1249],{"class":461},[456,1787,1788],{"class":545}," \"My Tools (dev)\"\n",[456,1790,1791],{"class":233,"line":571},[456,1792,509],{"emptyLinePlaceholder":508},[456,1794,1795],{"class":233,"line":576},[456,1796,509],{"emptyLinePlaceholder":508},[456,1798,1799,1802,1804],{"class":233,"line":596},[456,1800,1801],{"class":465},"provider ",[456,1803,588],{"class":461},[456,1805,1806],{"class":465}," _TempProvider()\n",[456,1808,1809],{"class":233,"line":605},[456,1810,1811],{"class":465},"QgsApplication.processingRegistry().addProvider(provider)\n",[14,1813,1814,1816,1817,1820,1821,1824,1825,1827],{},[349,1815,783],{}," This mirrors what a real plugin's ",[38,1818,1819],{},"initProcessing()"," does, but runs inline so you can iterate without restarting QGIS. After editing the algorithm, call ",[38,1822,1823],{},"QgsApplication.processingRegistry().removeProvider(provider)"," and re-run the snippet to refresh. Once the class is stable, move it into a packaged provider as described in ",[20,1826,51],{"href":31}," so it survives QGIS restarts and can be distributed.",[338,1829,1831],{"id":1830},"the-algorithm-lifecycle","The algorithm lifecycle",[14,1833,1834,1835,1838],{},"Understanding when each method runs explains most of the rules — in particular why ",[38,1836,1837],{},"createInstance"," exists and why state must not be stored on the algorithm object.",[14,1840,1841],{},[63,1842,1845,1848,1851,1855,1864,1870,1873,1877,1882,1917,1922,1926,1930,1935,1940,1944,1947],{"viewBox":1843,"role":66,"ariaLabel":1844,"xmlns":68},"0 0 760 244","The algorithm lifecycle: the registered prototype is cloned per run, initAlgorithm declares parameters, and processAlgorithm executes on the clone",[70,1846,1847],{},"Why createInstance exists",[74,1849,1850],{},"One prototype instance is registered with the provider and lives for the whole session. For each run, createInstance clones it, initAlgorithm declares the parameters on the clone, and processAlgorithm executes there. Because several runs can be in flight at once, storing state on the object would leak between them.",[78,1852],{"x":80,"y":80,"width":1853,"height":1854,"fill":83},"760","244",[93,1856,1857],{},[96,1858,1861],{"id":1859,"viewBox":99,"refX":132,"refY":101,"markerWidth":102,"markerHeight":102,"orient":1860},"algLifeArrow","auto-start-reverse",[106,1862],{"d":1863,"fill":118},"M0 0 L10 5 L0 10 z",[85,1865,1869],{"x":1866,"y":1867,"style":1868,"fill":90,"textAnchor":91},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","One prototype, one clone per run",[78,1871],{"x":1872,"y":246,"width":319,"height":123,"rx":116,"fill":179,"stroke":118,"style":135},"16",[85,1874,1876],{"x":266,"y":1875,"style":199,"fill":184,"textAnchor":91},"124","the prototype",[85,1878,1881],{"x":266,"y":1879,"style":1880,"fill":184,"textAnchor":91},"146","text-anchor:middle;font-size:10.5px;font-family:sans-serif","registered once",[1883,1884,1885,1889,1894,1898,1901,1905,1908,1911,1914],"g",{},[78,1886],{"x":1887,"y":178,"width":130,"height":1888,"rx":132,"fill":133,"stroke":134,"style":208},"288","56",[85,1890,1893],{"x":295,"y":1891,"style":1892,"fill":134,"textAnchor":91},"70","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","run 1 — a clone",[85,1895,1897],{"x":295,"y":1896,"style":1880,"fill":109,"textAnchor":91},"90","initAlgorithm → processAlgorithm",[78,1899],{"x":1887,"y":1900,"width":130,"height":1888,"rx":132,"fill":133,"stroke":134,"style":208},"110",[85,1902,1904],{"x":295,"y":1903,"style":1892,"fill":134,"textAnchor":91},"134","run 2 — a clone",[85,1906,1897],{"x":295,"y":1907,"style":1880,"fill":109,"textAnchor":91},"154",[78,1909],{"x":1887,"y":1910,"width":130,"height":1888,"rx":132,"fill":133,"stroke":134,"style":208},"174",[85,1912,1913],{"x":295,"y":313,"style":1892,"fill":134,"textAnchor":91},"run 3 — a clone",[85,1915,1897],{"x":295,"y":1916,"style":1880,"fill":109,"textAnchor":91},"218",[233,1918],{"x1":1919,"y1":251,"x2":166,"y2":1920,"stroke":118,"style":1921},"192","74","stroke-width:2;marker-end:url(#algLifeArrow)",[233,1923],{"x1":1919,"y1":1924,"x2":166,"y2":1925,"stroke":118,"style":1921},"132","138",[233,1927],{"x1":1919,"y1":1928,"x2":166,"y2":1929,"stroke":118,"style":1921},"144","202",[85,1931,418],{"x":1932,"y":1933,"style":1934,"fill":118,"textAnchor":91},"240","88","text-anchor:middle;font-size:10px;font-family:sans-serif",[78,1936],{"x":1937,"y":246,"width":1938,"height":123,"rx":116,"fill":133,"stroke":1939,"style":135},"528","216","#b91c1c",[85,1941,1943],{"x":1942,"y":1875,"style":1892,"fill":1939,"textAnchor":91},"636","self.counter = 0",[85,1945,1946],{"x":1942,"y":1879,"style":1880,"fill":109,"textAnchor":91},"leaks between concurrent runs",[233,1948],{"x1":1949,"y1":1924,"x2":1950,"y2":1924,"stroke":1939,"style":1951},"488","524","stroke-width:2;stroke-dasharray:5 4;marker-end:url(#algLifeArrow)",[338,1953,1955],{"id":1954},"feedback-cancellation-and-progress","Feedback, cancellation and progress",[14,1957,1958,1961],{},[38,1959,1960],{},"processAlgorithm"," receives a feedback object, and using it properly is what separates an algorithm that feels native from one that appears to hang.",[14,1963,1964],{},[63,1965,1968,1971,1974,1977,1980,1983,1987,1996,2000,2003,2007,2010,2012,2016,2018,2021,2025,2028,2031],{"viewBox":1966,"role":66,"ariaLabel":1967,"xmlns":68},"0 0 760 236","A processing loop reporting progress and checking for cancellation on each iteration, compared with one that does neither and appears frozen",[70,1969,1970],{},"Reporting progress and honouring cancel",[74,1972,1973],{},"A loop that calls setProgress on each iteration drives the progress bar and a loop that checks isCanceled can stop early when the user presses cancel. A loop that does neither leaves the progress bar at zero and ignores the cancel button entirely, so the dialog appears frozen until the work finishes.",[78,1975],{"x":80,"y":80,"width":1853,"height":1976,"fill":83},"236",[85,1978,1979],{"x":1866,"y":1867,"style":1868,"fill":90,"textAnchor":91},"Two calls inside the loop are the whole difference",[78,1981],{"x":1872,"y":178,"width":1982,"height":319,"rx":116,"fill":133,"stroke":1939,"style":135},"356",[85,1984,1986],{"x":1985,"y":1891,"style":124,"fill":1939,"textAnchor":91},"194","neither call",[78,1988],{"x":1989,"y":302,"width":1990,"height":1991,"rx":1992,"fill":1993,"stroke":1994,"style":1995},"48","272","22","4","#e7e2d4","#59645f","stroke-width:1.2",[85,1997,1999],{"x":1998,"y":193,"style":306,"fill":109,"textAnchor":91},"184","progress bar stays at 0%",[78,2001],{"x":251,"y":198,"width":235,"height":88,"rx":101,"fill":1993,"stroke":1994,"style":2002},"stroke-width:1.4",[85,2004,2006],{"x":1998,"y":2005,"style":306,"fill":1994,"textAnchor":91},"178","Cancel — does nothing",[85,2008,2009],{"x":1985,"y":261,"style":306,"fill":1939,"textAnchor":91},"the dialog looks frozen",[78,2011],{"x":295,"y":178,"width":1982,"height":319,"rx":116,"fill":133,"stroke":227,"style":135},[85,2013,2015],{"x":2014,"y":1891,"style":124,"fill":227,"textAnchor":91},"566","setProgress + isCanceled",[78,2017],{"x":87,"y":302,"width":1990,"height":1991,"rx":1992,"fill":1993,"stroke":1994,"style":1995},[78,2019],{"x":87,"y":302,"width":319,"height":1991,"rx":1992,"fill":227,"fillOpacity":2020},0.5,[85,2022,2024],{"x":2023,"y":193,"style":306,"fill":109,"textAnchor":91},"556","progress advances per feature",[78,2026],{"x":2027,"y":198,"width":235,"height":88,"rx":101,"fill":133,"stroke":227,"style":208},"492",[85,2029,2030],{"x":2023,"y":2005,"style":306,"fill":227,"textAnchor":91},"Cancel — stops cleanly",[85,2032,2033],{"x":2014,"y":261,"style":306,"fill":227,"textAnchor":91},"the algorithm feels native",[338,2035,2037],{"id":2036},"qgis-version-compatibility","QGIS Version Compatibility",[14,2039,2040,2041,2043,2044,2047],{},"The example targets ",[349,2042,351],{}," (Python 3.12). The algorithm API is stable across current releases; the only common pitfall is the ",[38,2045,2046],{},"QVariant"," import on older Pythons.",[2049,2050,2051,2067],"table",{},[2052,2053,2054],"thead",{},[2055,2056,2057,2061,2064],"tr",{},[2058,2059,2060],"th",{},"QGIS release",[2058,2062,2063],{},"Python",[2058,2065,2066],{},"Notes",[2068,2069,2070,2086,2097],"tbody",{},[2055,2071,2072,2076,2079],{},[2073,2074,2075],"td",{},"3.28 LTR",[2073,2077,2078],{},"3.9",[2073,2080,2081,2082,2085],{},"Works unchanged; ",[38,2083,2084],{},"QVariant.Double"," and all parameter classes available.",[2055,2087,2088,2091,2094],{},[2073,2089,2090],{},"3.34 LTR",[2073,2092,2093],{},"3.12",[2073,2095,2096],{},"Baseline for this guide.",[2055,2098,2099,2102,2104],{},[2073,2100,2101],{},"3.40 \u002F 3.44",[2073,2103,2093],{},[2073,2105,2106,2107,2109],{},"No breaking changes; newer builds also accept native Python type hints for fields, but ",[38,2108,2084],{}," remains valid and portable.",[14,2111,2112,2113,2116,2117,2120],{},"For maximum portability across all three lines, keep using ",[38,2114,2115],{},"QgsField(\"buff_area\", QVariant.Double)"," rather than the newer ",[38,2118,2119],{},"QMetaType","-based constructors, which only exist in the most recent releases.",[338,2122,2124],{"id":2123},"troubleshooting","Troubleshooting",[14,2126,2127,2132,2133,2135,2136,2138,2139,2142],{},[349,2128,2129],{},[38,2130,2131],{},"QgsProcessingException: There was an error initializing the algorithm","\nYour ",[38,2134,418],{}," is missing or returns ",[38,2137,855],{},". It must return a new ",[38,2140,2141],{},"BufferAndMeasureAlgorithm()"," instance.",[14,2144,2145,2148,2149,2152,2153,2155,2156,2159],{},[349,2146,2147],{},"The output layer is empty.","\nYou forgot to return the result dictionary, or ",[38,2150,2151],{},"addFeature()"," was never reached. Confirm ",[38,2154,172],{}," ends with ",[38,2157,2158],{},"return {self.OUTPUT: dest_id}"," and that the source actually contains features.",[14,2161,2162,2168,2169,2172,2173,2175,2176,2179],{},[349,2163,2164,2167],{},[38,2165,2166],{},"Wrong parameter value"," for OUTPUT.","\nThe sink geometry type or CRS does not match what you write. Ensure the ",[38,2170,2171],{},"QgsWkbTypes"," value passed to ",[38,2174,1271],{}," matches the geometry your transformation produces (buffering points and polygons both yield ",[38,2177,2178],{},"Polygon",").",[14,2181,2182,2185,2187,2188,2191,2192,2195,2196,2199,2200,2203],{},[349,2183,2184],{},"Progress bar never moves.",[38,2186,1290],{}," is missing or ",[38,2189,2190],{},"featureCount()"," returned ",[38,2193,2194],{},"-1"," for a streamed source. Guard the step calculation with ",[38,2197,2198],{},"if total else 0"," as shown, and call ",[38,2201,2202],{},"setProgress()"," inside the loop.",[14,2205,2206,2211,2212,2215],{},[349,2207,2208,617],{},[38,2209,2210],{},"NameError: name 'QVariant' is not defined","\nAdd ",[38,2213,2214],{},"from qgis.PyQt.QtCore import QVariant"," to the import block. This is the single most common omission when copying field-creation code.",[338,2217,2219],{"id":2218},"conclusion","Conclusion",[14,2221,2222,2223,2225,2226,2228,2229,2231,2232,2234],{},"A custom Processing algorithm is just a ",[38,2224,40],{}," subclass with declared parameters and a ",[38,2227,172],{}," that reads a source, transforms features, and fills a sink while reporting through ",[38,2230,44],{},". Once it runs in isolation, registering it on a provider — covered in ",[20,2233,51],{"href":31}," — makes it installable, batchable, and chainable with no further changes to the class itself.",[338,2236,2238],{"id":2237},"frequently-asked-questions","Frequently Asked Questions",[14,2240,2241,2244,2245,2248],{},[349,2242,2243],{},"Do I have to write a provider to use a custom algorithm?","\nNo. You can drop the same class into the user ",[38,2246,2247],{},"processing\u002Fscripts\u002F"," folder, where it appears under the built-in Scripts provider. A provider is for branding, grouping, and distribution.",[14,2250,2251,2254,2255,2258,2259,2262,2263,2265,2266,333,2269,2272,2273,617],{},[349,2252,2253],{},"How do I add a dropdown or boolean option?","\nUse ",[38,2256,2257],{},"QgsProcessingParameterEnum"," for a fixed list and ",[38,2260,2261],{},"QgsProcessingParameterBoolean"," for a checkbox inside ",[38,2264,332],{},", then read them with ",[38,2267,2268],{},"parameterAsEnum()",[38,2270,2271],{},"parameterAsBool()"," in ",[38,2274,172],{},[14,2276,2277,2280,2281,2283],{},[349,2278,2279],{},"Why must createInstance return a new object?","\nBatch and model execution may run several copies concurrently. Returning ",[38,2282,855],{}," shares mutable state between runs and produces corrupted or interleaved results.",[14,2285,2286,2289,2290,2294],{},[349,2287,2288],{},"Can I chain my algorithm with others?","\nYes. Because it is registered with Processing, it works in the Model Designer and in scripts — see ",[20,2291,2293],{"href":2292},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fchain-buffer-and-clip-pyqgis\u002F","Chain Buffer and Clip in PyQGIS"," for a worked pipeline.",[338,2296,2298],{"id":2297},"related","Related",[343,2300,2301,2305,2309],{},[346,2302,2303],{},[20,2304,51],{"href":31},[346,2306,2307],{},[20,2308,1692],{"href":1691},[346,2310,2311],{},[20,2312,2293],{"href":2292},[2314,2315,2316],"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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":452,"searchDepth":475,"depth":475,"links":2318},[2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332],{"id":340,"depth":475,"text":341},{"id":367,"depth":475,"text":368},{"id":385,"depth":475,"text":386},{"id":432,"depth":475,"text":433},{"id":807,"depth":475,"text":808},{"id":1301,"depth":475,"text":1302},{"id":1520,"depth":475,"text":1521},{"id":1830,"depth":475,"text":1831},{"id":1954,"depth":475,"text":1955},{"id":2036,"depth":475,"text":2037},{"id":2123,"depth":475,"text":2124},{"id":2218,"depth":475,"text":2219},{"id":2237,"depth":475,"text":2238},{"id":2297,"depth":475,"text":2298},"Subclass QgsProcessingAlgorithm to build a custom Processing algorithm in PyQGIS, with a complete runnable example covering parameters, feedback, and feature sinks.","md",{"slug":2336,"type":2337,"breadcrumb":2338,"datePublished":2339,"dateModified":2340},"write-custom-processing-algorithm-pyqgis","article","Write a Custom Processing Algorithm","2026-06-18","2026-07-18","\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis",{"title":5,"description":2333},"qgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002Findex","gqeoH4vQSMWokTk3gMAHTNhwzgL3u4YOZtcqBFGQk8s",1787823363072]