QGIS Processing "Algorithm Not Found" Error in Python: How to Fix It

On this page

Problem statement

The algorithm is right there in the Processing Toolbox. From Python it does not exist:

QgsProcessingException: Error: Algorithm native:buffer not found

Or it exists on your laptop and not on the server:

QgsProcessingException: Error: Algorithm native:selectbyexpression not found

Or the id resolves and the call fails on its input instead:

QgsProcessingException: Unable to execute algorithm
Could not load source layer for INPUT: nope.gpkg not found

Every message on this page was produced by running the code below, on QGIS 4.2 and on the 3.44 LTR in the official qgis/qgis:ltr image, with Rutland's schools as the data. There are only three ways to get the first two: the registry was never populated, the provider that owns the algorithm is not in this installation, or the id you used is not an id this QGIS knows. The error text is the same for all three, which is why it feels mysterious.

The registry comes up empty, or without your id, when:

  • Processing.initialize() was never called in a standalone script
  • Processing.initialize() ran before QgsApplication.initQgis()
  • the id is newer than the QGIS running it β€” 29 native: ids in QGIS 4.2 do not exist in 3.44
  • the id was typed from the dialog title rather than copied from the History panel
  • the algorithm belongs to a provider that is not installed where the script runs (PDAL in the 3.44 image, a plugin provider such as SAGA NextGen)
  • the script imports processing from a different QGIS installation than the one it initialised

Quick answer

To fix "algorithm not found":

  1. initialise QGIS then Processing, in that order, in every standalone script
  2. ask the registry whether the id exists before blaming the call
  3. copy ids from the Processing History panel β€” on the QGIS version the script will run on
  4. check algorithmHelp() for the parameter names β€” they are not the dialog labels
import os
import sys

os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from qgis.core import QgsApplication

qgs = QgsApplication([], False)
qgs.initQgis()                                    # 1. application first

sys.path.append("/usr/share/qgis/python/plugins")
from processing.core.Processing import Processing
import processing
Processing.initialize()                            # 2. then the registry

reg = QgsApplication.processingRegistry()
print("native:buffer ->", reg.algorithmById("native:buffer"))   # 3. verify the id exists

result = processing.run("native:buffer", {
    "INPUT": "data/schools.gpkg|layername=schools",
    "DISTANCE": 50,
    "OUTPUT": "out/buffered.gpkg",
})
print(result["OUTPUT"])
qgs.exitQgis()

algorithmById() returning None proves the problem is registration or the id itself, and separates that from a parameter problem in one line.

The anatomy of an algorithm id

An algorithm id split into its provider prefix and algorithm name. every id is provider:namenative:bufferthe providerthe algorithmnative:qgis:gdal:grass:sagang:model:Most "not found" errors are a provider that was never registered spatialworkflow.io
Every id is provider:name β€” and the provider half is where most failures live.

Initialise in order, list the registry, copy the right id

How the Processing registry is populated in a standalone script. the registry starts empty1QgsApplication.initQgis()the registry exists2sys.path += plugins dirfind the package3Processing.initialize()native:, qgis:, gdal:, grass:4plugin providers, if anye.g. SAGA NextGen5registry.algorithmById(id)proves it is thereInitialised before initQgis(), Processing left nothing behind: native:buffer not found spatialworkflow.io
The registry is empty until Processing.initialize() runs β€” and that must come after initQgis().

Initialise Processing in the right order

Inside QGIS, the Toolbox has already done this. Standalone, nothing has: after initQgis() alone, the registry holds no providers and no algorithms.

import os, sys
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")

from qgis.core import QgsApplication
qgs = QgsApplication([], False)
qgs.initQgis()                              # providers, CRS db, expression functions

sys.path.append("/usr/share/qgis/python/plugins")   # where the `processing` package lives
from processing.core.Processing import Processing
Processing.initialize()                     # registers native:, qgis:, gdal:, grass:, 3d: …

Calling Processing.initialize() before the application exists does not raise. It prints Application path not initialized dozens of times, returns, and leaves nothing behind: native:buffer is still not found after initQgis() (example 1).

