Run GRASS and SAGA Algorithms from PyQGIS

QGIS's native algorithms cover a lot, but some of the most powerful spatial tools live in GRASS GIS and SAGA: hydrological modelling, viewsheds with curvature, cost-distance surfaces, terrain indices, image segmentation, network cleaning. Processing wraps both as providers, so their tools can be called from Python with processing.run like any native algorithm — with a few differences in naming, parameters and behaviour that trip up first-time users.

This recipe belongs to Chaining Processing Algorithms. It checks that the providers are available, finds algorithm ids robustly, passes GRASS parameters and flags, controls the computational region and resolution, handles outputs and errors, and compares GRASS, SAGA and native tools.

How external tools run through ProcessingA processing.run call to a GRASS or SAGA algorithm goes through the provider, which exports inputs to the tool's own format, writes a temporary GRASS location or SAGA files, runs the external program, and imports the outputs back as GeoTIFFs or GeoPackages. The extra conversion explains why these algorithms start more slowly than native ones and why output formats are files rather than memory layers.Export, run externally, import backprocessing.rungrass: / saga:providerexport inputstemp workspaceGRASS / SAGAexternalprocessoutputsfilesimported

Prerequisites

  • QGIS 3.34 LTR or newer, or the QGIS 4 series. GRASS is bundled with the Windows (OSGeo4W) and macOS installers; on Linux it is a separate package. SAGA support comes from the separate "Processing Saga NextGen Provider" plugin on current releases.
  • Inputs in a projected CRS for anything involving distances or areas.

Check the providers are available

Scripts that depend on GRASS or SAGA should verify the providers before doing any work, because a missing provider fails with an unhelpful "algorithm not found".

from qgis.core import QgsApplication

registry = QgsApplication.processingRegistry()
for p in registry.providers():
    if p.id().startswith(("grass", "saga", "sagang")):
        print(f"{p.id():<8} {p.name():<28} active={p.isActive()} algorithms={len(p.algorithms())}")

def require_provider(prefix):
    for p in registry.providers():
        if p.id().startswith(prefix) and p.isActive() and p.algorithms():
            return p
    raise RuntimeError(f"Processing provider '{prefix}*' is not installed or not active")

grass = require_provider("grass")

Breakdown: Listing providers shows their ids, whether they are active and how many algorithms they expose — a provider that is active but has zero algorithms usually means the external program was not found. The GRASS provider's id has been grass7 on older releases and grass on newer ones; SAGA appears as sagang from the NextGen plugin. Failing early with a clear message is much friendlier than a missing-algorithm error halfway through a batch.

Find algorithm ids robustly

Because provider ids differ between versions, hard-coding grass7:r.slope.aspect breaks on a newer QGIS. Looking algorithms up by the tool name keeps scripts portable.

def find_alg(tool, prefixes=("grass", "sagang", "saga")):
    for alg in registry.algorithms():
        prov, _, name = alg.id().partition(":")
        if name == tool and prov.startswith(prefixes):
            return alg.id()
    raise RuntimeError(f"{tool} not found in providers {prefixes}")

R_SLOPE = find_alg("r.slope.aspect")
print(R_SLOPE)
import processing
print(processing.algorithmHelp(R_SLOPE)[:800])

Breakdown: Matching the part after the colon finds grass:r.slope.aspect or grass7:r.slope.aspect alike; the same works for SAGA tool names. algorithmHelp prints every parameter with its name, type and default — essential, because GRASS and SAGA parameter names come from the tools themselves and differ from native conventions. Keep the lookup function in a small shared module so every script uses it.

Pass GRASS parameters and flags

GRASS parameters use the tool's own option names in lower case, and flags are booleans with a leading dash. Output parameters take file paths.

GRASS parameter conventionsGRASS options such as elevation and slope keep their GRASS names. Flags such as -a or -e become boolean parameters whose keys start with a dash. Outputs are file paths, usually GeoTIFF. Region and resolution parameters, GRASS_REGION_PARAMETER and GRASS_REGION_CELLSIZE_PARAMETER, control the computational extent and cell size.Options, flags, outputs, regionoptionselevationformatGRASS namesflags"-a": True"-e": Truebooleansoutputsslope: pathaspect: pathfilesregionGRASS_REGION_PARAMETERextent, cell size

dem = "/data/terrain/dem_10m.tif"
res = processing.run(R_SLOPE, {
    "elevation": dem,
    "format": 0,                         # 0 = degrees, 1 = percent
    "precision": 0,                      # FCELL
    "-a": True,                          # do not align region to input
    "-e": True,                          # compute values at edges
    "zscale": 1.0,
    "min_slope": 0.0,
    "slope": "/data/terrain/slope_grass.tif",
    "aspect": "/data/terrain/aspect_grass.tif",
    "GRASS_REGION_PARAMETER": None,
    "GRASS_REGION_CELLSIZE_PARAMETER": 0,
    "GRASS_RASTER_FORMAT_OPT": "COMPRESS=DEFLATE",
})
print(res["slope"], res["aspect"])

Breakdown: Option names (elevation, format, zscale) are exactly those in the GRASS manual for r.slope.aspect. Flags are keys with a dash; omitting a flag means false. Each output is a separate parameter with a file path, and unrequested outputs can be left out. GRASS_REGION_PARAMETER set to None uses the input's extent; GRASS_REGION_CELLSIZE_PARAMETER 0 uses the input's resolution. GRASS_RASTER_FORMAT_OPT passes creation options to the exported GeoTIFF. The same output from QGIS's native algorithms is covered in generating slope, aspect and hillshade, useful for comparison.

Control region and resolution

GRASS computes on a region: an extent and a cell size. When several inputs have different extents or resolutions, setting the region explicitly decides the output grid and avoids surprises.

