[{"data":1,"prerenderedAt":1625},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-processing-algorithm-help-and-metadata":3},{"id":4,"title":5,"body":6,"description":1614,"extension":1615,"meta":1616,"navigation":331,"path":1621,"seo":1622,"stem":1623,"__hash__":1624},"docs\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-processing-algorithm-help-and-metadata\u002Findex.md","Write Help and Metadata for a Processing Algorithm",{"type":7,"value":8,"toc":1599},"minimark",[9,13,26,35,244,249,279,283,286,498,521,532,536,541,604,626,632,636,639,881,898,1014,1018,1023,1056,1066,1070,1076,1119,1131,1135,1138,1251,1271,1275,1281,1291,1336,1345,1349,1355,1440,1444,1493,1497,1514,1518,1524,1542,1557,1563,1567,1595],[10,11,5],"h1",{"id":12},"write-help-and-metadata-for-a-processing-algorithm",[14,15,16,17,21,22,25],"p",{},"An algorithm that works and cannot be found is not much use. The Processing toolbox has a search box, a group tree, a help panel and per-parameter tooltips, and every one of them is filled from a method you override. Most custom algorithms implement ",[18,19,20],"code",{},"name()"," and ",[18,23,24],{},"displayName()",", leave the rest at their defaults, and end up as an untitled entry in a group called \"Scripts\" with an empty help panel.",[14,27,28,29,34],{},"This recipe belongs to ",[30,31,33],"a",{"href":32},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002F","Processing Provider Plugins",". It covers the identity and grouping methods, writing help that renders properly, per-parameter tooltips, tags that make search work, and keeping all of it translatable.",[14,36,37],{},[38,39,44,48,52,59,76,85,95,101,110,115,122,127,133,138,143,147,151,155,162,168,172,178,183,186,190,193,197,202,207,214,221,226,230,234,239],"svg",{"viewBox":40,"role":41,"ariaLabel":42,"xmlns":43},"0 0 760 320","img","Where each algorithm method appears in the Processing interface: the toolbox tree, the search results, the dialog title, the help panel and the parameter tooltips","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[45,46,47],"title",{},"Which method fills which part of the interface",[49,50,51],"desc",{},"Group and group id place the algorithm in the toolbox tree. Display name is its label and is searched. Tags add extra search terms that need not appear in the name. Short help string fills the help panel beside the parameters, and each parameter's help string becomes its tooltip.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","320","#f6f3ea",[60,61,62],"defs",{},[63,64,71],"marker",{"id":65,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"helpArrow","0 0 10 10","8","5","7","auto-start-reverse",[72,73],"path",{"d":74,"fill":75},"M0 0 L10 5 L0 10 z","#2f3b35",[77,78,84],"text",{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Five methods, five different places in the interface",[53,86],{"x":87,"y":88,"width":89,"height":90,"rx":91,"fill":92,"stroke":93,"style":94},"20","56","236","240","10","#fffdf7","#59645f","stroke-width:2",[77,96,100],{"x":97,"y":98,"style":99,"fill":82,"textAnchor":83},"138","82","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","the toolbox",[53,102],{"x":103,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":82,"style":109},"42","98","192","24","3","#e7e2d4","stroke-width:1.2",[77,111,114],{"x":88,"y":112,"style":113,"fill":75},"115","font-size:9.5px;font-family:sans-serif","▾ Northshire tools",[53,116],{"x":117,"y":118,"width":119,"height":106,"rx":107,"fill":120,"stroke":121,"style":109},"62","126","172","#dbeafe","#2563eb",[77,123,126],{"x":124,"y":125,"style":113,"fill":75},"76","143","▾ Catchments",[53,128],{"x":98,"y":129,"width":130,"height":106,"rx":107,"fill":131,"stroke":132,"style":109},"154","152","#e8efe6","#15803d",[77,134,137],{"x":135,"y":136,"style":113,"fill":75},"96","171","Buffer by value",[77,139,142],{"x":97,"y":140,"style":141,"fill":93,"textAnchor":83},"204","text-anchor:middle;font-size:9.5px;font-family:monospace","groupId() → tree",[77,144,146],{"x":97,"y":145,"style":141,"fill":93,"textAnchor":83},"224","group() → its label",[77,148,150],{"x":97,"y":149,"style":141,"fill":93,"textAnchor":83},"244","displayName() → the row",[77,152,154],{"x":97,"y":153,"style":141,"fill":93,"textAnchor":83},"272","tags() → extra search terms",[156,157],"line",{"x1":158,"y1":159,"x2":160,"y2":159,"stroke":75,"style":161},"262","170","290","stroke-width:2;marker-end:url(#helpArrow)",[53,163],{"x":164,"y":88,"width":165,"height":90,"rx":91,"fill":92,"stroke":166,"style":167},"298","438","#0f766e","stroke-width:2.5",[77,169,171],{"x":170,"y":98,"style":99,"fill":166,"textAnchor":83},"517","the algorithm dialog",[53,173],{"x":57,"y":104,"width":174,"height":175,"rx":107,"fill":176,"stroke":166,"style":177},"200","26","#eef7f4","stroke-width:1.4",[77,179,182],{"x":180,"y":181,"style":113,"fill":75},"334","116","Input layer  ▾",[53,184],{"x":57,"y":185,"width":174,"height":175,"rx":107,"fill":176,"stroke":166,"style":177},"130",[77,187,189],{"x":180,"y":188,"style":113,"fill":75},"148","Distance field  ▾",[53,191],{"x":57,"y":192,"width":174,"height":175,"rx":107,"fill":176,"stroke":166,"style":177},"162",[77,194,196],{"x":180,"y":195,"style":113,"fill":75},"180","Output  …",[77,198,201],{"x":199,"y":200,"style":141,"fill":93,"textAnchor":83},"420","212","each parameter's help()",[77,203,206],{"x":199,"y":204,"style":205,"fill":93,"textAnchor":83},"230","text-anchor:middle;font-size:9.5px;font-family:sans-serif","becomes its tooltip",[53,208],{"x":209,"y":104,"width":210,"height":211,"rx":212,"fill":131,"stroke":132,"style":213},"536","182","120","4","stroke-width:1.6",[77,215,220],{"x":216,"y":217,"style":218,"fill":219,"textAnchor":83},"627","122","text-anchor:middle;font-size:9.5px;font-weight:bold;font-family:sans-serif","#166534","Help",[77,222,225],{"x":216,"y":223,"style":224,"fill":75,"textAnchor":83},"144","text-anchor:middle;font-size:9px;font-family:sans-serif","what it does, what",[77,227,229],{"x":216,"y":228,"style":224,"fill":75,"textAnchor":83},"160","it assumes, and what",[77,231,233],{"x":216,"y":232,"style":224,"fill":75,"textAnchor":83},"176","it does not handle",[77,235,238],{"x":216,"y":236,"style":237,"fill":93,"textAnchor":83},"202","text-anchor:middle;font-size:9px;font-family:monospace","shortHelpString()",[77,240,243],{"x":170,"y":241,"style":242,"fill":93,"textAnchor":83},"266","text-anchor:middle;font-size:10px;font-family:sans-serif","an empty help panel is the default, and it shows",[245,246,248],"h2",{"id":247},"prerequisites","Prerequisites",[250,251,252,260,272],"ul",{},[253,254,255,259],"li",{},[256,257,258],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[253,261,262,263,266,267,271],{},"A ",[18,264,265],{},"QgsProcessingAlgorithm"," subclass — see ",[30,268,270],{"href":269},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002F","writing a custom processing algorithm",".",[253,273,274,275,271],{},"A provider to register it in; see ",[30,276,278],{"href":277},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fregister-processing-provider-in-plugin\u002F","registering a processing provider in a plugin",[245,280,282],{"id":281},"identity-and-grouping","Identity and grouping",[14,284,285],{},"Four methods decide where the algorithm lives and what it is called.",[287,288,293],"pre",{"className":289,"code":290,"language":291,"meta":292,"style":292},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProcessingAlgorithm\nfrom qgis.PyQt.QtCore import QCoreApplication\n\n\nclass BufferByValueAlgorithm(QgsProcessingAlgorithm):\n\n    def name(self):\n        return \"bufferbyvalue\"\n\n    def displayName(self):\n        return self.tr(\"Buffer by value\")\n\n    def group(self):\n        return self.tr(\"Catchments\")\n\n    def groupId(self):\n        return \"catchments\"\n\n    def tr(self, text):\n        return QCoreApplication.translate(\"BufferByValueAlgorithm\", text)\n","python","",[18,294,295,313,326,333,338,356,361,373,383,388,398,416,421,431,445,450,460,468,473,484],{"__ignoreMap":292},[296,297,299,303,307,310],"span",{"class":156,"line":298},1,[296,300,302],{"class":301},"snl16","from",[296,304,306],{"class":305},"s95oV"," qgis.core ",[296,308,309],{"class":301},"import",[296,311,312],{"class":305}," QgsProcessingAlgorithm\n",[296,314,316,318,321,323],{"class":156,"line":315},2,[296,317,302],{"class":301},[296,319,320],{"class":305}," qgis.PyQt.QtCore ",[296,322,309],{"class":301},[296,324,325],{"class":305}," QCoreApplication\n",[296,327,329],{"class":156,"line":328},3,[296,330,332],{"emptyLinePlaceholder":331},true,"\n",[296,334,336],{"class":156,"line":335},4,[296,337,332],{"emptyLinePlaceholder":331},[296,339,341,344,348,351,353],{"class":156,"line":340},5,[296,342,343],{"class":301},"class",[296,345,347],{"class":346},"svObZ"," BufferByValueAlgorithm",[296,349,350],{"class":305},"(",[296,352,265],{"class":346},[296,354,355],{"class":305},"):\n",[296,357,359],{"class":156,"line":358},6,[296,360,332],{"emptyLinePlaceholder":331},[296,362,364,367,370],{"class":156,"line":363},7,[296,365,366],{"class":301},"    def",[296,368,369],{"class":346}," name",[296,371,372],{"class":305},"(self):\n",[296,374,376,379],{"class":156,"line":375},8,[296,377,378],{"class":301},"        return",[296,380,382],{"class":381},"sU2Wk"," \"bufferbyvalue\"\n",[296,384,386],{"class":156,"line":385},9,[296,387,332],{"emptyLinePlaceholder":331},[296,389,391,393,396],{"class":156,"line":390},10,[296,392,366],{"class":301},[296,394,395],{"class":346}," displayName",[296,397,372],{"class":305},[296,399,401,403,407,410,413],{"class":156,"line":400},11,[296,402,378],{"class":301},[296,404,406],{"class":405},"sDLfK"," self",[296,408,409],{"class":305},".tr(",[296,411,412],{"class":381},"\"Buffer by value\"",[296,414,415],{"class":305},")\n",[296,417,419],{"class":156,"line":418},12,[296,420,332],{"emptyLinePlaceholder":331},[296,422,424,426,429],{"class":156,"line":423},13,[296,425,366],{"class":301},[296,427,428],{"class":346}," group",[296,430,372],{"class":305},[296,432,434,436,438,440,443],{"class":156,"line":433},14,[296,435,378],{"class":301},[296,437,406],{"class":405},[296,439,409],{"class":305},[296,441,442],{"class":381},"\"Catchments\"",[296,444,415],{"class":305},[296,446,448],{"class":156,"line":447},15,[296,449,332],{"emptyLinePlaceholder":331},[296,451,453,455,458],{"class":156,"line":452},16,[296,454,366],{"class":301},[296,456,457],{"class":346}," groupId",[296,459,372],{"class":305},[296,461,463,465],{"class":156,"line":462},17,[296,464,378],{"class":301},[296,466,467],{"class":381}," \"catchments\"\n",[296,469,471],{"class":156,"line":470},18,[296,472,332],{"emptyLinePlaceholder":331},[296,474,476,478,481],{"class":156,"line":475},19,[296,477,366],{"class":301},[296,479,480],{"class":346}," tr",[296,482,483],{"class":305},"(self, text):\n",[296,485,487,489,492,495],{"class":156,"line":486},20,[296,488,378],{"class":301},[296,490,491],{"class":305}," QCoreApplication.translate(",[296,493,494],{"class":381},"\"BufferByValueAlgorithm\"",[296,496,497],{"class":305},", text)\n",[14,499,500,503,504,506,507,510,511,513,514,21,517,520],{},[256,501,502],{},"Breakdown:"," ",[18,505,20],{}," is the machine identifier and must be lower-case with no spaces or punctuation — it becomes the second half of the algorithm id, ",[18,508,509],{},"myplugin:bufferbyvalue",", which appears in every script that calls it. Changing it later breaks those scripts silently, so it is worth choosing carefully once. ",[18,512,24],{}," is what humans see and is searched. ",[18,515,516],{},"group()",[18,518,519],{},"groupId()"," are the label and the identifier of the toolbox subfolder, and the id must be stable for the same reason the name must be.",[14,522,523,524,527,528,531],{},"The ",[18,525,526],{},"tr()"," helper is boilerplate every algorithm needs, and the string passed to ",[18,529,530],{},"QCoreApplication.translate()"," must be the class name for Qt Linguist to group the strings sensibly.",[245,533,535],{"id":534},"write-help-that-helps","Write help that helps",[14,537,538,540],{},[18,539,238],{}," fills the panel beside the parameters, and it accepts a subset of HTML.",[287,542,544],{"className":289,"code":543,"language":291,"meta":292,"style":292},"    def shortHelpString(self):\n        return self.tr(\n            \"\u003Cp>Buffers each input feature by the value of a numeric field, in \"\n            \"layer units.\u003C\u002Fp>\"\n            \"\u003Cp>Features whose field value is null or not positive are skipped and \"\n            \"reported as a warning. The output keeps all input attributes and adds \"\n            \"\u003Cb>buffer_m\u003C\u002Fb> recording the distance used.\u003C\u002Fp>\"\n            \"\u003Cp>\u003Cb>The input must be in a projected CRS.\u003C\u002Fb> In a geographic CRS the \"\n            \"distance is interpreted in degrees, which is almost never intended.\u003C\u002Fp>\"\n        )\n",[18,545,546,555,564,569,574,579,584,589,594,599],{"__ignoreMap":292},[296,547,548,550,553],{"class":156,"line":298},[296,549,366],{"class":301},[296,551,552],{"class":346}," shortHelpString",[296,554,372],{"class":305},[296,556,557,559,561],{"class":156,"line":315},[296,558,378],{"class":301},[296,560,406],{"class":405},[296,562,563],{"class":305},".tr(\n",[296,565,566],{"class":156,"line":328},[296,567,568],{"class":381},"            \"\u003Cp>Buffers each input feature by the value of a numeric field, in \"\n",[296,570,571],{"class":156,"line":335},[296,572,573],{"class":381},"            \"layer units.\u003C\u002Fp>\"\n",[296,575,576],{"class":156,"line":340},[296,577,578],{"class":381},"            \"\u003Cp>Features whose field value is null or not positive are skipped and \"\n",[296,580,581],{"class":156,"line":358},[296,582,583],{"class":381},"            \"reported as a warning. The output keeps all input attributes and adds \"\n",[296,585,586],{"class":156,"line":363},[296,587,588],{"class":381},"            \"\u003Cb>buffer_m\u003C\u002Fb> recording the distance used.\u003C\u002Fp>\"\n",[296,590,591],{"class":156,"line":375},[296,592,593],{"class":381},"            \"\u003Cp>\u003Cb>The input must be in a projected CRS.\u003C\u002Fb> In a geographic CRS the \"\n",[296,595,596],{"class":156,"line":385},[296,597,598],{"class":381},"            \"distance is interpreted in degrees, which is almost never intended.\u003C\u002Fp>\"\n",[296,600,601],{"class":156,"line":390},[296,602,603],{"class":305},"        )\n",[14,605,606,608,609,612,613,612,616,612,619,21,622,625],{},[256,607,502],{}," The panel renders ",[18,610,611],{},"\u003Cp>",", ",[18,614,615],{},"\u003Cb>",[18,617,618],{},"\u003Ci>",[18,620,621],{},"\u003Cul>",[18,623,624],{},"\u003Ca href>",", which is enough for readable help and not enough for anything elaborate. The three paragraphs cover what a user actually needs: what it does, what it does with awkward input, and the assumption that will otherwise waste their afternoon. Naming the added field explicitly saves a round trip; stating the CRS requirement in bold is worth more than three paragraphs of description, because it is the failure that produces plausible-looking wrong output.",[14,627,628,631],{},[18,629,630],{},"shortDescription()"," supplies the one-line summary that appears as a tooltip in the toolbox, and defaults to the first sentence of the help — worth overriding when that sentence is long.",[245,633,635],{"id":634},"help-per-parameter","Help per parameter",[14,637,638],{},"Each parameter can carry its own explanation, which becomes its tooltip.",[287,640,642],{"className":289,"code":641,"language":291,"meta":292,"style":292},"from qgis.core import (\n    QgsProcessingParameterFeatureSource, QgsProcessingParameterField,\n    QgsProcessingParameterFeatureSink, QgsProcessing,\n)\n\n\n    def initAlgorithm(self, config=None):\n        source = QgsProcessingParameterFeatureSource(\n            \"INPUT\", self.tr(\"Input layer\"), [QgsProcessing.TypeVectorAnyGeometry]\n        )\n        source.setHelp(self.tr(\"The features to buffer. Must be in a projected CRS.\"))\n        self.addParameter(source)\n\n        field = QgsProcessingParameterField(\n            \"FIELD\", self.tr(\"Distance field\"),\n            parentLayerParameterName=\"INPUT\",\n            type=QgsProcessingParameterField.Numeric,\n        )\n        field.setHelp(self.tr(\n            \"Numeric field holding the buffer distance in layer units. \"\n            \"Null or non-positive values cause the feature to be skipped.\"\n        ))\n        self.addParameter(field)\n\n        self.addParameter(\n            QgsProcessingParameterFeatureSink(\"OUTPUT\", self.tr(\"Buffered\"))\n        )\n",[18,643,644,655,660,665,669,673,677,695,705,723,727,742,750,754,764,781,795,805,809,818,823,829,835,843,848,856,876],{"__ignoreMap":292},[296,645,646,648,650,652],{"class":156,"line":298},[296,647,302],{"class":301},[296,649,306],{"class":305},[296,651,309],{"class":301},[296,653,654],{"class":305}," (\n",[296,656,657],{"class":156,"line":315},[296,658,659],{"class":305},"    QgsProcessingParameterFeatureSource, QgsProcessingParameterField,\n",[296,661,662],{"class":156,"line":328},[296,663,664],{"class":305},"    QgsProcessingParameterFeatureSink, QgsProcessing,\n",[296,666,667],{"class":156,"line":335},[296,668,415],{"class":305},[296,670,671],{"class":156,"line":340},[296,672,332],{"emptyLinePlaceholder":331},[296,674,675],{"class":156,"line":358},[296,676,332],{"emptyLinePlaceholder":331},[296,678,679,681,684,687,690,693],{"class":156,"line":363},[296,680,366],{"class":301},[296,682,683],{"class":346}," initAlgorithm",[296,685,686],{"class":305},"(self, config",[296,688,689],{"class":301},"=",[296,691,692],{"class":405},"None",[296,694,355],{"class":305},[296,696,697,700,702],{"class":156,"line":375},[296,698,699],{"class":305},"        source ",[296,701,689],{"class":301},[296,703,704],{"class":305}," QgsProcessingParameterFeatureSource(\n",[296,706,707,710,712,715,717,720],{"class":156,"line":385},[296,708,709],{"class":381},"            \"INPUT\"",[296,711,612],{"class":305},[296,713,714],{"class":405},"self",[296,716,409],{"class":305},[296,718,719],{"class":381},"\"Input layer\"",[296,721,722],{"class":305},"), [QgsProcessing.TypeVectorAnyGeometry]\n",[296,724,725],{"class":156,"line":390},[296,726,603],{"class":305},[296,728,729,732,734,736,739],{"class":156,"line":400},[296,730,731],{"class":305},"        source.setHelp(",[296,733,714],{"class":405},[296,735,409],{"class":305},[296,737,738],{"class":381},"\"The features to buffer. Must be in a projected CRS.\"",[296,740,741],{"class":305},"))\n",[296,743,744,747],{"class":156,"line":418},[296,745,746],{"class":405},"        self",[296,748,749],{"class":305},".addParameter(source)\n",[296,751,752],{"class":156,"line":423},[296,753,332],{"emptyLinePlaceholder":331},[296,755,756,759,761],{"class":156,"line":433},[296,757,758],{"class":305},"        field ",[296,760,689],{"class":301},[296,762,763],{"class":305}," QgsProcessingParameterField(\n",[296,765,766,769,771,773,775,778],{"class":156,"line":447},[296,767,768],{"class":381},"            \"FIELD\"",[296,770,612],{"class":305},[296,772,714],{"class":405},[296,774,409],{"class":305},[296,776,777],{"class":381},"\"Distance field\"",[296,779,780],{"class":305},"),\n",[296,782,783,787,789,792],{"class":156,"line":452},[296,784,786],{"class":785},"s9osk","            parentLayerParameterName",[296,788,689],{"class":301},[296,790,791],{"class":381},"\"INPUT\"",[296,793,794],{"class":305},",\n",[296,796,797,800,802],{"class":156,"line":462},[296,798,799],{"class":785},"            type",[296,801,689],{"class":301},[296,803,804],{"class":305},"QgsProcessingParameterField.Numeric,\n",[296,806,807],{"class":156,"line":470},[296,808,603],{"class":305},[296,810,811,814,816],{"class":156,"line":475},[296,812,813],{"class":305},"        field.setHelp(",[296,815,714],{"class":405},[296,817,563],{"class":305},[296,819,820],{"class":156,"line":486},[296,821,822],{"class":381},"            \"Numeric field holding the buffer distance in layer units. \"\n",[296,824,826],{"class":156,"line":825},21,[296,827,828],{"class":381},"            \"Null or non-positive values cause the feature to be skipped.\"\n",[296,830,832],{"class":156,"line":831},22,[296,833,834],{"class":305},"        ))\n",[296,836,838,840],{"class":156,"line":837},23,[296,839,746],{"class":405},[296,841,842],{"class":305},".addParameter(field)\n",[296,844,846],{"class":156,"line":845},24,[296,847,332],{"emptyLinePlaceholder":331},[296,849,851,853],{"class":156,"line":850},25,[296,852,746],{"class":405},[296,854,855],{"class":305},".addParameter(\n",[296,857,859,862,865,867,869,871,874],{"class":156,"line":858},26,[296,860,861],{"class":305},"            QgsProcessingParameterFeatureSink(",[296,863,864],{"class":381},"\"OUTPUT\"",[296,866,612],{"class":305},[296,868,714],{"class":405},[296,870,409],{"class":305},[296,872,873],{"class":381},"\"Buffered\"",[296,875,741],{"class":305},[296,877,879],{"class":156,"line":878},27,[296,880,603],{"class":305},[14,882,883,503,885,888,889,892,893,897],{},[256,884,502],{},[18,886,887],{},"setHelp()"," on a parameter is separate from the parameter's label and is what fills the tooltip and the generated documentation. ",[18,890,891],{},"parentLayerParameterName=\"INPUT\""," is what makes the field dropdown follow the chosen layer — the same linkage the ",[30,894,896],{"href":895},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgsmaplayercombobox-in-plugin-dialog-pyqgis\u002F","layer and field combo boxes"," provide in a hand-built dialog, obtained here for free. Stating the units and the null behaviour in the parameter help rather than only in the algorithm help puts the information where the user is looking when they need it.",[14,899,900],{},[38,901,904,907,910,913,920,923,928,933,939,943,946,950,953,957,960,964,968,973,977,981,983,986,989,993,998,1002,1005,1008,1011],{"viewBox":902,"role":41,"ariaLabel":903,"xmlns":43},"0 0 760 288","Search terms reaching an algorithm through its display name, its group and its tags, so a user searching for a synonym still finds it",[45,905,906],{},"Tags catch the words that are not in the name",[49,908,909],{},"The toolbox search matches against the display name, the group and the tags. A user searching for a synonym such as variable distance or catchment finds the algorithm only if that word appears in one of them, and tags are the place to put words that would clutter the name.",[53,911],{"x":55,"y":55,"width":56,"height":912,"fill":58},"288",[60,914,915],{},[63,916,918],{"id":917,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"tagArrow2",[72,919],{"d":74,"fill":75},[77,921,922],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"People search for what they want, not what you called it",[53,924],{"x":106,"y":925,"width":174,"height":195,"rx":91,"fill":926,"stroke":927,"style":167},"60","#fdf2e2","#b45309",[77,929,932],{"x":930,"y":931,"style":99,"fill":927,"textAnchor":83},"124","88","what users type",[53,934],{"x":935,"y":936,"width":937,"height":106,"rx":212,"fill":92,"stroke":927,"style":938},"46","104","156","stroke-width:1.3",[77,940,942],{"x":930,"y":941,"style":205,"fill":75,"textAnchor":83},"121","variable buffer",[53,944],{"x":935,"y":945,"width":937,"height":106,"rx":212,"fill":92,"stroke":927,"style":938},"134",[77,947,949],{"x":930,"y":948,"style":205,"fill":75,"textAnchor":83},"151","catchment",[53,951],{"x":935,"y":952,"width":937,"height":106,"rx":212,"fill":92,"stroke":927,"style":938},"164",[77,954,956],{"x":930,"y":955,"style":205,"fill":75,"textAnchor":83},"181","zone of influence",[53,958],{"x":935,"y":959,"width":937,"height":106,"rx":212,"fill":92,"stroke":927,"style":938},"194",[77,961,963],{"x":930,"y":962,"style":205,"fill":75,"textAnchor":83},"211","by attribute",[156,965],{"x1":204,"y1":966,"x2":241,"y2":966,"stroke":75,"style":967},"150","stroke-width:2;marker-end:url(#tagArrow2)",[53,969],{"x":970,"y":124,"width":971,"height":188,"rx":91,"fill":972,"stroke":121,"style":167},"274","226","#eff3ff",[77,974,976],{"x":975,"y":936,"style":99,"fill":121,"textAnchor":83},"387","what search reads",[77,978,24],{"x":975,"y":979,"style":980,"fill":75,"textAnchor":83},"132","text-anchor:middle;font-size:10px;font-family:monospace",[77,982,516],{"x":975,"y":937,"style":980,"fill":75,"textAnchor":83},[77,984,985],{"x":975,"y":195,"style":980,"fill":132,"textAnchor":83},"tags()",[77,987,988],{"x":975,"y":140,"style":205,"fill":93,"textAnchor":83},"all three, case-insensitively",[156,990],{"x1":991,"y1":966,"x2":992,"y2":966,"stroke":75,"style":967},"506","542",[53,994],{"x":995,"y":124,"width":996,"height":188,"rx":91,"fill":997,"stroke":132,"style":167},"550","186","#edf8e9",[77,999,1001],{"x":1000,"y":936,"style":99,"fill":132,"textAnchor":83},"643","tags to add",[77,1003,1004],{"x":1000,"y":979,"style":205,"fill":75,"textAnchor":83},"buffer, variable, distance,",[77,1006,1007],{"x":1000,"y":130,"style":205,"fill":75,"textAnchor":83},"catchment, influence,",[77,1009,1010],{"x":1000,"y":119,"style":205,"fill":75,"textAnchor":83},"attribute, proximity",[77,1012,1013],{"x":1000,"y":174,"style":205,"fill":132,"textAnchor":83},"synonyms, not keywords",[245,1015,1017],{"id":1016},"tags-so-search-finds-it","Tags, so search finds it",[14,1019,1020,1022],{},[18,1021,985],{}," supplies extra search terms that need not appear in any visible label.",[287,1024,1026],{"className":289,"code":1025,"language":291,"meta":292,"style":292},"    def tags(self):\n        return self.tr(\"buffer,variable,distance,catchment,influence,attribute,proximity\").split(\",\")\n",[18,1027,1028,1037],{"__ignoreMap":292},[296,1029,1030,1032,1035],{"class":156,"line":298},[296,1031,366],{"class":301},[296,1033,1034],{"class":346}," tags",[296,1036,372],{"class":305},[296,1038,1039,1041,1043,1045,1048,1051,1054],{"class":156,"line":315},[296,1040,378],{"class":301},[296,1042,406],{"class":405},[296,1044,409],{"class":305},[296,1046,1047],{"class":381},"\"buffer,variable,distance,catchment,influence,attribute,proximity\"",[296,1049,1050],{"class":305},").split(",[296,1052,1053],{"class":381},"\",\"",[296,1055,415],{"class":305},[14,1057,1058,1060,1061,1065],{},[256,1059,502],{}," Returning a list is required; translating one comma-separated string and splitting it is the idiom the built-in algorithms use, because it gives translators a single string rather than seven fragments with no context. The tags worth adding are ",[1062,1063,1064],"em",{},"synonyms a user might type"," — the vocabulary of the domain rather than of the implementation. Adding the words already in the display name is harmless and pointless; adding \"GIS\" or \"vector\" is noise that makes every search match everything.",[245,1067,1069],{"id":1068},"link-to-fuller-documentation","Link to fuller documentation",[14,1071,1072,1073,1075],{},"Two methods point the ",[1062,1074,220],{}," button at a real page.",[287,1077,1079],{"className":289,"code":1078,"language":291,"meta":292,"style":292},"    def helpUrl(self):\n        return \"https:\u002F\u002Fdocs.example.org\u002Fplugins\u002Fnorthshire\u002Fbuffer-by-value\u002F\"\n\n    def helpString(self):\n        return self.shortHelpString()\n",[18,1080,1081,1090,1097,1101,1110],{"__ignoreMap":292},[296,1082,1083,1085,1088],{"class":156,"line":298},[296,1084,366],{"class":301},[296,1086,1087],{"class":346}," helpUrl",[296,1089,372],{"class":305},[296,1091,1092,1094],{"class":156,"line":315},[296,1093,378],{"class":301},[296,1095,1096],{"class":381}," \"https:\u002F\u002Fdocs.example.org\u002Fplugins\u002Fnorthshire\u002Fbuffer-by-value\u002F\"\n",[296,1098,1099],{"class":156,"line":328},[296,1100,332],{"emptyLinePlaceholder":331},[296,1102,1103,1105,1108],{"class":156,"line":335},[296,1104,366],{"class":301},[296,1106,1107],{"class":346}," helpString",[296,1109,372],{"class":305},[296,1111,1112,1114,1116],{"class":156,"line":340},[296,1113,378],{"class":301},[296,1115,406],{"class":405},[296,1117,1118],{"class":305},".shortHelpString()\n",[14,1120,1121,503,1123,1126,1127,1130],{},[256,1122,502],{},[18,1124,1125],{},"helpUrl()"," is what the button opens; without it the button either does nothing or falls back to a generic page. Hosting the long-form documentation outside the plugin means it can be corrected without a release, and a page per algorithm is a reasonable amount of work once the plugin has a documentation site at all. ",[18,1128,1129],{},"helpString()"," is the older full-help method and returning the short help from it avoids maintaining two versions of the same text.",[245,1132,1134],{"id":1133},"icons-and-a-flag-or-two","Icons and a flag or two",[14,1136,1137],{},"Two small touches make the algorithm feel finished.",[287,1139,1141],{"className":289,"code":1140,"language":291,"meta":292,"style":292},"from qgis.PyQt.QtGui import QIcon\nimport os\n\n\n    def icon(self):\n        return QIcon(os.path.join(os.path.dirname(__file__), \"icons\", \"buffer.svg\"))\n\n    def flags(self):\n        return super().flags() | QgsProcessingAlgorithm.FlagNoThreading\n\n    def createInstance(self):\n        return BufferByValueAlgorithm()\n",[18,1142,1143,1155,1162,1166,1170,1179,1202,1206,1215,1231,1235,1244],{"__ignoreMap":292},[296,1144,1145,1147,1150,1152],{"class":156,"line":298},[296,1146,302],{"class":301},[296,1148,1149],{"class":305}," qgis.PyQt.QtGui ",[296,1151,309],{"class":301},[296,1153,1154],{"class":305}," QIcon\n",[296,1156,1157,1159],{"class":156,"line":315},[296,1158,309],{"class":301},[296,1160,1161],{"class":305}," os\n",[296,1163,1164],{"class":156,"line":328},[296,1165,332],{"emptyLinePlaceholder":331},[296,1167,1168],{"class":156,"line":335},[296,1169,332],{"emptyLinePlaceholder":331},[296,1171,1172,1174,1177],{"class":156,"line":340},[296,1173,366],{"class":301},[296,1175,1176],{"class":346}," icon",[296,1178,372],{"class":305},[296,1180,1181,1183,1186,1189,1192,1195,1197,1200],{"class":156,"line":358},[296,1182,378],{"class":301},[296,1184,1185],{"class":305}," QIcon(os.path.join(os.path.dirname(",[296,1187,1188],{"class":405},"__file__",[296,1190,1191],{"class":305},"), ",[296,1193,1194],{"class":381},"\"icons\"",[296,1196,612],{"class":305},[296,1198,1199],{"class":381},"\"buffer.svg\"",[296,1201,741],{"class":305},[296,1203,1204],{"class":156,"line":363},[296,1205,332],{"emptyLinePlaceholder":331},[296,1207,1208,1210,1213],{"class":156,"line":375},[296,1209,366],{"class":301},[296,1211,1212],{"class":346}," flags",[296,1214,372],{"class":305},[296,1216,1217,1219,1222,1225,1228],{"class":156,"line":385},[296,1218,378],{"class":301},[296,1220,1221],{"class":405}," super",[296,1223,1224],{"class":305},"().flags() ",[296,1226,1227],{"class":301},"|",[296,1229,1230],{"class":305}," QgsProcessingAlgorithm.FlagNoThreading\n",[296,1232,1233],{"class":156,"line":390},[296,1234,332],{"emptyLinePlaceholder":331},[296,1236,1237,1239,1242],{"class":156,"line":400},[296,1238,366],{"class":301},[296,1240,1241],{"class":346}," createInstance",[296,1243,372],{"class":305},[296,1245,1246,1248],{"class":156,"line":418},[296,1247,378],{"class":301},[296,1249,1250],{"class":305}," BufferByValueAlgorithm()\n",[14,1252,1253,503,1255,1258,1259,1262,1263,1266,1267,1270],{},[256,1254,502],{},[18,1256,1257],{},"createInstance()"," is not optional — Processing clones the algorithm for each run, and an implementation that returns anything other than a fresh instance produces state leaking between runs. ",[18,1260,1261],{},"FlagNoThreading"," tells Processing the algorithm must run on the main thread, which is required if it touches the interface or a layer's edit buffer and harmful otherwise, since it blocks the UI. Other flags worth knowing are ",[18,1264,1265],{},"FlagHideFromToolbox"," for an algorithm that exists only to be called from a model, and ",[18,1268,1269],{},"FlagSupportsBatch",", which is on by default.",[245,1272,1274],{"id":1273},"keep-it-translatable","Keep it translatable",[14,1276,1277,1278,1280],{},"Every user-visible string should pass through ",[18,1279,526],{},", and one detail decides whether Qt Linguist can find them.",[14,1282,1283,1284,1286,1287,1290],{},"The context string in ",[18,1285,530],{}," must match the class name, and the text passed must be a literal rather than an f-string — ",[18,1288,1289],{},"pylupdate5"," scans the source statically and cannot evaluate anything. Where a message needs a value interpolated, translate the template and format afterwards:",[287,1292,1294],{"className":289,"code":1293,"language":291,"meta":292,"style":292},"        feedback.pushWarning(\n            self.tr(\"Skipped {count} feature(s) with a null or non-positive distance.\")\n            .format(count=skipped)\n        )\n",[18,1295,1296,1301,1319,1332],{"__ignoreMap":292},[296,1297,1298],{"class":156,"line":298},[296,1299,1300],{"class":305},"        feedback.pushWarning(\n",[296,1302,1303,1306,1308,1311,1314,1317],{"class":156,"line":315},[296,1304,1305],{"class":405},"            self",[296,1307,409],{"class":305},[296,1309,1310],{"class":381},"\"Skipped ",[296,1312,1313],{"class":405},"{count}",[296,1315,1316],{"class":381}," feature(s) with a null or non-positive distance.\"",[296,1318,415],{"class":305},[296,1320,1321,1324,1327,1329],{"class":156,"line":328},[296,1322,1323],{"class":305},"            .format(",[296,1325,1326],{"class":785},"count",[296,1328,689],{"class":301},[296,1330,1331],{"class":305},"skipped)\n",[296,1333,1334],{"class":156,"line":335},[296,1335,603],{"class":305},[14,1337,1338,1340,1341,271],{},[256,1339,502],{}," Translating first and formatting second is what keeps the placeholder inside the translatable string, so a translator can move it to wherever their language needs it. Using a named placeholder rather than a positional one gives them a hint about what it holds. The full workflow for extracting and compiling these is in ",[30,1342,1344],{"href":1343},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Ftranslate-qgis-plugin-with-qt-linguist\u002F","translating a QGIS plugin with Qt Linguist",[245,1346,1348],{"id":1347},"qgis-version-compatibility","QGIS version compatibility",[14,1350,1351,1352,1354],{},"The examples target ",[256,1353,258],{}," (Python 3.12).",[1356,1357,1358,1374],"table",{},[1359,1360,1361],"thead",{},[1362,1363,1364,1368,1371],"tr",{},[1365,1366,1367],"th",{},"QGIS version",[1365,1369,1370],{},"Python",[1365,1372,1373],{},"Notes",[1375,1376,1377,1393,1406,1419,1430],"tbody",{},[1362,1378,1379,1383,1386],{},[1380,1381,1382],"td",{},"3.16 LTR",[1380,1384,1385],{},"3.7",[1380,1387,1388,1389,1392],{},"All methods present; ",[18,1390,1391],{},"setHelp"," on parameters available.",[1362,1394,1395,1398,1401],{},[1380,1396,1397],{},"3.22 LTR",[1380,1399,1400],{},"3.9",[1380,1402,1403,1405],{},[18,1404,630],{}," shown as the toolbox tooltip.",[1362,1407,1408,1411,1413],{},[1380,1409,1410],{},"3.28 LTR",[1380,1412,1400],{},[1380,1414,1415,1416,1418],{},"Algorithm flags gain additional members; ",[18,1417,1261],{}," unchanged.",[1362,1420,1421,1424,1427],{},[1380,1422,1423],{},"3.34 LTR",[1380,1425,1426],{},"3.12",[1380,1428,1429],{},"Baseline for this page.",[1362,1431,1432,1435,1437],{},[1380,1433,1434],{},"3.36+",[1380,1436,1426],{},[1380,1438,1439],{},"Flags gain a scoped enum form; the flat names remain as aliases.",[245,1441,1443],{"id":1442},"troubleshooting","Troubleshooting",[250,1445,1446,1456,1462,1470,1476,1487],{},[253,1447,1448,503,1451,21,1453,1455],{},[256,1449,1450],{},"The algorithm appears under \"Scripts\".",[18,1452,516],{},[18,1454,519],{}," were not overridden.",[253,1457,1458,1461],{},[256,1459,1460],{},"Search does not find it."," No tags, and the search term is not in the display name or group.",[253,1463,1464,503,1467,1469],{},[256,1465,1466],{},"The help panel is empty.",[18,1468,238],{}," was not implemented; the base class returns nothing.",[253,1471,1472,1475],{},[256,1473,1474],{},"HTML shows as literal text."," The panel accepts a subset — check for an unsupported tag, and remember unescaped angle brackets in prose break it.",[253,1477,1478,503,1481,1483,1484,1486],{},[256,1479,1480],{},"State leaks between runs.",[18,1482,1257],{}," returns ",[18,1485,714],{}," rather than a new instance.",[253,1488,1489,1492],{},[256,1490,1491],{},"Translations are missing."," The strings were not literals, or the context does not match the class name.",[245,1494,1496],{"id":1495},"conclusion","Conclusion",[14,1498,1499,1500,21,1502,1504,1505,1507,1508,1510,1511,1513],{},"Choose ",[18,1501,20],{},[18,1503,519],{}," once and never change them, write ",[18,1506,238],{}," covering what it does, what it does with awkward input, and its one dangerous assumption, put units and null behaviour in the per-parameter help, and add tags that are synonyms rather than keywords. Return a fresh object from ",[18,1509,1257],{},", and pass every visible string through ",[18,1512,526],{}," as a literal.",[245,1515,1517],{"id":1516},"frequently-asked-questions","Frequently Asked Questions",[14,1519,1520,1523],{},[256,1521,1522],{},"Where does the help appear in batch mode?","\nParameter help becomes the column tooltip in the batch dialog; the algorithm help is not shown there, which is another reason to put the important warnings in the parameter help.",[14,1525,1526,1533,1534,1537,1538,271],{},[256,1527,1528,1529,1532],{},"Does the help text appear in ",[18,1530,1531],{},"qgis_process","?","\nYes — ",[18,1535,1536],{},"qgis_process help \u003Cid>"," prints the algorithm help and the parameter descriptions, which makes them the documentation for anyone using the ",[30,1539,1541],{"href":1540},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fuse-qgis-process-command-line-runner\u002F","command-line runner",[14,1543,1544,1547,1548,612,1550,1552,1553,1556],{},[256,1545,1546],{},"Can I generate documentation from these methods?","\nYes. Iterating the provider's algorithms and reading ",[18,1549,24],{},[18,1551,238],{}," and each parameter's ",[18,1554,1555],{},"help()"," produces a documentation page per algorithm that cannot drift from the code.",[14,1558,1559,1562],{},[256,1560,1561],{},"Should the group match the plugin name?","\nNo — the provider already supplies the top-level name. Groups should divide the plugin's algorithms by what they do, which is what makes a provider with twenty algorithms navigable.",[245,1564,1566],{"id":1565},"related","Related",[250,1568,1569,1574,1579,1585,1590],{},[253,1570,1571,1573],{},[30,1572,33],{"href":32}," — the guide this recipe belongs to",[253,1575,1576],{},[30,1577,1578],{"href":269},"Write a Custom Processing Algorithm in PyQGIS",[253,1580,1581],{},[30,1582,1584],{"href":1583},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fadd-parameters-to-processing-algorithm-pyqgis\u002F","Add Parameters to a Processing Algorithm in PyQGIS",[253,1586,1587],{},[30,1588,1589],{"href":277},"Register a Processing Provider in a Plugin",[253,1591,1592],{},[30,1593,1594],{"href":1343},"Translate a QGIS Plugin with Qt Linguist",[1596,1597,1598],"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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":292,"searchDepth":315,"depth":315,"links":1600},[1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613],{"id":247,"depth":315,"text":248},{"id":281,"depth":315,"text":282},{"id":534,"depth":315,"text":535},{"id":634,"depth":315,"text":635},{"id":1016,"depth":315,"text":1017},{"id":1068,"depth":315,"text":1069},{"id":1133,"depth":315,"text":1134},{"id":1273,"depth":315,"text":1274},{"id":1347,"depth":315,"text":1348},{"id":1442,"depth":315,"text":1443},{"id":1495,"depth":315,"text":1496},{"id":1516,"depth":315,"text":1517},{"id":1565,"depth":315,"text":1566},"Make a custom QGIS algorithm discoverable and self-explaining — shortHelpString, per-parameter help, tags and groups, help URLs, and keeping the text translatable.","md",{"slug":1617,"type":1618,"breadcrumb":1619,"datePublished":1620,"dateModified":1620},"write-processing-algorithm-help-and-metadata","article","Algorithm Help & Metadata","2026-08-27","\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-processing-algorithm-help-and-metadata",{"title":5,"description":1614},"qgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-processing-algorithm-help-and-metadata\u002Findex","LvTCuAyGlMiouTASYuWIjiKaCUbLEhw_neDi1oX9ZwU",1787823363077]