If import processing itself fails with ModuleNotFoundError, the plugins path is wrong for your installation β€” it is /usr/share/qgis/python/plugins on Debian, Ubuntu and the official Docker image, and Contents/Resources/python/plugins inside a macOS bundle.

List what is actually registered

Once the registry is populated, ask it rather than guessing.

reg = QgsApplication.processingRegistry()

print("providers:", [p.id() for p in reg.providers()])
print("algorithms:", len(reg.algorithms()))

# find every algorithm whose id or name mentions "buffer"
for alg in reg.algorithms():
    if "buffer" in alg.id().lower() or "buffer" in alg.displayName().lower():
        print(f"{alg.id():32} {alg.displayName()}")
providers: ['3d', 'gdal', 'grass', 'model', 'native', 'pdal', 'project', 'qgis', 'script']
algorithms: 772
gdal:buffervectors               Buffer vectors
gdal:onesidebuffer               One side buffer
grass:r.buffer                   r.buffer
grass:r.buffer.lowmem            r.buffer.lowmem
grass:v.buffer                   v.buffer
native:buffer                    Buffer
native:bufferbym                 Variable width buffer (by M value)
native:multiringconstantbuffer   Multi-ring buffer (constant distance)
native:singlesidedbuffer         Single sided buffer
native:taperedbuffer             Tapered buffers
native:wedgebuffers              Create wedge buffers
qgis:variabledistancebuffer      Variable distance buffer

An empty provider list means Processing.initialize() did not run, or ran too early. A populated list without your algorithm means the id is wrong for this version or its provider is not installed.

Copy the id from the History panel, not the dialog

Run the algorithm once in the QGIS GUI, then open Processing β†’ History. Every run is recorded as the exact Python call the dialog made:

processing.run("native:joinattributesbylocation", {
    'INPUT': '/data/points.gpkg|layername=points',
    'JOIN': '/data/zones.gpkg|layername=zones',
    'PREDICATE': [0],
    'JOIN_FIELDS': [],
    'METHOD': 0,
    'DISCARD_NONMATCHING': False,
    'PREFIX': '',
    'OUTPUT': 'TEMPORARY_OUTPUT',
})

That is the ground truth for the QGIS that wrote it: correct id, correct parameter names, correct enum values. Guessing an id from the dialog title ("Join attributes by location") is how you end up with qgis:joinattributes, which does not exist in either version. The catch is the version: a History entry from QGIS 4.2 can name an algorithm that the 3.44 LTR on your server has under a different prefix, or not at all.

Know the provider prefixes β€” and which direction ids move

Prefix Provider Registered by Processing.initialize() in the two builds tested
native: QGIS C++ algorithms 321 in 3.44, 350 in 4.2
qgis: Python algorithms 44 in 3.44, 31 in 4.2 β€” 13 moved to native:
gdal: GDAL/OGR command-line tools 57 and 59; runs ogr2ogr, gdalwarp and friends
grass: GRASS GIS 307 in both, because GRASS was installed in both
pdal: PDAL point clouds 24 in 4.2, absent from the 3.44 image
3d:, model:, script: 3D, models and scripts from the profile model and script empty in a fresh profile

The move from qgis: to native: is the one that bites, and in one direction only. The 13 algorithms that 4.2 ported to C++ β€” selectbyexpression, randomselection, minimumboundinggeometry, heatmapkerneldensityestimation and nine more β€” still resolve under their old qgis: ids in 4.2, which quietly redirects them. Their new native: ids do not exist in 3.44 at all. Code written against the older release keeps working on the newer one; code copied from the newer one fails on the older.

Read the parameter names from the algorithm itself

An id that resolves but rejects your parameters is a different problem with a one-line diagnosis.

processing.algorithmHelp("native:buffer")

This prints every parameter, its type, whether it is optional, and the integer values behind each enum β€” the information the dialog hides behind friendly labels. The same comes from the definitions directly:

alg = QgsApplication.processingRegistry().algorithmById("native:buffer")
for name in ("END_CAP_STYLE", "JOIN_STYLE"):
    param = alg.parameterDefinition(name)
    print(name, dict(enumerate(param.options())), "default", param.defaultValue())
