User Defined Processes
In openEO, a User Defined Process (UDP) allows users to define a custom process graph that can be reused across different workflows. We have often noticed that many users repeatedly perform the same processing steps, which takes up valuable time and effort. A UDP helps to encapsulate these steps into a single, reusable process. It is useful for sharing a cloud-mask routine, an index calculation, or a project-specific workflow.
Create a reusable workflow
A UDP is not a single raster operation; it packages a process graph behind a reusable identifier. Use it when multiple users or applications should call the same cloud mask, index, or compositing workflow. A UDP has an identifier, summary, parameter definitions, and a process graph; once saved to a backend, it can be called like another process in a new graph.
Some good candidate examples for a UDP could be:
- A cloud or quality-mask routine used by several projects.
- A named index or feature-engineering recipe with configurable bands.
- A standard compositing workflow for one data product.
- An inference or prediction workflow that is repeatedly used across projects.
A backend or workspace stores a UDP. The graph itself can use standardised processes, but the UDP identifier, permissions, and any backend-specific process used inside it are not automatically portable.
See the openEO API documentation for the current UDP endpoints and the process reference for graph building blocks.
Define clear parameters
Declare every value that callers must supply. Parameter definitions document the interface, validate inputs, and allow a graph to refer to caller-provided values instead of fixed literals. The Python client provides helpers for common types, including Parameter.datacube, Parameter.number, Parameter.string, Parameter.spatial_extent, Parameter.temporal_interval, and Parameter.geojson.
from openeo.api.process import Parameter
spatial_extent = Parameter.spatial_extent(
name="spatial_extent",
description="Area to process.",
)
temporal_extent = Parameter.temporal_interval(
name="temporal_extent",
description="Date range to process.",
)Use a default only when a value is genuinely optional. For more specialised inputs, create a Parameter with a JSON Schema schema argument. Ensure every Parameter used in the graph is also included when the UDP is saved.
For example, define an array of band names with a JSON Schema:
bands = Parameter(
name="bands",
description="Bands to process.",
schema={"type": "array", "items": {"type": "string"}},
default=["B04", "B08"],
)Build and store a UDP
Build the process graph with the same Python-client methods used for an ordinary workflow, passing Parameter objects where the future caller should provide values. This example creates a reusable, parameterised Sentinel-2 loading and NDVI workflow:
import openeo
from openeo.api.process import Parameter
connection = openeo.connect("https://openeo.example.org").authenticate_oidc()
spatial_extent = Parameter.spatial_extent("spatial_extent", "Area to process.")
temporal_extent = Parameter.temporal_interval("temporal_extent", "Date range to process.")
cube = connection.load_collection(
"SENTINEL2_L2A",
spatial_extent=spatial_extent,
temporal_extent=temporal_extent,
bands=["B04", "B08"],
)
ndvi = (cube.band("B08") - cube.band("B04")) / (cube.band("B08") + cube.band("B04"))
connection.save_user_defined_process(
user_defined_process_id="ndvi_for_extent",
process_graph=ndvi,
parameters=[spatial_extent, temporal_extent],
summary="Calculate NDVI for an area and time range.",
)The process identifier is the stable public interface for users and applications. Treat parameter names, defaults, and output semantics as a versioned contract. Inspect the generated graph using print_json() before saving when troubleshooting.
When a graph already exists as JSON or a Python dictionary, pass it directly as process_graph to save_user_defined_process. The dictionary must include a result: true node and must declare every value referenced through from_parameter.
Keep the code or JSON specification used to create a UDP in version control. Saving a UDP registers it on a backend, but does not replace a reviewable source definition or release history.
Export a portable definition
Use build_process_dict to create the complete metadata and process-graph specification for review, version control, or deployment to another service:
import json
from openeo.rest.udp import build_process_dict
definition = build_process_dict(
process_id="ndvi_for_extent",
process_graph=ndvi,
parameters=[spatial_extent, temporal_extent],
summary="Calculate NDVI for an area and time range.",
)
with open("ndvi_for_extent.json", "w") as file:
json.dump(definition, file, indent=2)The exported definition is portable source, not a guarantee of cross-backend execution. Validate it on every target backend.
Invoke a UDP
After saving, a UDP is available to subsequent process graphs on the same backend. Use datacube_from_process for a UDP that returns a data cube, then continue composing processes or download/submit it as a job.
ndvi = connection.datacube_from_process(
"ndvi_for_extent",
spatial_extent={"west": 4.0, "south": 51.0, "east": 4.1, "north": 51.1},
temporal_extent=["2024-06-01", "2024-06-30"],
)
ndvi.download("ndvi.tiff", format="GTiff")For a UDP returning a scalar, array, or other non-cube value, build its call with openeo.processes.process("udp_id", ...) and execute that process graph. A UDP can also call another UDP, allowing teams to assemble a small library of tested building blocks.
from openeo.processes import divide, process, subtract
from openeo.api.process import Parameter
fahrenheit = Parameter.number("fahrenheit", "Temperature in degrees Fahrenheit.")
celsius = divide(subtract(fahrenheit, 32), 1.8)
connection.save_user_defined_process("fahrenheit_to_celsius", celsius, parameters=[fahrenheit])
result = connection.execute(process("fahrenheit_to_celsius", fahrenheit=70))Applicability and constraints
UDPs are an openEO API feature, not a Python-client-only feature. The Python client provides the examples on this page as convenience methods; other openEO clients and direct API requests can also create and invoke UDPs.
A UDP is not automatically available across all backends. It is saved to a single backend or workspace, and it can only be called from that same service unless it is exported and deployed elsewhere. A UDP built entirely from standardised processes can often be recreated on another backend, but success still depends on that backend supporting all processes, parameter schemas, result types, and required collections. Backend-specific processes and custom runtime assumptions reduce portability.
Before publishing a UDP for a team, check the target backend’s process support and test the saved definition with representative inputs. Use the openEO Hub to compare advertised process support, while treating its results as an overview rather than a guarantee of identical behaviour or resource limits.