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.

Which method fills which part of the interfaceGroup 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.Five methods, five different places in the interfacethe toolbox▾ Northshire tools▾ CatchmentsBuffer by valuegroupId() → treegroup() → its labeldisplayName() → the rowtags() → extra search termsthe algorithm dialogInput layer ▾Distance field ▾Output …each parameter's help()becomes its tooltipHelpwhat it does, whatit assumes, and whatit does not handleshortHelpString()an empty help panel is the default, and it shows

Prerequisites

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 catch the words that are not in the nameThe 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.People search for what they want, not what you called itwhat users typevariable buffercatchmentzone of influenceby attributewhat search readsdisplayName()group()tags()all three, case-insensitivelytags to addbuffer, variable, distance,catchment, influence,attribute, proximitysynonyms, not keywords

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.

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 versionPythonNotes
3.16 LTR3.7All methods present; setHelp on parameters available.
3.22 LTR3.9shortDescription() shown as the toolbox tooltip.
3.28 LTR3.9Algorithm flags gain additional members; FlagNoThreading unchanged.
3.34 LTR3.12Baseline for this page.
3.36+3.12Flags gain a scoped enum form; the flat names remain as aliases.

Troubleshooting

  • The algorithm appears under "Scripts". group() and groupId() 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() returns self rather 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.