END_CAP_STYLE {0: 'Round', 1: 'Flat', 2: 'Square'} default 0
JOIN_STYLE {0: 'Round', 1: 'Miter', 2: 'Bevel'} default 0

END_CAP_STYLE takes 0, 1 or 2, not "round".

Check that you are talking to one QGIS

Two installations in one process β€” a system QGIS plus a conda one, say β€” produce a registry populated from one and an API from the other.

import qgis
from qgis.core import Qgis

print("qgis pkg   :", qgis.__file__)
print("processing :", processing.__file__)
print("version    :", Qgis.QGIS_VERSION)
qgis pkg   : /usr/share/qgis/python/qgis/__init__.py
processing : /usr/share/qgis/python/plugins/processing/__init__.py
version    : 4.2.2-BelΓ©m do ParΓ‘

Both paths should sit under the same prefix. If they do not, fix the environment before touching the code.

Resolving algorithms on Rutland's schools

Where each algorithm id prefix comes from, and what it needs to be available. the prefix says where the code livesprefixcomes fromneedsnative:QGIS core, C++29 ids are 4.2-onlyqgis:Python algorithmsold ids still resolvegdal:ogr2ogr, gdal_* toolsbinaries; errors not raisedgrass:GRASS modulesGRASS; auto-registeredthe registry is empty until Processing is initialised, which is the other half of this errorCopy ids from the History panel of the QGIS version you deploy on spatialworkflow.io
Example 3 diffs the catalogues of two releases; example 5 shows what gdal: and grass: run underneath.

The data used on this page

Rutland's schools from Geofabrik's 1 January 2026 OpenStreetMap extract (Β© OpenStreetMap contributors, ODbL), in British National Grid. The page runs under QGIS 4.2; example 3 also runs a script in the qgis/qgis:ltr Docker image, which was QGIS 3.44.

import os
import subprocess
import sys
from pathlib import Path

import requests
from osgeo import gdal

gdal.UseExceptions()
Path("data").mkdir(exist_ok=True)
Path("out").mkdir(exist_ok=True)
if not Path("data/schools.gpkg").exists():
    Path("data/rutland.shp.zip").write_bytes(requests.get(
        "https://download.geofabrik.de/europe/united-kingdom/england/rutland-260101-free.shp.zip", timeout=300).content)
    gdal.VectorTranslate("data/schools.gpkg", "/vsizip/data/rutland.shp.zip/gis_osm_pois_a_free_1.shp",
                         layerName="schools", dstSRS="EPSG:27700", reproject=True, where="fclass = 'school'")

os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
sys.path.append("/usr/share/qgis/python/plugins")
from qgis.core import QgsApplication, QgsProject, QgsProcessingFeedback, QgsVectorLayer
qgs = QgsApplication([], False)
qgs.initQgis()
print("after initQgis():", len(QgsApplication.processingRegistry().algorithms()), "algorithms")
from processing.core.Processing import Processing
import processing
Processing.initialize()
print("after Processing.initialize():", len(QgsApplication.processingRegistry().algorithms()), "algorithms")
print(QgsVectorLayer("data/schools.gpkg|layername=schools", "schools", "ogr").featureCount(), "schools")
after initQgis(): 0 algorithms
after Processing.initialize(): 772 algorithms
26 schools

Example 1: A reusable initialiser, and what the wrong order does

Each script runs in its own process, because a process gets one QgsApplication.

Path("qgis_boot.py").write_text('''
"""qgis_boot.py - initialise QGIS + Processing, or explain why not."""
import os, sys
from pathlib import Path

os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")

PLUGIN_DIRS = [
    "/usr/share/qgis/python/plugins",
    "/Applications/QGIS.app/Contents/Resources/python/plugins",
]

def boot():
    from qgis.core import QgsApplication

    app = QgsApplication([], False)
    app.initQgis()

    for d in PLUGIN_DIRS:
        if Path(d).is_dir() and d not in sys.path:
            sys.path.append(d)
    try:
        from processing.core.Processing import Processing
    except ImportError as exc:
        raise RuntimeError(f"cannot import the processing plugin. Tried: {PLUGIN_DIRS}") from exc

    Processing.initialize()
    n = len(QgsApplication.processingRegistry().algorithms())
    if n == 0:
        raise RuntimeError("Processing registry is empty after initialize()")
    print(f"processing ready: {n} algorithms")
    return app
''')

