How to Run QGIS Processing Algorithms from Python
The Processing toolbox is the best part of QGIS and the least documented from Python. Behind every dialog is a string id and a dictionary, and once you can produce those two things reliably you can script anything the toolbox can do β including the GDAL, GRASS, and SAGA algorithms that arrive with it. The trick is not memorising parameters. It is knowing the four places QGIS will tell you what they are.
Problem statement
You want to replace a sequence of toolbox clicks with a script, and you hit the same wall each time:
- You do not know the algorithm id. The dialog says "Buffer"; the call needs
native:buffer, and there is alsogdal:buffervectorsand a legacyqgis:alias that behave differently. - You do not know the parameter names.
DISTANCEorBUFFER_DISTANCE?INPUTorINPUT_LAYER? A wrong key is silently ignored or raises an unhelpful exception. - The algorithm "is not found".
processing.runraisesQgsProcessingExceptionin a standalone script that works fine in the console. - The output is a string, and you wanted a layer. Or it is a layer and you wanted a file, or it is a temporary thing that vanished.
- Errors are invisible. A run half-completes and the reason sits in a log panel you cannot see from a scheduled job.
The goal: a call you can copy from a real run, verify before executing, chain into the next algorithm, and log properly when it fails.
Quick answer
Every Processing call has the same three parts β an id, a parameter dictionary, and a results dictionary.
import processing
result = processing.run("native:buffer", {
"INPUT": "data/parcels.gpkg|layername=parcels",
"DISTANCE": 25,
"SEGMENTS": 8,
"END_CAP_STYLE": 0,
"JOIN_STYLE": 0,
"MITER_LIMIT": 2,
"DISSOLVE": False,
"OUTPUT": "out/parcels_buffer.gpkg",
})
print(result["OUTPUT"]) # 'out/parcels_buffer.gpkg'
To find the id and the parameter names for any algorithm, run it once from the toolbox and read Processing β History, or ask QGIS directly:
processing.algorithmHelp("native:buffer")
In a standalone script the framework needs initialising first β see running PyQGIS headless and the PyQGIS overview.
Step-by-step solution
Find the algorithm id
Four routes, in the order you will actually use them.
The History panel. Run the algorithm once from the toolbox with real inputs, then open Processing β History. QGIS records the exact processing.run(...) call it executed, id and full parameter dictionary included. Copy, paste, edit the paths. This is the single fastest route and it is correct by construction.
algorithmHelp. From the Python Console or a script:
import processing
processing.algorithmHelp("native:buffer")
It prints every parameter with its type, whether it is optional, and the accepted enum values β including the numbers behind END_CAP_STYLE and JOIN_STYLE, which are otherwise pure folklore.
Listing everything. When you know the word but not the id:
from qgis.core import QgsApplication
for alg in QgsApplication.processingRegistry().algorithms():
if "buffer" in alg.displayName().lower():
print(alg.id(), "β", alg.displayName())
The command line. qgis_process list prints every algorithm on the system, and qgis_process help native:buffer prints the same detail as algorithmHelp without starting Python at all.
Understand the id namespaces
The prefix before the colon is the provider, and it tells you what you are getting.
| Prefix | Provider | Notes |
|---|---|---|
native: |
QGIS core, C++ | Fastest, best maintained β prefer these |
qgis: |
QGIS core, legacy Python | Many are aliases of a native: algorithm; some are unique |
gdal: |
GDAL/OGR command wrappers | Shell out to ogr2ogr, gdalwarp etc. β great for rasters and format conversion |
grass: / saga: |
External providers | Powerful, but only if that provider is installed and configured |
model: |
Your saved Processing models | See building a model and running it from Python |
script: |
Your own script algorithms | See writing a custom script algorithm |
Automation should prefer native: where it exists. A grass: call in a scheduled job means the job now depends on GRASS being installed and correctly configured on that machine, which is a real deployment cost.
Pass inputs in whatever form you have
The INPUT parameter is forgiving in a genuinely useful way. All of these work:
processing.run("native:buffer", {"INPUT": "data/parcels.gpkg", ...}) # a path
processing.run("native:buffer", {"INPUT": "data/parcels.gpkg|layername=parcels", ...}) # a path + layer
processing.run("native:buffer", {"INPUT": layer, ...}) # a QgsVectorLayer
processing.run("native:buffer", {"INPUT": previous["OUTPUT"], ...}) # an earlier result
The |layername= suffix matters for multi-layer containers like GeoPackage: without it you get the first layer, which is rarely what you meant and never what you meant on the day it changes.
To operate on a subset without writing an intermediate file, wrap the layer in a QgsProcessingFeatureSourceDefinition with a selection, or filter first with native:extractbyexpression β the second is clearer and works headlessly, where there is no selection to speak of.
Choose where the output goes
"TEMPORARY_OUTPUT"β a scratch layer that lives as long as the process. Use it for every intermediate step. The result is a layer id or path you pass straight into the next algorithm."memory:"β an in-memory layer, similar in effect;TEMPORARY_OUTPUTis the current idiom and behaves better with algorithms that need a real sink."out/result.gpkg"β a real file. The driver comes from the extension, so.gpkg,.geojson,.shp, and.fgball just work."ogr:dbname='β¦' table=\"β¦\""β a database sink, for writing straight into PostGIS.
Chain intermediates in memory and write once at the end. A four-step workflow that writes four GeoPackages spends most of its time in I/O for files nobody reads.
Read the results dictionary properly
processing.run returns a dict, and the key is the output parameter name β usually but not always OUTPUT.
result = processing.run("native:joinattributesbylocation", {...})
print(result.keys()) # dict_keys(['OUTPUT', 'JOINED_COUNT', 'UNJOINABLE_COUNT'])
Several algorithms return extra values worth logging: counts, statistics, or a second sink for the non-matching features. native:checkvalidity returns three (VALID_OUTPUT, INVALID_OUTPUT, ERROR_OUTPUT), and using only the first quietly discards the diagnosis.
What you get inside those keys depends on the output target. With a file path you get the path back as a string. With TEMPORARY_OUTPUT you get an identifier that other algorithms accept but that is not a layer object. When you need a real layer, use processing.runAndLoadResults in the desktop, or construct it yourself:
from qgis.core import QgsVectorLayer
out = processing.run("native:buffer", {..., "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
layer = out if isinstance(out, QgsVectorLayer) else QgsVectorLayer(out, "buffered", "ogr")
Capture feedback and errors
By default, warnings and progress go to a place a scheduled job cannot see. Pass a feedback object and route it into logging.
import logging
from qgis.core import QgsProcessingFeedback
log = logging.getLogger("processing")
class LogFeedback(QgsProcessingFeedback):
def pushInfo(self, info): log.info(info)
def pushWarning(self, warning): log.warning(warning)
def reportError(self, error, fatalError=False):
log.error(error)
def setProgressText(self, text): log.info(text)
processing.run("native:buffer", {...}, feedback=LogFeedback())
Now a run that half-fails leaves a trace. Combine it with alerting on failure and an overnight job stops being a black box.
Validate before you run
For anything long, check the parameters before spending the time:
from qgis.core import QgsApplication, QgsProcessingContext
alg = QgsApplication.processingRegistry().algorithmById("native:buffer")
params = {"INPUT": "data/parcels.gpkg", "DISTANCE": 25, "OUTPUT": "TEMPORARY_OUTPUT"}
ok, message = alg.checkParameterValues(params, QgsProcessingContext())
if not ok:
raise ValueError(f"bad parameters: {message}")
This is the Processing equivalent of validating pipeline inputs before the run starts β a second of checking against twenty minutes of wasted work.
Code examples
Example 1: A three-algorithm chain with no intermediate files
import processing
parcels = "data/parcels.gpkg|layername=parcels"
reprojected = processing.run("native:reprojectlayer", {
"INPUT": parcels,
"TARGET_CRS": "EPSG:27700",
"OUTPUT": "TEMPORARY_OUTPUT",
})["OUTPUT"]
valid = processing.run("native:fixgeometries", {
"INPUT": reprojected,
"OUTPUT": "TEMPORARY_OUTPUT",
})["OUTPUT"]
final = processing.run("native:clip", {
"INPUT": valid,
"OVERLAY": "data/district.geojson",
"OUTPUT": "out/parcels_clean.gpkg",
})["OUTPUT"]
print("wrote", final)
Reproject, repair, clip β one file written. Note the order: fixing geometries after reprojection catches the invalidities that reprojection itself can introduce.
Example 2: A thin wrapper that logs and times every call
import logging
import time
import processing
log = logging.getLogger("alg")
def run(alg_id, params, **kwargs):
started = time.perf_counter()
log.info("β %s", alg_id)
try:
result = processing.run(alg_id, params, **kwargs)
except Exception as exc:
log.error("β %s failed after %.1fs: %s", alg_id, time.perf_counter() - started, exc)
raise
log.info("β %s in %.1fs", alg_id, time.perf_counter() - started)
return result
Swap processing.run for run throughout a script and you get a timed, labelled trace of the whole workflow for four lines of cost. It also gives you one place to add retries β the pattern from adding retries and timeouts drops straight in.
Example 3: Discovering enum values instead of guessing them
from qgis.core import QgsApplication, QgsProcessingParameterEnum
alg = QgsApplication.processingRegistry().algorithmById("native:buffer")
for p in alg.parameterDefinitions():
line = f"{p.name():<16} {p.type():<12} {'optional' if p.flags() else ''}"
if isinstance(p, QgsProcessingParameterEnum):
line += " options=" + ", ".join(f"{i}={o}" for i, o in enumerate(p.options()))
print(line)
This is how you learn that END_CAP_STYLE is 0=Round, 1=Flat, 2=Square rather than copying a magic number from a forum post.
Example 4: Raster work through the GDAL provider
Vector algorithms are mostly native:; raster work often lives under gdal:.
processing.run("gdal:warpreproject", {
"INPUT": "data/dem.tif",
"SOURCE_CRS": None,
"TARGET_CRS": "EPSG:27700",
"RESAMPLING": 1, # 0=nearest, 1=bilinear, 2=cubic β¦
"NODATA": -9999,
"TARGET_RESOLUTION": 10,
"OPTIONS": "COMPRESS=DEFLATE|PREDICTOR=2|TILED=YES",
"DATA_TYPE": 0,
"OUTPUT": "out/dem_bng.tif",
})
processing.run("native:zonalstatisticsfb", {
"INPUT": "out/parcels_clean.gpkg",
"INPUT_RASTER": "out/dem_bng.tif",
"RASTER_BAND": 1,
"COLUMN_PREFIX": "elev_",
"STATISTICS": [2, 5, 6], # mean, min, max
"OUTPUT": "out/parcels_elevation.gpkg",
})
The OPTIONS string is passed to GDAL creation options verbatim β the same knobs you would use from the command line, which makes rasterio knowledge transfer directly.
Example 5: A config-driven step list
Because a call is just an id and a dictionary, a workflow is just a list of them β and a list is data you can put in a config file.
import processing
STEPS = [
("native:reprojectlayer", {"TARGET_CRS": "EPSG:27700"}),
("native:fixgeometries", {}),
("native:buffer", {"DISTANCE": 25, "SEGMENTS": 8, "DISSOLVE": False}),
]
def apply_all(source, steps, final_output):
current = source
for i, (alg_id, params) in enumerate(steps):
is_last = i == len(steps) - 1
result = processing.run(alg_id, {
**params,
"INPUT": current,
"OUTPUT": final_output if is_last else "TEMPORARY_OUTPUT",
})
current = result["OUTPUT"]
return current
apply_all("data/parcels.gpkg|layername=parcels", STEPS, "out/parcels_final.gpkg")
That is a Processing-flavoured version of the step registry from chaining GIS processing steps, and it accepts a YAML config without further work.
Explanation
Processing is a framework, not a library of functions. An algorithm declares its parameters as objects β this one is a feature source, that one is a number with a minimum of zero, this one is an enum with four options β and the framework uses those declarations to build the dialog, to validate input, to expose the algorithm in the model builder, and to accept a dictionary from Python. That single declaration is why the same algorithm works identically from a toolbox click, a model, qgis_process, and your script. It is also why the parameter names are stable enough to script against: they are part of the algorithm's public definition, not an implementation detail.
The consequence for automation is that processing.run is a genuinely thin surface. There is no separate scripting API to learn and no divergence between what the GUI does and what your script does β the History panel proves it, because what it records is literally the call the dialog made. When a colleague asks why the script gives different numbers from their manual run, the answer is always a parameter difference, and the two dictionaries can be diffed.
The performance story is worth understanding too. native: algorithms are C++ operating on feature sources, streaming features rather than materialising whole tables, which is why a native: buffer on a large layer will usually beat a hand-written PyQGIS feature loop by an order of magnitude β and why chaining through TEMPORARY_OUTPUT is not merely tidier but faster than writing and re-reading GeoPackages between steps. The gdal: algorithms are different in kind: they build a command line and run the GDAL binary, so their cost includes process startup and they always write real files.
Finally, the error model. processing.run raises QgsProcessingException on a hard failure, but a great deal of what goes wrong is a warning β features skipped for invalid geometry, a CRS assumed, an attribute truncated. Those go to the feedback object, and if you did not pass one they go nowhere you will look. Passing a logging feedback object is the difference between "the job ran" and "the job ran and dropped 412 features", which is exactly the kind of thing a batch job's error report exists to surface.
Edge cases or notes
Algorithm not found in a standalone script
The registry is empty until Processing is initialised. In a standalone script that means appending the plugins directory to sys.path and calling Processing.initialize() before the first processing.run. If native: algorithms resolve but grass: ones do not, that provider is genuinely not installed rather than not initialised.
Legacy qgis: ids still work but are not equivalent
Many qgis: ids are kept as aliases and forward to the native: implementation, but a handful are separate Python implementations with different parameters and slower execution. If you inherited a script full of qgis: ids, check each one with algorithmHelp before assuming an id swap is safe.
None is meaningful for optional parameters
Omitting an optional key and passing None are both accepted, but for parameters like SOURCE_CRS passing None explicitly means "detect it", which is what you usually want. Passing an empty string is not the same and often fails validation.
Selections do not exist headlessly
QgsProcessingFeatureSourceDefinition(layer.id(), True) restricts an algorithm to selected features, which is useful in the desktop and meaningless in a scheduled job. Express the subset as an expression instead, so the same script produces the same result whoever runs it.
Output file overwriting
Writing to an existing GeoPackage with the same layer name replaces that layer; writing a shapefile silently replaces the whole set of sidecar files. If a run must never clobber, check Path(out).exists() first β the framework will not stop you.
Threads and progress
processing.run blocks. To keep a GUI responsive you would use QgsProcessingAlgRunnerTask, but for automation blocking is what you want. Do not try to run several algorithms on threads in one process; run separate processes instead, as covered in parallel batch processing.
Internal links
- How to Automate QGIS with Python (PyQGIS): The Complete Workflow
- How to Run a PyQGIS Script Headless Without Opening QGIS
- How to Batch Process Layers in QGIS with PyQGIS
- How to Build a QGIS Processing Model and Run It from Python
- How to Write a Custom QGIS Processing Script Algorithm in Python
- How to Chain GIS Processing Steps into a Reusable Pipeline
- How to Drive a GIS Pipeline from a YAML Config File
- How to Log and Summarise Errors in a Batch GIS Job
FAQ
How do I find the parameter names for an algorithm?
Three reliable ways: run it once from the toolbox and read the recorded call in Processing β History; call processing.algorithmHelp("native:buffer"), which prints every parameter with its type and enum options; or run qgis_process help native:buffer from a terminal. Never copy parameter names from a tutorial without checking β several changed between QGIS versions.
What is the difference between native: and qgis: algorithms?
native: algorithms are implemented in C++ and are the current, maintained implementations. qgis: is the older Python provider; many of its ids are now aliases forwarding to the native version, but some remain distinct and slower. For new automation, use native: wherever the algorithm exists there.
Why does processing.run say the algorithm is not found?
The Processing registry has not been initialised. Inside QGIS this is done for you; in a standalone script you must append the QGIS plugins directory to sys.path, import Processing from processing.core.Processing, and call Processing.initialize() before the first run. If only third-party providers are missing, install and enable them instead.
Should I use TEMPORARY_OUTPUT or write files between steps?
Use TEMPORARY_OUTPUT for every intermediate. It avoids disk I/O, keeps the workspace clean, and is faster on any non-trivial dataset. Write a real file only where you want a result someone will open, and at the end of the chain β plus, when debugging, at the one step whose output you need to inspect.
How do I see warnings from an algorithm in a scheduled job?
Pass a QgsProcessingFeedback subclass that forwards pushInfo, pushWarning, and reportError into the logging module. Without it, those messages go to the QGIS log panel, which does not exist in a headless run β so a job that skipped hundreds of invalid features looks identical to a clean one.
Can I run Processing algorithms without writing Python at all?
Yes β qgis_process run native:buffer -- INPUT=in.gpkg DISTANCE=25 OUTPUT=out.gpkg runs any algorithm from the shell, with --json for machine-readable output. It is ideal for a single algorithm in a Makefile or CI step. Once you need conditionals, loops, or error handling, move to Python.
Do Processing algorithms handle CRS transformation for me?
Only where the algorithm says so. Overlay algorithms like native:clip will reproject the overlay to the input's CRS, but distance and area parameters are always interpreted in the input layer's units β so a 25-unit buffer on a layer in EPSG:4326 means 25 degrees. Reproject to a metric CRS first; the same trap is covered for the pure-Python stack in calculating area and distance correctly.
How do I pass a selection or a filtered subset to an algorithm?
In the desktop you can wrap the layer in QgsProcessingFeatureSourceDefinition with "selected features only". For automation, filter explicitly with native:extractbyexpression and feed its output into the next step β it is reproducible, it works headlessly, and the expression is visible in the script rather than in someone's session.