[{"data":1,"prerenderedAt":1844},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis":3},{"id":4,"title":5,"body":6,"description":1837,"extension":1838,"meta":1839,"navigation":214,"path":1840,"seo":1841,"stem":1842,"__hash__":1843},"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":1823},"minimark",[9,13,26,39,44,67,71,86,90,93,135,139,151,488,514,518,534,972,1011,1015,1025,1200,1230,1234,1237,1386,1405,1408,1524,1540,1544,1554,1616,1627,1631,1649,1666,1686,1710,1722,1726,1741,1745,1755,1781,1790,1801,1805,1819],[10,11,5],"h1",{"id":12},"write-a-custom-processing-algorithm-in-pyqgis",[14,15,16,17,21,22,25],"p",{},"Subclassing ",[18,19,20],"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 copies an input vector layer to an output, computes a new attribute, and reports progress through ",[18,23,24],{},"feedback"," — so you can see every required method working together rather than in isolated fragments.",[14,27,28,29,34,35,38],{},"This task belongs to the ",[30,31,33],"a",{"href":32},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002F","Processing Provider Plugins for QGIS"," cluster. The algorithm class you build here is exactly what a provider's ",[18,36,37],{},"loadAlgorithms()"," registers, so once it runs you can drop it straight into a plugin.",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,47,55,58,61],"ul",{},[48,49,50,54],"li",{},[51,52,53],"strong",{},"QGIS 3.34 LTR"," (Python 3.12) with the Processing plugin active.",[48,56,57],{},"A vector layer to test against (any polygon or point layer in the project).",[48,59,60],{},"Basic Python class knowledge and the QGIS Python Console for quick iteration.",[48,62,63,64,66],{},"Optional: the provider scaffolding from ",[30,65,33],{"href":32}," if you intend to ship the algorithm in a plugin.",[40,68,70],{"id":69},"how-a-processing-algorithm-differs-from-a-plain-function","How a Processing Algorithm Differs from a Plain Function",[14,72,73,74,78,79,81,82,85],{},"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 ",[75,76,77],"em",{},"sink"," instead of a hardcoded file, you let the user pick GeoPackage, Shapefile, or an in-memory layer at run time. By reporting through ",[75,80,24],{}," instead of ",[18,83,84],{},"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.",[40,87,89],{"id":88},"the-methods-every-algorithm-must-implement","The Methods Every Algorithm Must Implement",[14,91,92],{},"A Processing algorithm is defined by a small, fixed set of overrides. Identity methods describe the tool; the two lifecycle methods do the work.",[45,94,95,101,107,117,123,129],{},[48,96,97,100],{},[18,98,99],{},"name()"," — the machine id (lowercase, no spaces); combined with the provider id to address the algorithm.",[48,102,103,106],{},[18,104,105],{},"displayName()"," — the human-readable label in the Toolbox and dialog.",[48,108,109,112,113,116],{},[18,110,111],{},"group()"," \u002F ",[18,114,115],{},"groupId()"," — optional sub-grouping under the provider.",[48,118,119,122],{},[18,120,121],{},"createInstance()"," — returns a fresh copy of the algorithm; required by the framework.",[48,124,125,128],{},[18,126,127],{},"initAlgorithm()"," — declares input and output parameters.",[48,130,131,134],{},[18,132,133],{},"processAlgorithm()"," — reads the source, transforms features, writes the sink, reports progress.",[40,136,138],{"id":137},"declaring-parameters-in-initalgorithm","Declaring Parameters in initAlgorithm",[14,140,141,143,144,147,148,150],{},[18,142,127],{}," runs once when QGIS builds the dialog. Each ",[18,145,146],{},"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 ",[18,149,133],{}," readable and typo-proof.",[152,153,158],"pre",{"className":154,"code":155,"language":156,"meta":157,"style":157},"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","",[18,159,160,179,185,191,197,203,209,216,221,239,253,264,275,280,300,309,315,330,344,350,356,363,369,381,393,405,418,431,436,441,448,454,466,478,483],{"__ignoreMap":157},[161,162,165,169,173,176],"span",{"class":163,"line":164},"line",1,[161,166,168],{"class":167},"snl16","from",[161,170,172],{"class":171},"s95oV"," qgis.core ",[161,174,175],{"class":167},"import",[161,177,178],{"class":171}," (\n",[161,180,182],{"class":163,"line":181},2,[161,183,184],{"class":171},"    QgsProcessingAlgorithm,\n",[161,186,188],{"class":163,"line":187},3,[161,189,190],{"class":171},"    QgsProcessingParameterFeatureSource,\n",[161,192,194],{"class":163,"line":193},4,[161,195,196],{"class":171},"    QgsProcessingParameterFeatureSink,\n",[161,198,200],{"class":163,"line":199},5,[161,201,202],{"class":171},"    QgsProcessingParameterNumber,\n",[161,204,206],{"class":163,"line":205},6,[161,207,208],{"class":171},")\n",[161,210,212],{"class":163,"line":211},7,[161,213,215],{"emptyLinePlaceholder":214},true,"\n",[161,217,219],{"class":163,"line":218},8,[161,220,215],{"emptyLinePlaceholder":214},[161,222,224,227,231,234,236],{"class":163,"line":223},9,[161,225,226],{"class":167},"class",[161,228,230],{"class":229},"svObZ"," BufferAndMeasureAlgorithm",[161,232,233],{"class":171},"(",[161,235,20],{"class":229},[161,237,238],{"class":171},"):\n",[161,240,242,246,249],{"class":163,"line":241},10,[161,243,245],{"class":244},"sDLfK","    INPUT",[161,247,248],{"class":167}," =",[161,250,252],{"class":251},"sU2Wk"," \"INPUT\"\n",[161,254,256,259,261],{"class":163,"line":255},11,[161,257,258],{"class":244},"    DISTANCE",[161,260,248],{"class":167},[161,262,263],{"class":251}," \"DISTANCE\"\n",[161,265,267,270,272],{"class":163,"line":266},12,[161,268,269],{"class":244},"    OUTPUT",[161,271,248],{"class":167},[161,273,274],{"class":251}," \"OUTPUT\"\n",[161,276,278],{"class":163,"line":277},13,[161,279,215],{"emptyLinePlaceholder":214},[161,281,283,286,289,292,295,298],{"class":163,"line":282},14,[161,284,285],{"class":167},"    def",[161,287,288],{"class":229}," initAlgorithm",[161,290,291],{"class":171},"(self, config",[161,293,294],{"class":167},"=",[161,296,297],{"class":244},"None",[161,299,238],{"class":171},[161,301,303,306],{"class":163,"line":302},15,[161,304,305],{"class":244},"        self",[161,307,308],{"class":171},".addParameter(\n",[161,310,312],{"class":163,"line":311},16,[161,313,314],{"class":171},"            QgsProcessingParameterFeatureSource(\n",[161,316,318,321,324,327],{"class":163,"line":317},17,[161,319,320],{"class":244},"                self",[161,322,323],{"class":171},".",[161,325,326],{"class":244},"INPUT",[161,328,329],{"class":171},",\n",[161,331,333,335,338,341],{"class":163,"line":332},18,[161,334,320],{"class":244},[161,336,337],{"class":171},".tr(",[161,339,340],{"class":251},"\"Input layer\"",[161,342,343],{"class":171},"),\n",[161,345,347],{"class":163,"line":346},19,[161,348,349],{"class":171},"            )\n",[161,351,353],{"class":163,"line":352},20,[161,354,355],{"class":171},"        )\n",[161,357,359,361],{"class":163,"line":358},21,[161,360,305],{"class":244},[161,362,308],{"class":171},[161,364,366],{"class":163,"line":365},22,[161,367,368],{"class":171},"            QgsProcessingParameterNumber(\n",[161,370,372,374,376,379],{"class":163,"line":371},23,[161,373,320],{"class":244},[161,375,323],{"class":171},[161,377,378],{"class":244},"DISTANCE",[161,380,329],{"class":171},[161,382,384,386,388,391],{"class":163,"line":383},24,[161,385,320],{"class":244},[161,387,337],{"class":171},[161,389,390],{"class":251},"\"Buffer distance (layer units)\"",[161,392,343],{"class":171},[161,394,396,400,402],{"class":163,"line":395},25,[161,397,399],{"class":398},"s9osk","                type",[161,401,294],{"class":167},[161,403,404],{"class":171},"QgsProcessingParameterNumber.Double,\n",[161,406,408,411,413,416],{"class":163,"line":407},26,[161,409,410],{"class":398},"                defaultValue",[161,412,294],{"class":167},[161,414,415],{"class":244},"100.0",[161,417,329],{"class":171},[161,419,421,424,426,429],{"class":163,"line":420},27,[161,422,423],{"class":398},"                minValue",[161,425,294],{"class":167},[161,427,428],{"class":244},"0.0",[161,430,329],{"class":171},[161,432,434],{"class":163,"line":433},28,[161,435,349],{"class":171},[161,437,439],{"class":163,"line":438},29,[161,440,355],{"class":171},[161,442,444,446],{"class":163,"line":443},30,[161,445,305],{"class":244},[161,447,308],{"class":171},[161,449,451],{"class":163,"line":450},31,[161,452,453],{"class":171},"            QgsProcessingParameterFeatureSink(\n",[161,455,457,459,461,464],{"class":163,"line":456},32,[161,458,320],{"class":244},[161,460,323],{"class":171},[161,462,463],{"class":244},"OUTPUT",[161,465,329],{"class":171},[161,467,469,471,473,476],{"class":163,"line":468},33,[161,470,320],{"class":244},[161,472,337],{"class":171},[161,474,475],{"class":251},"\"Buffered output\"",[161,477,343],{"class":171},[161,479,481],{"class":163,"line":480},34,[161,482,349],{"class":171},[161,484,486],{"class":163,"line":485},35,[161,487,355],{"class":171},[14,489,490,493,494,497,498,501,502,505,506,509,510,513],{},[51,491,492],{},"Breakdown:"," ",[18,495,496],{},"QgsProcessingParameterFeatureSource"," produces a layer picker that also accepts a \"selected features only\" toggle and on-the-fly reprojection. ",[18,499,500],{},"QgsProcessingParameterNumber"," with ",[18,503,504],{},"type=Double"," renders a spin box; ",[18,507,508],{},"minValue"," enforces a non-negative distance in the dialog. ",[18,511,512],{},"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.",[40,515,517],{"id":516},"implementing-processalgorithm","Implementing processAlgorithm",[14,519,520,522,523,526,527,530,531,533],{},[18,521,133],{}," receives the resolved ",[18,524,525],{},"parameters",", a ",[18,528,529],{},"context",", and a ",[18,532,24],{}," 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.",[152,535,537],{"className":154,"code":536,"language":156,"meta":157,"style":157},"    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",[18,538,539,549,572,589,597,613,617,637,641,647,657,668,672,684,689,700,705,710,715,720,724,737,743,758,762,772,799,803,820,828,833,837,852,862,867,872,884,890,895,913,918,954],{"__ignoreMap":157},[161,540,541,543,546],{"class":163,"line":164},[161,542,285],{"class":167},[161,544,545],{"class":229}," processAlgorithm",[161,547,548],{"class":171},"(self, parameters, context, feedback):\n",[161,550,551,554,556,559,562,565,567,569],{"class":163,"line":181},[161,552,553],{"class":171},"        source ",[161,555,294],{"class":167},[161,557,558],{"class":244}," self",[161,560,561],{"class":171},".parameterAsSource(parameters, ",[161,563,564],{"class":244},"self",[161,566,323],{"class":171},[161,568,326],{"class":244},[161,570,571],{"class":171},", context)\n",[161,573,574,577,580,583,586],{"class":163,"line":187},[161,575,576],{"class":167},"        if",[161,578,579],{"class":171}," source ",[161,581,582],{"class":167},"is",[161,584,585],{"class":244}," None",[161,587,588],{"class":171},":\n",[161,590,591,594],{"class":163,"line":193},[161,592,593],{"class":167},"            raise",[161,595,596],{"class":171}," QgsProcessingException(\n",[161,598,599,601,604,606,608,610],{"class":163,"line":199},[161,600,320],{"class":244},[161,602,603],{"class":171},".invalidSourceError(parameters, ",[161,605,564],{"class":244},[161,607,323],{"class":171},[161,609,326],{"class":244},[161,611,612],{"class":171},"))\n",[161,614,615],{"class":163,"line":205},[161,616,215],{"emptyLinePlaceholder":214},[161,618,619,622,624,626,629,631,633,635],{"class":163,"line":211},[161,620,621],{"class":171},"        distance ",[161,623,294],{"class":167},[161,625,558],{"class":244},[161,627,628],{"class":171},".parameterAsDouble(parameters, ",[161,630,564],{"class":244},[161,632,323],{"class":171},[161,634,378],{"class":244},[161,636,571],{"class":171},[161,638,639],{"class":163,"line":218},[161,640,215],{"emptyLinePlaceholder":214},[161,642,643],{"class":163,"line":223},[161,644,646],{"class":645},"sAwPA","        # Build the output field set: input fields plus a new area field.\n",[161,648,649,652,654],{"class":163,"line":241},[161,650,651],{"class":171},"        out_fields ",[161,653,294],{"class":167},[161,655,656],{"class":171}," source.fields()\n",[161,658,659,662,665],{"class":163,"line":255},[161,660,661],{"class":171},"        out_fields.append(QgsField(",[161,663,664],{"class":251},"\"buff_area\"",[161,666,667],{"class":171},", QVariant.Double))\n",[161,669,670],{"class":163,"line":266},[161,671,215],{"emptyLinePlaceholder":214},[161,673,674,677,679,681],{"class":163,"line":277},[161,675,676],{"class":171},"        sink, dest_id ",[161,678,294],{"class":167},[161,680,558],{"class":244},[161,682,683],{"class":171},".parameterAsSink(\n",[161,685,686],{"class":163,"line":282},[161,687,688],{"class":171},"            parameters,\n",[161,690,691,694,696,698],{"class":163,"line":302},[161,692,693],{"class":244},"            self",[161,695,323],{"class":171},[161,697,463],{"class":244},[161,699,329],{"class":171},[161,701,702],{"class":163,"line":311},[161,703,704],{"class":171},"            context,\n",[161,706,707],{"class":163,"line":317},[161,708,709],{"class":171},"            out_fields,\n",[161,711,712],{"class":163,"line":332},[161,713,714],{"class":171},"            QgsWkbTypes.Polygon,\n",[161,716,717],{"class":163,"line":346},[161,718,719],{"class":171},"            source.sourceCrs(),\n",[161,721,722],{"class":163,"line":352},[161,723,355],{"class":171},[161,725,726,728,731,733,735],{"class":163,"line":358},[161,727,576],{"class":167},[161,729,730],{"class":171}," sink ",[161,732,582],{"class":167},[161,734,585],{"class":244},[161,736,588],{"class":171},[161,738,739,741],{"class":163,"line":365},[161,740,593],{"class":167},[161,742,596],{"class":171},[161,744,745,747,750,752,754,756],{"class":163,"line":371},[161,746,320],{"class":244},[161,748,749],{"class":171},".invalidSinkError(parameters, ",[161,751,564],{"class":244},[161,753,323],{"class":171},[161,755,463],{"class":244},[161,757,612],{"class":171},[161,759,760],{"class":163,"line":383},[161,761,215],{"emptyLinePlaceholder":214},[161,763,764,767,769],{"class":163,"line":395},[161,765,766],{"class":171},"        total ",[161,768,294],{"class":167},[161,770,771],{"class":171}," source.featureCount()\n",[161,773,774,777,779,782,785,788,791,793,796],{"class":163,"line":407},[161,775,776],{"class":171},"        step ",[161,778,294],{"class":167},[161,780,781],{"class":244}," 100.0",[161,783,784],{"class":167}," \u002F",[161,786,787],{"class":171}," total ",[161,789,790],{"class":167},"if",[161,792,787],{"class":171},[161,794,795],{"class":167},"else",[161,797,798],{"class":244}," 0\n",[161,800,801],{"class":163,"line":420},[161,802,215],{"emptyLinePlaceholder":214},[161,804,805,808,811,814,817],{"class":163,"line":433},[161,806,807],{"class":167},"        for",[161,809,810],{"class":171}," current, feature ",[161,812,813],{"class":167},"in",[161,815,816],{"class":244}," enumerate",[161,818,819],{"class":171},"(source.getFeatures()):\n",[161,821,822,825],{"class":163,"line":438},[161,823,824],{"class":167},"            if",[161,826,827],{"class":171}," feedback.isCanceled():\n",[161,829,830],{"class":163,"line":443},[161,831,832],{"class":167},"                break\n",[161,834,835],{"class":163,"line":450},[161,836,215],{"emptyLinePlaceholder":214},[161,838,839,842,844,847,850],{"class":163,"line":456},[161,840,841],{"class":171},"            geom ",[161,843,294],{"class":167},[161,845,846],{"class":171}," feature.geometry().buffer(distance, ",[161,848,849],{"class":244},"5",[161,851,208],{"class":171},[161,853,854,857,859],{"class":163,"line":468},[161,855,856],{"class":171},"            new_feature ",[161,858,294],{"class":167},[161,860,861],{"class":171}," QgsFeature(out_fields)\n",[161,863,864],{"class":163,"line":480},[161,865,866],{"class":171},"            new_feature.setGeometry(geom)\n",[161,868,869],{"class":163,"line":485},[161,870,871],{"class":171},"            new_feature.setAttributes(\n",[161,873,875,878,881],{"class":163,"line":874},36,[161,876,877],{"class":171},"                feature.attributes() ",[161,879,880],{"class":167},"+",[161,882,883],{"class":171}," [geom.area()])\n",[161,885,887],{"class":163,"line":886},37,[161,888,889],{"class":171},"            sink.addFeature(new_feature, QgsFeatureSink.FastInsert)\n",[161,891,893],{"class":163,"line":892},38,[161,894,215],{"emptyLinePlaceholder":214},[161,896,898,901,904,907,910],{"class":163,"line":897},39,[161,899,900],{"class":171},"            feedback.setProgress(",[161,902,903],{"class":244},"int",[161,905,906],{"class":171},"(current ",[161,908,909],{"class":167},"*",[161,911,912],{"class":171}," step))\n",[161,914,916],{"class":163,"line":915},40,[161,917,215],{"emptyLinePlaceholder":214},[161,919,921,924,927,930,933,936,939,942,944,947,949,952],{"class":163,"line":920},41,[161,922,923],{"class":171},"        feedback.pushInfo(",[161,925,926],{"class":167},"f",[161,928,929],{"class":251},"\"Buffered ",[161,931,932],{"class":244},"{",[161,934,935],{"class":171},"total",[161,937,938],{"class":244},"}",[161,940,941],{"class":251}," features by ",[161,943,932],{"class":244},[161,945,946],{"class":171},"distance",[161,948,938],{"class":244},[161,950,951],{"class":251}," units.\"",[161,953,208],{"class":171},[161,955,957,960,963,965,967,969],{"class":163,"line":956},42,[161,958,959],{"class":167},"        return",[161,961,962],{"class":171}," {",[161,964,564],{"class":244},[161,966,323],{"class":171},[161,968,463],{"class":244},[161,970,971],{"class":171},": dest_id}\n",[14,973,974,493,976,979,980,983,984,987,988,991,992,995,996,999,1000,1003,1004,1007,1008,323],{},[51,975,492],{},[18,977,978],{},"parameterAsSource"," and ",[18,981,982],{},"parameterAsSink"," convert the raw parameter values into usable objects, raising a clear error through the ",[18,985,986],{},"invalidSourceError","\u002F",[18,989,990],{},"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 ",[18,993,994],{},"buff_area",". ",[18,997,998],{},"feedback.isCanceled()"," lets the user stop a long run from the dialog; ",[18,1001,1002],{},"feedback.setProgress()"," drives the progress bar; ",[18,1005,1006],{},"feedback.pushInfo()"," writes to the log panel. The method must return a dictionary keyed by output name, mapping to the sink's ",[18,1009,1010],{},"dest_id",[40,1012,1014],{"id":1013},"identity-and-createinstance","Identity and createInstance",[14,1016,1017,1018,1020,1021,1024],{},"The remaining methods describe the algorithm and let the framework clone it. ",[18,1019,121],{}," must return a new object every time so concurrent batch runs do not share state. The ",[18,1022,1023],{},"tr()"," helper marks strings for translation.",[152,1026,1028],{"className":154,"code":1027,"language":156,"meta":157,"style":157},"    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",[18,1029,1030,1040,1047,1051,1060,1073,1077,1086,1099,1103,1112,1119,1123,1132,1141,1146,1153,1157,1167,1180,1184,1193],{"__ignoreMap":157},[161,1031,1032,1034,1037],{"class":163,"line":164},[161,1033,285],{"class":167},[161,1035,1036],{"class":229}," name",[161,1038,1039],{"class":171},"(self):\n",[161,1041,1042,1044],{"class":163,"line":181},[161,1043,959],{"class":167},[161,1045,1046],{"class":251}," \"bufferandmeasure\"\n",[161,1048,1049],{"class":163,"line":187},[161,1050,215],{"emptyLinePlaceholder":214},[161,1052,1053,1055,1058],{"class":163,"line":193},[161,1054,285],{"class":167},[161,1056,1057],{"class":229}," displayName",[161,1059,1039],{"class":171},[161,1061,1062,1064,1066,1068,1071],{"class":163,"line":199},[161,1063,959],{"class":167},[161,1065,558],{"class":244},[161,1067,337],{"class":171},[161,1069,1070],{"class":251},"\"Buffer and Measure\"",[161,1072,208],{"class":171},[161,1074,1075],{"class":163,"line":205},[161,1076,215],{"emptyLinePlaceholder":214},[161,1078,1079,1081,1084],{"class":163,"line":211},[161,1080,285],{"class":167},[161,1082,1083],{"class":229}," group",[161,1085,1039],{"class":171},[161,1087,1088,1090,1092,1094,1097],{"class":163,"line":218},[161,1089,959],{"class":167},[161,1091,558],{"class":244},[161,1093,337],{"class":171},[161,1095,1096],{"class":251},"\"Vector geometry\"",[161,1098,208],{"class":171},[161,1100,1101],{"class":163,"line":223},[161,1102,215],{"emptyLinePlaceholder":214},[161,1104,1105,1107,1110],{"class":163,"line":241},[161,1106,285],{"class":167},[161,1108,1109],{"class":229}," groupId",[161,1111,1039],{"class":171},[161,1113,1114,1116],{"class":163,"line":255},[161,1115,959],{"class":167},[161,1117,1118],{"class":251}," \"vectorgeometry\"\n",[161,1120,1121],{"class":163,"line":266},[161,1122,215],{"emptyLinePlaceholder":214},[161,1124,1125,1127,1130],{"class":163,"line":277},[161,1126,285],{"class":167},[161,1128,1129],{"class":229}," shortHelpString",[161,1131,1039],{"class":171},[161,1133,1134,1136,1138],{"class":163,"line":282},[161,1135,959],{"class":167},[161,1137,558],{"class":244},[161,1139,1140],{"class":171},".tr(\n",[161,1142,1143],{"class":163,"line":302},[161,1144,1145],{"class":251},"            \"Buffers each input feature by a fixed distance and records \"\n",[161,1147,1148,1151],{"class":163,"line":311},[161,1149,1150],{"class":251},"            \"the resulting polygon area in a new 'buff_area' field.\"",[161,1152,208],{"class":171},[161,1154,1155],{"class":163,"line":317},[161,1156,215],{"emptyLinePlaceholder":214},[161,1158,1159,1161,1164],{"class":163,"line":332},[161,1160,285],{"class":167},[161,1162,1163],{"class":229}," tr",[161,1165,1166],{"class":171},"(self, string):\n",[161,1168,1169,1171,1174,1177],{"class":163,"line":346},[161,1170,959],{"class":167},[161,1172,1173],{"class":171}," QCoreApplication.translate(",[161,1175,1176],{"class":251},"\"Processing\"",[161,1178,1179],{"class":171},", string)\n",[161,1181,1182],{"class":163,"line":352},[161,1183,215],{"emptyLinePlaceholder":214},[161,1185,1186,1188,1191],{"class":163,"line":358},[161,1187,285],{"class":167},[161,1189,1190],{"class":229}," createInstance",[161,1192,1039],{"class":171},[161,1194,1195,1197],{"class":163,"line":365},[161,1196,959],{"class":167},[161,1198,1199],{"class":171}," BufferAndMeasureAlgorithm()\n",[14,1201,1202,493,1204,1206,1207,995,1210,1212,1213,987,1215,1217,1218,995,1220,1223,1224,1226,1227,1229],{},[51,1203,492],{},[18,1205,99],{}," is the permanent id used as ",[18,1208,1209],{},"provider_id:bufferandmeasure",[18,1211,105],{}," is cosmetic and translatable. ",[18,1214,111],{},[18,1216,115],{}," slot the tool under a \"Vector geometry\" node, matching the grouping conventions described in ",[30,1219,33],{"href":32},[18,1221,1222],{},"shortHelpString()"," populates the help panel beside the dialog. ",[18,1225,121],{}," returning a brand-new object is mandatory — returning ",[18,1228,564],{}," causes subtle bugs under batch and model execution.",[40,1231,1233],{"id":1232},"complete-imports-and-a-test-run","Complete Imports and a Test Run",[14,1235,1236],{},"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.",[152,1238,1240],{"className":154,"code":1239,"language":156,"meta":157,"style":157},"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",[18,1241,1242,1254,1264,1269,1274,1279,1284,1289,1293,1300,1304,1309,1325,1338,1350,1362,1367],{"__ignoreMap":157},[161,1243,1244,1246,1249,1251],{"class":163,"line":164},[161,1245,168],{"class":167},[161,1247,1248],{"class":171}," qgis.PyQt.QtCore ",[161,1250,175],{"class":167},[161,1252,1253],{"class":171}," QCoreApplication, QVariant\n",[161,1255,1256,1258,1260,1262],{"class":163,"line":181},[161,1257,168],{"class":167},[161,1259,172],{"class":171},[161,1261,175],{"class":167},[161,1263,178],{"class":171},[161,1265,1266],{"class":163,"line":187},[161,1267,1268],{"class":171},"    QgsField,\n",[161,1270,1271],{"class":163,"line":193},[161,1272,1273],{"class":171},"    QgsFeature,\n",[161,1275,1276],{"class":163,"line":199},[161,1277,1278],{"class":171},"    QgsFeatureSink,\n",[161,1280,1281],{"class":163,"line":205},[161,1282,1283],{"class":171},"    QgsWkbTypes,\n",[161,1285,1286],{"class":163,"line":211},[161,1287,1288],{"class":171},"    QgsProcessingException,\n",[161,1290,1291],{"class":163,"line":218},[161,1292,208],{"class":171},[161,1294,1295,1297],{"class":163,"line":223},[161,1296,175],{"class":167},[161,1298,1299],{"class":171}," processing\n",[161,1301,1302],{"class":163,"line":241},[161,1303,215],{"emptyLinePlaceholder":214},[161,1305,1306],{"class":163,"line":255},[161,1307,1308],{"class":645},"# Run the algorithm headlessly once it is registered on a provider:\n",[161,1310,1311,1314,1316,1319,1322],{"class":163,"line":266},[161,1312,1313],{"class":171},"result ",[161,1315,294],{"class":167},[161,1317,1318],{"class":171}," processing.run(",[161,1320,1321],{"class":251},"\"mytools:bufferandmeasure\"",[161,1323,1324],{"class":171},", {\n",[161,1326,1327,1330,1333,1336],{"class":163,"line":277},[161,1328,1329],{"class":251},"    \"INPUT\"",[161,1331,1332],{"class":171},": ",[161,1334,1335],{"class":251},"\"\u002Fdata\u002Fsites.gpkg|layername=sites\"",[161,1337,329],{"class":171},[161,1339,1340,1343,1345,1348],{"class":163,"line":282},[161,1341,1342],{"class":251},"    \"DISTANCE\"",[161,1344,1332],{"class":171},[161,1346,1347],{"class":244},"250.0",[161,1349,329],{"class":171},[161,1351,1352,1355,1357,1360],{"class":163,"line":302},[161,1353,1354],{"class":251},"    \"OUTPUT\"",[161,1356,1332],{"class":171},[161,1358,1359],{"class":251},"\"memory:buffered_sites\"",[161,1361,329],{"class":171},[161,1363,1364],{"class":163,"line":311},[161,1365,1366],{"class":171},"})\n",[161,1368,1369,1372,1375,1378,1381,1384],{"class":163,"line":317},[161,1370,1371],{"class":244},"print",[161,1373,1374],{"class":171},"(result[",[161,1376,1377],{"class":251},"\"OUTPUT\"",[161,1379,1380],{"class":171},"].featureCount(), ",[161,1382,1383],{"class":251},"\"features written\"",[161,1385,208],{"class":171},[14,1387,1388,1390,1391,1394,1395,1397,1398,1400,1401,323],{},[51,1389,492],{}," The first import block lists every class the algorithm references. The ",[18,1392,1393],{},"processing.run()"," call exercises the algorithm exactly as the Toolbox dialog would, using the parameter keys you defined. Passing ",[18,1396,1359],{}," as ",[18,1399,463],{}," keeps the result in RAM for inspection. For the broader patterns around invoking algorithms programmatically, see ",[30,1402,1404],{"href":1403},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script",[14,1406,1407],{},"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:",[152,1409,1411],{"className":154,"code":1410,"language":156,"meta":157,"style":157},"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",[18,1412,1413,1424,1428,1432,1446,1455,1462,1466,1475,1482,1486,1494,1501,1505,1509,1519],{"__ignoreMap":157},[161,1414,1415,1417,1419,1421],{"class":163,"line":164},[161,1416,168],{"class":167},[161,1418,172],{"class":171},[161,1420,175],{"class":167},[161,1422,1423],{"class":171}," QgsApplication, QgsProcessingProvider\n",[161,1425,1426],{"class":163,"line":181},[161,1427,215],{"emptyLinePlaceholder":214},[161,1429,1430],{"class":163,"line":187},[161,1431,215],{"emptyLinePlaceholder":214},[161,1433,1434,1436,1439,1441,1444],{"class":163,"line":193},[161,1435,226],{"class":167},[161,1437,1438],{"class":229}," _TempProvider",[161,1440,233],{"class":171},[161,1442,1443],{"class":229},"QgsProcessingProvider",[161,1445,238],{"class":171},[161,1447,1448,1450,1453],{"class":163,"line":199},[161,1449,285],{"class":167},[161,1451,1452],{"class":229}," loadAlgorithms",[161,1454,1039],{"class":171},[161,1456,1457,1459],{"class":163,"line":205},[161,1458,305],{"class":244},[161,1460,1461],{"class":171},".addAlgorithm(BufferAndMeasureAlgorithm())\n",[161,1463,1464],{"class":163,"line":211},[161,1465,215],{"emptyLinePlaceholder":214},[161,1467,1468,1470,1473],{"class":163,"line":218},[161,1469,285],{"class":167},[161,1471,1472],{"class":244}," id",[161,1474,1039],{"class":171},[161,1476,1477,1479],{"class":163,"line":223},[161,1478,959],{"class":167},[161,1480,1481],{"class":251}," \"mytools\"\n",[161,1483,1484],{"class":163,"line":241},[161,1485,215],{"emptyLinePlaceholder":214},[161,1487,1488,1490,1492],{"class":163,"line":255},[161,1489,285],{"class":167},[161,1491,1036],{"class":229},[161,1493,1039],{"class":171},[161,1495,1496,1498],{"class":163,"line":266},[161,1497,959],{"class":167},[161,1499,1500],{"class":251}," \"My Tools (dev)\"\n",[161,1502,1503],{"class":163,"line":277},[161,1504,215],{"emptyLinePlaceholder":214},[161,1506,1507],{"class":163,"line":282},[161,1508,215],{"emptyLinePlaceholder":214},[161,1510,1511,1514,1516],{"class":163,"line":302},[161,1512,1513],{"class":171},"provider ",[161,1515,294],{"class":167},[161,1517,1518],{"class":171}," _TempProvider()\n",[161,1520,1521],{"class":163,"line":311},[161,1522,1523],{"class":171},"QgsApplication.processingRegistry().addProvider(provider)\n",[14,1525,1526,1528,1529,1532,1533,1536,1537,1539],{},[51,1527,492],{}," This mirrors what a real plugin's ",[18,1530,1531],{},"initProcessing()"," does, but runs inline so you can iterate without restarting QGIS. After editing the algorithm, call ",[18,1534,1535],{},"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 ",[30,1538,33],{"href":32}," so it survives QGIS restarts and can be distributed.",[40,1541,1543],{"id":1542},"qgis-version-compatibility","QGIS Version Compatibility",[14,1545,1546,1547,1549,1550,1553],{},"The example targets ",[51,1548,53],{}," (Python 3.12). The algorithm API is stable across current releases; the only common pitfall is the ",[18,1551,1552],{},"QVariant"," import on older Pythons.",[1555,1556,1557,1573],"table",{},[1558,1559,1560],"thead",{},[1561,1562,1563,1567,1570],"tr",{},[1564,1565,1566],"th",{},"QGIS release",[1564,1568,1569],{},"Python",[1564,1571,1572],{},"Notes",[1574,1575,1576,1592,1603],"tbody",{},[1561,1577,1578,1582,1585],{},[1579,1580,1581],"td",{},"3.28 LTR",[1579,1583,1584],{},"3.9",[1579,1586,1587,1588,1591],{},"Works unchanged; ",[18,1589,1590],{},"QVariant.Double"," and all parameter classes available.",[1561,1593,1594,1597,1600],{},[1579,1595,1596],{},"3.34 LTR",[1579,1598,1599],{},"3.12",[1579,1601,1602],{},"Baseline for this guide.",[1561,1604,1605,1608,1610],{},[1579,1606,1607],{},"3.40 \u002F 3.44",[1579,1609,1599],{},[1579,1611,1612,1613,1615],{},"No breaking changes; newer builds also accept native Python type hints for fields, but ",[18,1614,1590],{}," remains valid and portable.",[14,1617,1618,1619,1622,1623,1626],{},"For maximum portability across all three lines, keep using ",[18,1620,1621],{},"QgsField(\"buff_area\", QVariant.Double)"," rather than the newer ",[18,1624,1625],{},"QMetaType","-based constructors, which only exist in the most recent releases.",[40,1628,1630],{"id":1629},"troubleshooting","Troubleshooting",[14,1632,1633,1638,1639,1641,1642,1644,1645,1648],{},[51,1634,1635],{},[18,1636,1637],{},"QgsProcessingException: There was an error initializing the algorithm","\nYour ",[18,1640,121],{}," is missing or returns ",[18,1643,564],{},". It must return a new ",[18,1646,1647],{},"BufferAndMeasureAlgorithm()"," instance.",[14,1650,1651,1654,1655,1658,1659,1661,1662,1665],{},[51,1652,1653],{},"The output layer is empty.","\nYou forgot to return the result dictionary, or ",[18,1656,1657],{},"addFeature()"," was never reached. Confirm ",[18,1660,133],{}," ends with ",[18,1663,1664],{},"return {self.OUTPUT: dest_id}"," and that the source actually contains features.",[14,1667,1668,1674,1675,1678,1679,1681,1682,1685],{},[51,1669,1670,1673],{},[18,1671,1672],{},"Wrong parameter value"," for OUTPUT.","\nThe sink geometry type or CRS does not match what you write. Ensure the ",[18,1676,1677],{},"QgsWkbTypes"," value passed to ",[18,1680,982],{}," matches the geometry your transformation produces (buffering points and polygons both yield ",[18,1683,1684],{},"Polygon",").",[14,1687,1688,1691,1693,1694,1697,1698,1701,1702,1705,1706,1709],{},[51,1689,1690],{},"Progress bar never moves.",[18,1692,1002],{}," is missing or ",[18,1695,1696],{},"featureCount()"," returned ",[18,1699,1700],{},"-1"," for a streamed source. Guard the step calculation with ",[18,1703,1704],{},"if total else 0"," as shown, and call ",[18,1707,1708],{},"setProgress()"," inside the loop.",[14,1711,1712,1717,1718,1721],{},[51,1713,1714,323],{},[18,1715,1716],{},"NameError: name 'QVariant' is not defined","\nAdd ",[18,1719,1720],{},"from qgis.PyQt.QtCore import QVariant"," to the import block. This is the single most common omission when copying field-creation code.",[40,1723,1725],{"id":1724},"conclusion","Conclusion",[14,1727,1728,1729,1731,1732,1734,1735,1737,1738,1740],{},"A custom Processing algorithm is just a ",[18,1730,20],{}," subclass with declared parameters and a ",[18,1733,133],{}," that reads a source, transforms features, and fills a sink while reporting through ",[18,1736,24],{},". Once it runs in isolation, registering it on a provider — covered in ",[30,1739,33],{"href":32}," — makes it installable, batchable, and chainable with no further changes to the class itself.",[40,1742,1744],{"id":1743},"frequently-asked-questions","Frequently Asked Questions",[14,1746,1747,1750,1751,1754],{},[51,1748,1749],{},"Do I have to write a provider to use a custom algorithm?","\nNo. You can drop the same class into the user ",[18,1752,1753],{},"processing\u002Fscripts\u002F"," folder, where it appears under the built-in Scripts provider. A provider is for branding, grouping, and distribution.",[14,1756,1757,1760,1761,1764,1765,1768,1769,1771,1772,979,1775,1778,1779,323],{},[51,1758,1759],{},"How do I add a dropdown or boolean option?","\nUse ",[18,1762,1763],{},"QgsProcessingParameterEnum"," for a fixed list and ",[18,1766,1767],{},"QgsProcessingParameterBoolean"," for a checkbox inside ",[18,1770,127],{},", then read them with ",[18,1773,1774],{},"parameterAsEnum()",[18,1776,1777],{},"parameterAsBool()"," in ",[18,1780,133],{},[14,1782,1783,1786,1787,1789],{},[51,1784,1785],{},"Why must createInstance return a new object?","\nBatch and model execution may run several copies concurrently. Returning ",[18,1788,564],{}," shares mutable state between runs and produces corrupted or interleaved results.",[14,1791,1792,1795,1796,1800],{},[51,1793,1794],{},"Can I chain my algorithm with others?","\nYes. Because it is registered with Processing, it works in the Model Designer and in scripts — see ",[30,1797,1799],{"href":1798},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fchain-buffer-and-clip-pyqgis\u002F","Chain Buffer and Clip in PyQGIS"," for a worked pipeline.",[40,1802,1804],{"id":1803},"related","Related",[45,1806,1807,1811,1815],{},[48,1808,1809],{},[30,1810,33],{"href":32},[48,1812,1813],{},[30,1814,1404],{"href":1403},[48,1816,1817],{},[30,1818,1799],{"href":1798},[1820,1821,1822],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":157,"searchDepth":181,"depth":181,"links":1824},[1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836],{"id":42,"depth":181,"text":43},{"id":69,"depth":181,"text":70},{"id":88,"depth":181,"text":89},{"id":137,"depth":181,"text":138},{"id":516,"depth":181,"text":517},{"id":1013,"depth":181,"text":1014},{"id":1232,"depth":181,"text":1233},{"id":1542,"depth":181,"text":1543},{"id":1629,"depth":181,"text":1630},{"id":1724,"depth":181,"text":1725},{"id":1743,"depth":181,"text":1744},{"id":1803,"depth":181,"text":1804},"Subclass QgsProcessingAlgorithm to build a custom Processing algorithm in PyQGIS, with a complete runnable example covering parameters, feedback, and feature sinks.","md",{},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis",{"title":5,"description":1837},"qgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002Findex","zebcdQc3qXogXHkB_QL8wrdlBTa3GGrXdCUnsoQr5PY",1781792483475]