Path("wrong_order.py").write_text('''
import os, sys
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
sys.path.append("/usr/share/qgis/python/plugins")
from qgis.core import QgsApplication
from processing.core.Processing import Processing
Processing.initialize()                        # too early: no application yet
app = QgsApplication([], False)
app.initQgis()
import processing
try:
    processing.run("native:buffer", {"INPUT": "data/schools.gpkg", "DISTANCE": 50, "OUTPUT": "TEMPORARY_OUTPUT"})
except Exception as exc:
    print(type(exc).__name__ + ":", str(exc).strip())
app.exitQgis()
''')

for script in (["-c", "import qgis_boot; app = qgis_boot.boot(); app.exitQgis()"],
               ["-c", "import qgis_boot; qgis_boot.boot().exitQgis()"],
               ["wrong_order.py"]):
    run = subprocess.run([sys.executable, *script], capture_output=True, text=True)
    lines = [l for l in (run.stdout + run.stderr).splitlines() if l.strip() and not l.startswith("QThreadStorage")]
    noise = sum(l == "Application path not initialized" for l in lines)
    print(f"{script[-1]}: exit {run.returncode}" + (f", 'Application path not initialized' x {noise}" if noise else ""))
    for line in lines:
        if line != "Application path not initialized":
            print("   ", line)
import qgis_boot; app = qgis_boot.boot(); app.exitQgis(): exit 0
    processing ready: 772 algorithms
import qgis_boot; qgis_boot.boot().exitQgis(): exit -11
wrong_order.py: exit 0, 'Application path not initialized' x 55
    QgsProcessingException: Error: Algorithm native:buffer not found

The wrong order raised nothing at the point of the mistake. The only signs were a screenful of warnings and, later, an algorithm that is not found. The middle run is a different trap with the same helper: chaining boot().exitQgis() in one expression leaves the application to be destroyed the moment the call returns, and the process died with a segmentation fault (exit -11) before printing anything. Keep the application in a variable for the life of the script.

Example 2: Resolve an algorithm before running it

def require_algorithm(alg_id: str):
    reg = QgsApplication.processingRegistry()
    alg = reg.algorithmById(alg_id)
    if alg is not None:
        return alg

    needle = alg_id.split(":")[-1].lower()
    close = [a.id() for a in reg.algorithms() if needle in a.id().lower()]
    raise ValueError(
        f"algorithm {alg_id!r} not found in QGIS {Qgis.QGIS_VERSION.split('-')[0]}. "
        f"Providers loaded: {[p.id() for p in reg.providers()]}. "
        f"Did you mean: {close[:5]}?"
    )

from qgis.core import Qgis
print(require_algorithm("native:buffer").id())
print(require_algorithm("qgis:selectbyexpression").id(), "(asked for qgis:selectbyexpression)")
for wrong in ("qgis:joinattributes", "native:bufer"):
    try:
        require_algorithm(wrong)
    except ValueError as exc:
        print(exc)
native:buffer
native:selectbyexpression (asked for qgis:selectbyexpression)
algorithm 'qgis:joinattributes' not found in QGIS 4.2.2. Providers loaded: ['3d', 'gdal', 'grass', 'model', 'native', 'pdal', 'project', 'qgis', 'script']. Did you mean: ['native:joinattributesbylocation', 'native:joinattributestable']?
algorithm 'native:bufer' not found in QGIS 4.2.2. Providers loaded: ['3d', 'gdal', 'grass', 'model', 'native', 'pdal', 'project', 'qgis', 'script']. Did you mean: []?

Two things to notice. algorithmById() resolves the old qgis: id and hands back the native: algorithm, so the id you asked for and the id you got differ. And the suggestions only help when the misspelling is a substring: native:bufer finds nothing, because bufer is not in buffer. Printing the provider list is what makes that message useful anyway.

Example 3: Dump the catalogue, and compare it with the server's

import csv