from qgis.core import QgsRasterLayer

ref = QgsRasterLayer(dem, "dem")
e = ref.extent()
region = f"{e.xMinimum()},{e.xMaximum()},{e.yMinimum()},{e.yMaximum()} [{ref.crs().authid()}]"

R_COST = find_alg("r.cost")
cost = processing.run(R_COST, {
    "input": "/data/model/friction_10m.tif",
    "start_points": "/data/model/origins.gpkg",
    "max_cost": 0, "null_cost": None, "memory": 1024,
    "-k": True,                           # knight's move for smoother surfaces
    "output": "/data/model/cumulative_cost.tif",
    "GRASS_REGION_PARAMETER": region,
    "GRASS_REGION_CELLSIZE_PARAMETER": ref.rasterUnitsPerPixelX(),
})["output"]

Breakdown: Passing the reference raster's extent and pixel size as region and cell size makes the cost surface align with the DEM exactly, so later raster arithmetic needs no resampling — the alignment discipline from resampling and aligning rasters. Vector inputs such as start points are converted to GRASS vectors automatically. The memory option, in megabytes, lets GRASS use more RAM for large rasters, which speeds up many modules considerably.

Run SAGA tools

SAGA tools follow the same pattern with their own parameter names, which are upper-case in Processing. The NextGen provider exposes them under sagang:.

TWI = find_alg("topographicwetnessindex", prefixes=("sagang", "saga"))
twi = processing.run(TWI, {
    "SLOPE": "/data/terrain/slope_rad.tif",
    "AREA": "/data/hydro/catchment_area.tif",
    "TRANS": None,
    "CONV": 0,
    "METHOD": 0,
    "TWI": "/data/terrain/twi.sdat",
})["TWI"]
print(twi)

Breakdown: SAGA tool names in Processing are lower-case versions of the SAGA tool titles without spaces; find them with the lookup helper and confirm parameters with algorithmHelp. SAGA writes its native .sdat format by default; QGIS reads it directly, and gdal:translate converts it to GeoTIFF when needed. The topographic wetness index here combines slope and upslope area, typically computed with SAGA's own catchment area tool. Exact parameter names vary between SAGA versions, which is another reason to read the help on the target machine.

Handle outputs and errors

External tools fail differently from native algorithms: missing dependencies, unsupported data types, a region that excludes all inputs. Catching exceptions and reading the log gives the real reason.

Where the real error message isWhen a GRASS or SAGA algorithm fails, processing.run raises a generic exception. The external tool's own message is written to the Processing log and the feedback object. Capturing feedback in a script, or reading the Processing log panel interactively, shows the actual cause, such as a region with no data or an unsupported input type.The exception says that; the log says whyexceptionalgorithm failedfeedback logtool's own messagefixregion, types,missing deps

from qgis.core import QgsProcessingFeedback

class CollectingFeedback(QgsProcessingFeedback):
    def __init__(self):
        super().__init__()
        self.lines = []
    def pushInfo(self, info):
        self.lines.append(info)
    def reportError(self, error, fatalError=False):
        self.lines.append("ERROR: " + error)
    def pushConsoleInfo(self, info):
        self.lines.append(info)

fb = CollectingFeedback()
try:
    processing.run(R_SLOPE, {"elevation": "/data/terrain/missing.tif",
                             "slope": "/tmp/s.tif"}, feedback=fb)
except Exception as err:
    print("failed:", err)
    print("\n".join(fb.lines[-15:]))

Breakdown: A feedback subclass that records pushInfo, pushConsoleInfo and errors captures the external tool's console output, which is where GRASS and SAGA explain what went wrong. Printing the last lines after a failure turns "algorithm failed" into "Raster map not found" or "region contains no data". In unattended scripts, log these lines rather than printing them, following handling errors and logging in unattended scripts.

Native, GRASS or SAGA?

Prefer native algorithms when one exists: they start instantly, accept memory layers, need no external installation and behave identically on every machine. Use GRASS for its depth in hydrology, cost surfaces, viewsheds and topology cleaning, and when its algorithms are the established reference in a field. Use SAGA for terrain analysis indices, some classification tools and its large set of specialised modules. Whichever you choose, record the provider and version with the results — the same tool name can produce slightly different numbers across versions — and keep a fallback in mind for machines where the provider is missing.

QGIS version compatibility

GRASS algorithms are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4 when GRASS 8 is installed; the provider id changed from grass7 to grass during the 3.x series, hence the lookup. SAGA support comes from the Saga NextGen provider plugin on current releases, which supports SAGA 7.3 and newer. Parameter names follow the external tools and can change with their versions.

Troubleshooting

  • "Algorithm not found". The provider is missing or the id prefix differs; use the lookup helper.
  • The provider shows zero algorithms. The external program is not installed or not on the path.
  • Outputs are offset or resampled. The region was not set; pass region and cell size explicitly.
  • Very slow on large rasters. Increase GRASS's memory option and avoid unnecessary format conversions.

Conclusion

Check providers before running, find algorithm ids by tool name rather than hard-coded prefixes, pass GRASS options by their own names with dashed boolean flags and file outputs, set region and cell size to control the output grid, use upper-case parameters for SAGA tools, capture feedback to see the real error, and prefer native algorithms where they exist.

Frequently Asked Questions

Can GRASS algorithms use memory layers as input? Yes — the provider exports them to temporary files first. Outputs are files.

Do I need a GRASS location or mapset? No. The provider creates a temporary one for each run.

Can I chain GRASS steps without re-exporting data? Not through Processing; each call exports and imports. For long GRASS-only chains, a GRASS session script is faster.

Are WhiteboxTools available the same way? Yes, through the WhiteboxTools for QGIS plugin, with the same lookup approach.