Creating a Reusable PyQGIS Processing Algorithm Template
To build a production-grade PyQGIS processing algorithm, subclass QgsProcessingAlgorithm, implement the mandatory metadata and I/O methods, and register the…
To build a production-grade PyQGIS processing algorithm, subclass QgsProcessingAlgorithm, implement the mandatory metadata and I/O methods, and register the instance with the QGIS processing framework. The architecture must strictly separate execution logic from UI rendering, route progress and cancellation signals through QgsProcessingFeedback, and validate inputs before touching the feature iterator. Below is a complete, QGIS 3.x-compatible template that handles parameter definition, safe feature iteration, output sink creation, and graceful error recovery. You can drop this directly into a custom plugin provider or a standalone automation script.
Production-Ready Template Code
from qgis.core import (
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorDestination,
QgsProcessingParameterNumber,
QgsProcessingException,
QgsProcessingFeedback,
QgsFeatureSink,
QgsFeature,
QgsProcessing,
)
class ReusableTemplateAlgorithm(QgsProcessingAlgorithm):
# --- Parameter Keys ---
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
THRESHOLD = 'THRESHOLD'
def name(self):
return 'reusable_template'
def displayName(self):
return 'Reusable Processing Template'
def group(self):
return 'Custom Automation'
def groupId(self):
return 'custom_automation'
def shortHelpString(self):
return "A production-ready template for building custom PyQGIS processing algorithms. " \
"See [Building Custom Processing Algorithms](/plugin-development-ui-integration/building-custom-processing-algorithms/) for advanced parameter chaining and model integration."
def createInstance(self):
return ReusableTemplateAlgorithm()
# --- I/O Definition ---
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
'Input Vector Layer',
[QgsProcessing.TypeVectorAnyGeometry]
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.THRESHOLD,
'Processing Threshold',
type=QgsProcessingParameterNumber.Double,
defaultValue=0.5,
minValue=0.0,
maxValue=1.0
)
)
self.addParameter(
QgsProcessingParameterVectorDestination(
self.OUTPUT,
'Output Vector Layer',
type=QgsProcessing.TypeVectorAnyGeometry
)
)
# --- Execution ---
def processAlgorithm(self, parameters, context, feedback):
# 1. Validate & resolve input source
source = self.parameterAsSource(parameters, self.INPUT, context)
if source is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
# 2. Resolve threshold
threshold = self.parameterAsDouble(parameters, self.THRESHOLD, context)
# 3. Prepare output sink (inherits schema, geometry type, and CRS from input)
(sink, dest_id) = self.parameterAsSink(
parameters, self.OUTPUT, context,
source.fields(), source.wkbType(), source.sourceCrs()
)
if sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
# 4. Iterate with progress & cancellation handling
total = 100.0 / source.featureCount() if source.featureCount() else 0
features = source.getFeatures()
processed_count = 0
for current, feature in enumerate(features):
if feedback.isCanceled():
break
# Example business logic: filter by threshold or pass-through
# Replace with your actual spatial/attribute operations
if threshold > 0.0:
# Simulate conditional processing
pass
new_feature = QgsFeature()
new_feature.setGeometry(feature.geometry())
new_feature.setAttributes(feature.attributes())
sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
processed_count += 1
feedback.setProgress(current * total)
feedback.pushInfo(f"Successfully processed {processed_count} features.")
return {self.OUTPUT: dest_id}
flowchart TD
A["processAlgorithm()"] --> B["parameterAsSource() validate input"]
B --> C["parameterAsSink() create output"]
C --> D{"Next feature?"}
D -->|"yes"| E{"feedback.isCanceled()?"}
E -->|"yes"| Z["Break early"]
E -->|"no"| F["sink.addFeature(); setProgress()"]
F --> D
D -->|"no"| G["return result dict"]
Z --> G
Architecture Breakdown
Metadata & Instantiation
The name(), displayName(), group(), and groupId() methods define how the algorithm appears in the Processing Toolbox. QGIS uses these strings for localization, search indexing, and UI grouping. The createInstance() method is mandatory; the framework calls it to spawn fresh algorithm instances for each execution, preventing state leakage between runs.
Parameter Definition (I/O)
initAlgorithm() declares all inputs and outputs. Use QgsProcessingParameterFeatureSource for flexible layer acceptance and QgsProcessingParameterVectorDestination for memory-backed or file-backed outputs. The framework automatically handles file dialogs, layer dropdowns, and temporary storage routing. For deeper UI customization, consult the Plugin Development & UI Integration documentation to understand how processing providers bind to custom dialogs and batch interfaces.
Execution & Feedback Loop
processAlgorithm() is the core worker. It receives a parameters dictionary, a context object (containing project, transform, and temporary path settings), and a feedback object. Always:
- Resolve sources/sinks using
parameterAsSource()andparameterAsSink(). - Check for
Nonereturns and raiseQgsProcessingExceptionwithinvalidSourceError()orinvalidSinkError(). - Loop through
source.getFeatures(), checkingfeedback.isCanceled()every iteration. - Call
feedback.setProgress()with a 0–100 float to update the progress bar. - Return a dictionary mapping output keys to their resolved IDs.
This pattern aligns with the official QGIS PyQGIS Developer Cookbook, which emphasizes non-blocking execution and explicit sink management.
Registration & Integration
To make the algorithm discoverable, register it with the QGIS processing registry. In a plugin, override initGui() or your provider’s loadAlgorithms() method:
from qgis.core import QgsApplication, QgsProcessingProvider
class CustomProvider(QgsProcessingProvider):
def loadAlgorithms(self):
self.addAlgorithm(ReusableTemplateAlgorithm())
def id(self):
return "custom_automation_provider"
def name(self):
return "Custom Automation Provider"
# In your plugin's __init__.py or initGui():
provider = CustomProvider()
QgsApplication.processingRegistry().addProvider(provider)
For standalone scripts (outside QGIS Desktop), initialize the application context first:
from qgis.core import QgsApplication
QgsApplication.setPrefixPath("/usr", True) # Adjust to your QGIS install path
qgs = QgsApplication([], False)
qgs.initQgis()
# Register provider, run algorithm via QgsProcessing.runAlgorithm()
Production Best Practices
- Never mutate input layers in-place. Always write to a sink. The processing framework expects deterministic, side-effect-free execution.
- Use
QgsFeatureSink.FastInsertwhen adding features. It bypasses unnecessary geometry validation and accelerates bulk writes by 30–50%. - Handle CRS transformations explicitly. If your algorithm requires a specific projection, use
QgsCoordinateTransforminside the loop or request reprojected inputs viaQgsProcessingParameterFeatureSourceflags. - Log with
feedback.pushInfo()andfeedback.pushWarning()instead ofprint(). The Processing UI captures these streams and surfaces them in the algorithm log panel. - Keep algorithms stateless. Store configuration in project variables or layer metadata, not class attributes. This prevents race conditions when running batch jobs or parallel models.
- Test with
QgsProcessingFeedbackmocks. The QGIS API Reference documents all required method signatures. Mock the feedback object to verify progress reporting and cancellation behavior before UI deployment.
By following this structure, you ensure compatibility with QGIS 3.x modeler integration, batch processing, and headless automation pipelines.