reg = QgsApplication.processingRegistry()
with open("algorithms_4.2.csv", "w", newline="", encoding="utf-8") as fh:
    w = csv.writer(fh)
    w.writerow(["id", "display_name", "provider", "group"])
    for alg in sorted(reg.algorithms(), key=lambda a: a.id()):
        w.writerow([alg.id(), alg.displayName(), alg.provider().id(), alg.group()])
ids_42 = {a.id() for a in reg.algorithms()}

Path("dump_ids.py").write_text('''
import os, sys
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
sys.path.append("/usr/share/qgis/python/plugins")
from qgis.core import QgsApplication, Qgis
app = QgsApplication([], False); app.initQgis()
from processing.core.Processing import Processing
Processing.initialize()
reg = QgsApplication.processingRegistry()
print(Qgis.QGIS_VERSION)
print("\\n".join(sorted(a.id() for a in reg.algorithms())))
''')
docker = subprocess.run(["docker", "run", "--rm", "-v", f"{Path.cwd()}:/work", "-w", "/work",
                         "--user", f"{os.getuid()}:{os.getgid()}", "-e", "HOME=/tmp", "-e", "QT_QPA_PLATFORM=offscreen",
                         "qgis/qgis:ltr", "python3", "dump_ids.py"], capture_output=True, text=True)
server_version, *server_ids = docker.stdout.split()
ids_344 = set(server_ids)
print(f"QGIS 4.2: {len(ids_42)} algorithms; QGIS {server_version.split('-')[0]} (qgis/qgis:ltr): {len(ids_344)}")

newer = sorted(ids_42 - ids_344)
print("in 4.2, missing on the LTR:", {p: sum(i.startswith(p + ":") for i in newer) for p in ("native", "pdal", "gdal")})
moved = sorted(i for i in ids_344 - ids_42)
print("on the LTR, gone from 4.2's list:", len(moved), "- e.g.", moved[:3])
print("do the old ids still resolve in 4.2?",
      all(reg.algorithmById(i) is not None for i in moved),
      "->", reg.algorithmById(moved[0]).id())
QGIS 4.2: 772 algorithms; QGIS 3.44.15 (qgis/qgis:ltr): 730
in 4.2, missing on the LTR: {'native': 29, 'pdal': 24, 'gdal': 2}
on the LTR, gone from 4.2's list: 13 - e.g. ['qgis:distancematrix', 'qgis:heatmapkerneldensityestimation', 'qgis:hypsometriccurves']
do the old ids still resolve in 4.2? True -> native:distancematrix

A local catalogue is faster to search than the documentation and always matches the installed version. The comparison is the one worth automating: if the machine that develops the pipeline and the machine that runs it are on different QGIS releases, diff their catalogues before the first deployment, not after the first failure.

Example 4: The same checks from the shell

def qgis_process(*args):
    run = subprocess.run(["qgis_process", *args], capture_output=True, text=True,
                         env=dict(os.environ, QT_QPA_PLATFORM="offscreen"))
    return [l for l in (run.stdout + run.stderr).splitlines() if l.strip()]

listing = qgis_process("list")
print(sum("buffer" in l.lower() for l in listing if "\t" in l), "listed ids mention buffer")
print(qgis_process("help", "native:buffer")[0])
print(next(l for l in qgis_process("run", "native:buffer", "--", "INPUT=data/schools.gpkg|layername=schools",
                                    "DISTANCE=50", "OUTPUT=out/qp_buffer.gpkg") if l.startswith("OUTPUT")))
print(qgis_process("run", "native:bufer")[-1])
11 listed ids mention buffer
Buffer (native:buffer)
OUTPUT:	out/qp_buffer.gpkg
Algorithm native:bufer not found!

qgis_process initialises QGIS and Processing for you, so if an id works there and not in your script, the problem is your initialisation rather than the id. Its own error for an unknown id is worded differently from the Python one β€” not found!, with an exclamation mark β€” which is worth knowing when grepping logs.

