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

Problem statement

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

QgsProcessingException: Error: Algorithm native:buffer not found
QgsProcessingException: Error: Algorithm qgis:joinattributesbylocation not found

Or the call runs but complains about a parameter you copied from the dialog:

QgsProcessingException: Unable to execute algorithm
Could not load source layer for INPUT: invalid value

There are only three possibilities: the registry was never populated, the provider that owns the algorithm is not loaded, or the id you used is not the id QGIS knows. The error text is the same for all three, which is why it feels mysterious.

Common causes:

  • Processing.initialize() was never called in a standalone script
  • Processing.initialize() ran before QgsApplication.initQgis()
  • the algorithm belongs to a third-party provider (GRASS, SAGA, GDAL) that is not enabled
  • the id is out of date: qgis: versus native:, or renamed between QGIS versions
  • the id was typed from the dialog title rather than copied from the History panel
  • 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. print the registry and confirm the id exists before blaming the call
  3. copy ids from the Processing History panel, never from the dialog title
  4. enable the provider (GRASS, SAGA) explicitly if the algorithm belongs to it
  5. check algorithmHelp() for the parameter names β€” they are not the dialog labels
import sys
from qgis.core import QgsApplication

QgsApplication.setPrefixPath("/usr", True)
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

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

result = processing.run("native:buffer", {
    "INPUT": "data/raw/parcels.gpkg",
    "DISTANCE": 50,
    "OUTPUT": "data/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 provider prefix and algorithm name, with examples per provider.
Every id is `provider:name` β€” and the provider half is where most failures live.

Step-by-step solution

Vertical steps from QgsApplication init through Processing.initialize to provider registration.
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.

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

from qgis.core import QgsApplication
QgsApplication.setPrefixPath("/usr", True)
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:, 3d: …

If import processing itself fails with ModuleNotFoundError, the plugins path is wrong for your installation β€” check /usr/share/qgis/python/plugins, /usr/lib/qgis/python/plugins, or the Contents/Resources/python/plugins folder inside a macOS bundle.

List what is actually registered

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

from qgis.core import QgsApplication

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():40} {alg.displayName()}")

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 or the provider is disabled.

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: 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.

Know the provider prefixes

Prefix Provider Notes
native: QGIS C++ algorithms Fastest, always available
qgis: Legacy Python algorithms Many were ported to native:; some ids still resolve as aliases
gdal: GDAL/OGR command wrappers Requires the GDAL binaries on PATH
grass: / grass7: GRASS GIS Needs GRASS installed and the plugin enabled
saga: / sagang: SAGA Needs SAGA installed; prefix changed across versions
3d:, pdal: Newer built-ins Availability depends on the QGIS build

The most common single mistake is using a qgis: id for an algorithm that now lives under native:. When in doubt, search the registry as shown above.

Enable third-party providers explicitly

GRASS and SAGA algorithms are registered by plugins that a standalone script does not load automatically.

from qgis.core import QgsApplication

# GRASS, if the plugin ships with your QGIS build
try:
    from processing_grass.processing_plugin import GrassProvider   # QGIS 3.36+
    QgsApplication.processingRegistry().addProvider(GrassProvider())
except ImportError:
    print("GRASS provider not available in this build")

Older builds expose it as processing.algs.grass7.Grass7AlgorithmProvider. Because the module path has moved more than once, guard the import and report clearly rather than letting the script fail later with "algorithm not found".

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.

import processing

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. END_CAP_STYLE taking 0/1/2 rather than "round" is a typical discovery.

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, processing, sys
from qgis.core import Qgis

print("python     :", sys.executable)
print("qgis pkg   :", qgis.__file__)
print("processing :", processing.__file__)
print("version    :", Qgis.QGIS_VERSION)

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

Code examples

Example 1: a reusable initialiser that fails with a useful message

"""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",
    "/usr/lib/qgis/python/plugins",
    "/Applications/QGIS.app/Contents/Resources/python/plugins",
]

def boot(prefix="/usr"):
    from qgis.core import QgsApplication

    QgsApplication.setPrefixPath(prefix, True)
    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

Example 2: resolve an algorithm before running it

from qgis.core import QgsApplication

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. "
        f"Providers loaded: {[p.id() for p in reg.providers()]}. "
        f"Did you mean: {close[:5]}?"
    )

require_algorithm("native:buffer")

Suggesting near-matches turns a dead end into a fix, especially for the qgis: β†’ native: renames.

Example 3: dump the full catalogue to a file

from qgis.core import QgsApplication
import csv

reg = QgsApplication.processingRegistry()
with open("algorithms.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()])
print("wrote algorithms.csv")

A local catalogue is faster to search than the documentation and always matches your installed version.

Example 4: the same check from the shell

qgis_process list | head -20
qgis_process list | grep -i buffer
qgis_process help native:buffer
qgis_process run native:buffer -- INPUT=data/raw/parcels.gpkg DISTANCE=50 OUTPUT=data/out/buf.gpkg

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.

Explanation

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 the wrong key.

Grid comparing four ways to discover an algorithm id and when to use each.
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 provider registry, the CRS database and the expression engine; Processing.initialize() builds on all three. Calling them the other way round leaves you with an application whose registry was populated against nothing, which usually manifests as an empty algorithm list rather than an error.

The id itself has history. Many algorithms began as Python implementations under the qgis: prefix and were rewritten in C++ under native: for speed. QGIS keeps aliases for a number of the old ids, but not all, and third-party prefixes have moved too (saga: became sagang: when SAGA Next Gen replaced the old provider). This is why hard-coding ids from a blog post is fragile, and why the History panel β€” which reflects your installed version β€” is the right source.

Finally, provider availability is a property of the installation, not the code. A GRASS algorithm requires GRASS on the machine and the provider registered in the process. In a Docker image built from qgis/qgis, that means installing the GRASS packages explicitly. Checking the provider list at start-up and failing with a clear message costs three lines and saves a confusing debugging session on a machine you cannot see.

Edge cases or notes

  • processing.run vs processing.runAndLoadResults: The latter only makes sense with a GUI. In a standalone script it will not load anything into a canvas that does not exist.
  • 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 have their own prefixes: Models saved in the profile register under model:, and script algorithms under script:, only after the profile is loaded.
  • Version differences are real: Parameters are added and renamed across QGIS releases. Pin the QGIS version in Docker if a pipeline must keep working unattended.
  • qgis: ids may still resolve: Aliases exist for compatibility, so an old id sometimes works β€” do not assume that means it is current.

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:?

native: for anything that exists there β€” it is the C++ implementation and it is faster. Many qgis: ids are aliases kept for compatibility, and some have been removed. Confirm against your own registry.

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?

Their providers are separate plugins that must be installed and registered. In a headless environment you usually have to add the provider to the registry explicitly after Processing.initialize().

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. Pin the QGIS version for scheduled pipelines, and resolve ids through a helper that fails loudly with near-matches when one disappears.