Write Help and Metadata for a Processing Algorithm
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 name() and displayName(), leave the rest at their defaults, and end up as an untitled entry in a group called "Scripts" with an empty help panel.
This recipe belongs to 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.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- A
QgsProcessingAlgorithmsubclass — see writing a custom processing algorithm. - A provider to register it in; see registering a processing provider in a plugin.
Identity and grouping
Four methods decide where the algorithm lives and what it is called.
from qgis.core import QgsProcessingAlgorithm
from qgis.PyQt.QtCore import QCoreApplication
class BufferByValueAlgorithm(QgsProcessingAlgorithm):
def name(self):
return "bufferbyvalue"
def displayName(self):
return self.tr("Buffer by value")
def group(self):
return self.tr("Catchments")
def groupId(self):
return "catchments"
def tr(self, text):
return QCoreApplication.translate("BufferByValueAlgorithm", text)
Breakdown: name() is the machine identifier and must be lower-case with no spaces or punctuation — it becomes the second half of the algorithm id, myplugin:bufferbyvalue, which appears in every script that calls it. Changing it later breaks those scripts silently, so it is worth choosing carefully once. displayName() is what humans see and is searched. group() and 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.
The tr() helper is boilerplate every algorithm needs, and the string passed to QCoreApplication.translate() must be the class name for Qt Linguist to group the strings sensibly.
Write help that helps
shortHelpString() fills the panel beside the parameters, and it accepts a subset of HTML.
def shortHelpString(self):
return self.tr(
"<p>Buffers each input feature by the value of a numeric field, in "
"layer units.</p>"
"<p>Features whose field value is null or not positive are skipped and "
"reported as a warning. The output keeps all input attributes and adds "
"<b>buffer_m</b> recording the distance used.</p>"
"<p><b>The input must be in a projected CRS.</b> In a geographic CRS the "
"distance is interpreted in degrees, which is almost never intended.</p>"
)
Breakdown: The panel renders <p>, <b>, <i>, <ul> and <a 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.
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.
Help per parameter
Each parameter can carry its own explanation, which becomes its tooltip.
from qgis.core import (
QgsProcessingParameterFeatureSource, QgsProcessingParameterField,
QgsProcessingParameterFeatureSink, QgsProcessing,
)
def initAlgorithm(self, config=None):
source = QgsProcessingParameterFeatureSource(
"INPUT", self.tr("Input layer"), [QgsProcessing.TypeVectorAnyGeometry]
)
source.setHelp(self.tr("The features to buffer. Must be in a projected CRS."))
self.addParameter(source)
field = QgsProcessingParameterField(
"FIELD", self.tr("Distance field"),
parentLayerParameterName="INPUT",
type=QgsProcessingParameterField.Numeric,
)
field.setHelp(self.tr(
"Numeric field holding the buffer distance in layer units. "
"Null or non-positive values cause the feature to be skipped."
))
self.addParameter(field)
self.addParameter(
QgsProcessingParameterFeatureSink("OUTPUT", self.tr("Buffered"))
)
Breakdown: setHelp() on a parameter is separate from the parameter's label and is what fills the tooltip and the generated documentation. parentLayerParameterName="INPUT" is what makes the field dropdown follow the chosen layer — the same linkage the 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.
Tags, so search finds it
tags() supplies extra search terms that need not appear in any visible label.
def tags(self):
return self.tr("buffer,variable,distance,catchment,influence,attribute,proximity").split(",")
Breakdown: 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 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.
Link to fuller documentation
Two methods point the Help button at a real page.
def helpUrl(self):
return "https://docs.example.org/plugins/northshire/buffer-by-value/"
def helpString(self):
return self.shortHelpString()
Breakdown: 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. helpString() is the older full-help method and returning the short help from it avoids maintaining two versions of the same text.
Icons and a flag or two
Two small touches make the algorithm feel finished.
from qgis.PyQt.QtGui import QIcon
import os
def icon(self):
return QIcon(os.path.join(os.path.dirname(__file__), "icons", "buffer.svg"))
def flags(self):
return super().flags() | QgsProcessingAlgorithm.FlagNoThreading
def createInstance(self):
return BufferByValueAlgorithm()
Breakdown: 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. 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 FlagHideFromToolbox for an algorithm that exists only to be called from a model, and FlagSupportsBatch, which is on by default.
Keep it translatable
Every user-visible string should pass through tr(), and one detail decides whether Qt Linguist can find them.
The context string in QCoreApplication.translate() must match the class name, and the text passed must be a literal rather than an f-string — pylupdate5 scans the source statically and cannot evaluate anything. Where a message needs a value interpolated, translate the template and format afterwards:
feedback.pushWarning(
self.tr("Skipped {count} feature(s) with a null or non-positive distance.")
.format(count=skipped)
)
Breakdown: 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 translating a QGIS plugin with Qt Linguist.
QGIS version compatibility
The examples target QGIS 3.34 LTR (Python 3.12).
| QGIS version | Python | Notes |
|---|---|---|
| 3.16 LTR | 3.7 | All methods present; setHelp on parameters available. |
| 3.22 LTR | 3.9 | shortDescription() shown as the toolbox tooltip. |
| 3.28 LTR | 3.9 | Algorithm flags gain additional members; FlagNoThreading unchanged. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.36+ | 3.12 | Flags gain a scoped enum form; the flat names remain as aliases. |
Troubleshooting
- The algorithm appears under "Scripts".
group()andgroupId()were not overridden. - Search does not find it. No tags, and the search term is not in the display name or group.
- The help panel is empty.
shortHelpString()was not implemented; the base class returns nothing. - HTML shows as literal text. The panel accepts a subset — check for an unsupported tag, and remember unescaped angle brackets in prose break it.
- State leaks between runs.
createInstance()returnsselfrather than a new instance. - Translations are missing. The strings were not literals, or the context does not match the class name.
Conclusion
Choose name() and groupId() once and never change them, write shortHelpString() 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 createInstance(), and pass every visible string through tr() as a literal.
Frequently Asked Questions
Where does the help appear in batch mode? Parameter 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.
Does the help text appear in qgis_process?
Yes — qgis_process help <id> prints the algorithm help and the parameter descriptions, which makes them the documentation for anyone using the command-line runner.
Can I generate documentation from these methods?
Yes. Iterating the provider's algorithms and reading displayName(), shortHelpString() and each parameter's help() produces a documentation page per algorithm that cannot drift from the code.
Should the group match the plugin name? No — 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.