Features written by four buffer algorithms from three providers, each given the same 26 schools and 100 m. same request, three providersnative:buffer26, one per schoolgdal: + GEOMETRY26, plus a warninggrass:v.buffer20, overlaps dissolvedgdal: defaultsno layer; run() returned01326The failed gdal: run raised nothing β€” the error went to the feedback object only spatialworkflow.io
Example 5 in one picture: an algorithm with the same name in another provider is a different program.

Example 5: What the gdal: and grass: providers actually run

import re


class CommandLog(QgsProcessingFeedback):
    """Keep the programs a provider runs and the errors they report."""
    def __init__(self):
        super().__init__()
        self.commands, self.errors = [], []

    def pushCommandInfo(self, info):
        self.commands.append(info.split()[0])

    def reportError(self, error, fatalError=False):
        self.errors.append(error.strip())

schools = "data/schools.gpkg|layername=schools"
runs = {
    "gdal:buffervectors": {"INPUT": schools, "DISTANCE": 100, "OUTPUT": "out/gdal_buffer.gpkg"},
    "gdal:buffervectors + GEOMETRY": {"INPUT": schools, "GEOMETRY": "geom", "DISTANCE": 100,
                                      "OUTPUT": "out/gdal_buffer_geom.gpkg"},
    "grass:v.buffer": {"input": schools, "distance": 100, "output": "out/grass_buffer.gpkg"},
    "native:buffer": {"INPUT": schools, "DISTANCE": 100, "OUTPUT": "out/native_buffer.gpkg"},
}
for label, params in runs.items():
    log = CommandLog()
    result = processing.run(label.split()[0], params, feedback=log)
    n = QgsVectorLayer(next(iter(result.values())), "out", "ogr").featureCount()
    print(f"{label:31} ran {log.commands or 'in-process'} -> {n} features")
    for error in log.errors[:1]:
        message = re.sub(r"^.*?(ERROR|Warning) \d+: ", "", error).split("): ")[-1]
        print("   reported:", message[:100])
gdal:buffervectors              ran ['ogr2ogr'] -> -2 features
   reported: no such column: geometry
gdal:buffervectors + GEOMETRY   ran ['ogr2ogr'] -> 26 features
   reported: A geometry of type MULTIPOLYGON is inserted into layer SELECT of geometry type POLYGON, which is not
grass:v.buffer                  ran ['g.proj', 'v.in.ogr', 'g.region', 'v.buffer', 'v.out.ogr'] -> 20 features
native:buffer                   ran in-process -> 26 features

A gdal: algorithm is a command line for ogr2ogr or a gdal_* tool, and a grass: one is a sequence of GRASS modules with an import at the start and an export at the end. That is why their availability is a property of the machine β€” the binaries have to be there β€” and it has two consequences the run above shows.

The first gdal:buffervectors call failed and processing.run() did not raise. The algorithm writes SQL against a geometry column called geometry; this GeoPackage's column is geom, ogr2ogr exited with an error, and the call returned an output path to a file with no layer in it (a feature count of -2). The error only reached the feedback object. Passing GEOMETRY fixed it, though ogr2ogr still reported multipolygons going into a layer it had declared as polygons β€” and named that layer SELECT, after the SQL that made it. Check the output of a gdal: algorithm, not just the return value.

The second is that the providers are different programs, not different spellings: GRASS's v.buffer dissolved the overlapping school buffers and returned fewer features than the 26 schools that went in, where native:buffer kept one per school. Swapping one provider's algorithm for another's because the name matches changes the result.

The page's QGIS session ends here:

QgsProject.instance().clear()
qgs.exitQgis()
print("QGIS closed")
QGIS closed

A registry, not a namespace

Processing is a registry, not a namespace. At start-up it is empty; Processing.initialize() asks each provider to enumerate its algorithms and register them under provider:name. processing.run() then looks the string up in that registry. "Algorithm not found" is therefore a dictionary miss, and there are exactly two ways to get one: nothing put the key in, or you asked for a key this installation does not have.

Four ways to discover an algorithm id and when to use each. four ways to stop guessingwhat it gives youbest whenHistory panelβœ“the exact call madeafter one GUI runalgorithmHelp()every parameter and enumwhile writing coderegistry loopids matching a wordhunting for an idqgis_process listids and help, no Pythonfrom a shellThe History panel reflects your installed version β€” a blog post does not spatialworkflow.io
Four ways to find the right id β€” the History panel is the fastest and the most reliable.

Ordering matters because the providers need a live application. initQgis() sets up the application's paths, the CRS database and the expression engine, and Processing.initialize() builds on them. Run it first and it finds no application path β€” hence the repeated warning β€” and whatever it managed to register did not survive: the registry was empty again after initQgis(), and a second initialize() call was needed to fill it.

The id itself has history. Many algorithms began as Python implementations under the qgis: prefix and were rewritten in C++ under native:. QGIS keeps the old id working as an alias after a port β€” all 13 ported between 3.44 and 4.2 still resolved β€” but the alias only runs forward in time. That asymmetry is the usual shape of this error in production: a developer on the latest release copies an id from the History panel, and the pinned LTR in the pipeline's container has never heard of it.

Finally, provider availability is a property of the installation, not the code. In both builds tested, GRASS was installed and Processing.initialize() registered its 307 algorithms without being asked; PDAL was in the 4.2 package and not in the 3.44 image. Checking for the ids a pipeline needs at start-up, and failing with the provider list, costs a few lines and replaces a confusing failure on a machine you cannot see with a clear one.

Run versus runAndLoadResults, enums and profiles

  • processing.run vs processing.runAndLoadResults: the latter works headless, but "load" means adding the result to QgsProject.instance() β€” in a standalone run it returned a layer id and put a layer called Buffered into a project nobody sees. Use run and keep the output.
  • Enum parameters take integers: PREDICATE: [0] means "intersects". algorithmHelp() lists the mapping; the names in the dialog are labels only.
  • Layer parameters accept several forms: a file path, a path|layername=x URI, or a QgsVectorLayer object. The URI form is what History records for multi-layer files.
  • TEMPORARY_OUTPUT needs a live context: the result layer lives in the Processing context and disappears with it. Write it out before teardown.
  • Model and script algorithms come from the profile: the model and script providers are always registered, and in a fresh headless profile they held no algorithms. A model an analyst saved in their desktop profile is not visible to a script running under another user.
  • Pin the QGIS version in Docker if a pipeline must keep working unattended, and develop against the same version.
  • An old qgis: id that still works has been redirected; algorithmById() returns the native: algorithm. Update the id when convenient, but it is not what breaks.

FAQ

Why does the algorithm work in the Toolbox but not in my script?

Because the Toolbox runs inside a QGIS that has already initialised Processing and loaded every enabled provider. A standalone script must call QgsApplication.initQgis() and then Processing.initialize() itself.

Should I use qgis: or native:?

Whichever the QGIS that runs the script has. A qgis: id that was ported to C++ keeps working as an alias on newer releases, but the new native: id does not exist on older ones: 13 algorithms that are native: in 4.2 are only qgis: in 3.44. If one script must run on both, use the old id or check the registry first.

How do I find the correct parameter names?

Run processing.algorithmHelp("native:buffer"), or qgis_process help native:buffer from a shell. Both list every parameter, its type, and the integer values behind enums.

Why are GRASS or SAGA algorithms missing?

GRASS algorithms need GRASS installed on the machine; where it was, Processing.initialize() registered all 307 of them in both QGIS 3.44 and 4.2 without further code. SAGA comes from a separate plugin (SAGA NextGen) that has to be installed and loaded, and PDAL algorithms depend on how QGIS was packaged β€” the 3.44 Docker image had none.

What does "Could not load source layer for INPUT" mean?

The id resolved but the input value did not. Check the path, and for multi-layer files use the URI form file.gpkg|layername=parcels β€” the same string the History panel records.

Can I list every available algorithm from the command line?

Yes: qgis_process list prints them grouped by provider, and qgis_process help <id> prints the parameters. This is the quickest way to check what a server build actually has.

Why did my script break after a QGIS upgrade?

Algorithm ids and parameter names do change between releases: between 3.44 and 4.2, 13 algorithms moved from qgis: to native: and 29 new native: ids appeared. Pin the QGIS version for scheduled pipelines, develop on the same version, and resolve ids through a helper that fails loudly with the provider list when one is missing.