Create and run Python nodes¶
Use a User Function node for a short function entered in an editor (see
Code nodes). To reuse a Python implementation
across scripts, define a NodeDefinition and register it with a NodeRegistry.
The built-in adapter catalog lists the nodes
already supplied by FlowGraph; you do not need to redefine those nodes.
Complete example¶
Run this script with python example.py in an environment with flowgraph
installed (or uv run python example.py from this repository). It creates a
custom Scale Integer node, connects the built-in Integer Input to it,
executes the graph, and saves and reloads it:
from collections.abc import Mapping
from typing import Any
from flowgraph.adapters.data_types import INTEGER, ParameterKind
from flowgraph.application.node_registry import create_node_registry
from flowgraph.application.workflow_core import (
ExecContext,
NodeDefinition,
ParameterDefinition,
PortDefinition,
PortDirection,
WorkflowEdge,
WorkflowExecutor,
WorkflowGraph,
)
from flowgraph.application.workflow_io import execute_workflow_file, save_workflow
def scale_integer(
inputs: Mapping[str, Any],
parameters: Mapping[str, Any],
_context: ExecContext,
) -> Mapping[str, Any]:
return {"result": inputs["value"] * parameters["factor"]}
SCALE_INTEGER = NodeDefinition(
id="scale-integer",
label="Scale Integer",
description="Multiply an integer by a configurable factor.",
icon="mdi-multiplication",
requirements=(),
ports=(
PortDefinition("value", PortDirection.INPUT, INTEGER, "Value"),
PortDefinition("result", PortDirection.OUTPUT, INTEGER, "Result"),
),
parameters=(ParameterDefinition("factor", ParameterKind.INTEGER, "Factor", 2),),
executor=scale_integer,
)
registry = create_node_registry() # Includes the built-in set-int node.
registry.register(SCALE_INTEGER)
graph = WorkflowGraph()
graph.add_node(registry.create_instance("set-int", "source", parameters={"value": 7}))
graph.add_node(registry.create_instance("scale-integer", "scale", parameters={"factor": 3}))
graph.add_edge(WorkflowEdge("source", "value", "scale", "value"), registry)
assert not graph.validate(registry)
result = WorkflowExecutor(registry).run(graph)
assert result.node_outputs["scale"]["result"] == 21
print(result.node_outputs["scale"]["result"])
save_workflow(graph, "scaled-workflow.json")
# Register the custom definition again before loading in a different process.
reloaded = execute_workflow_file("scaled-workflow.json", registry=registry)
assert reloaded.node_outputs["scale"]["result"] == 21
scale-integer is the reusable definition ID; scale is this graph's unique
instance ID. Edges use instance IDs and the exact port names, not display
labels. factor is automatically an optional input port as well as a parameter:
if connected, its incoming value overrides the configured value for that run.
Set port=False on a ParameterDefinition to keep a setting local to the node.
Required inputs must be connected (or supplied through the workflow interface).
The executor receives named inputs, instance parameters, and an ExecContext.
The context contains parallel_tasks, gpu_available, and
connected_output_ports. Return a mapping keyed by declared output names with
values matching their DataType; return {} for a node without outputs.
FlowGraph validates edges and outputs and reports node execution failures with
the instance and definition IDs. The example's requirements=() means no
additional direct execution dependency beyond FlowGraph. If your executor
needs another distribution, declare it with requirements=("py:package-name",);
this is metadata, not an installer. Consult Workflow nodes
for port types, parameter behavior, and requirements conventions.
Reuse, publish, and load¶
Saved workflow JSON records the definition ID, not its Python executor.
When loading or executing that file later, import the module defining your node
and register the definition in the registry passed to load_workflow() or
execute_workflow_file(). create_node_registry() contains the built-in nodes;
register your own definition on that registry to use both. For a custom-only
registry, construct NodeRegistry() and register every definition used by the
graph. registry.register() rejects duplicate IDs.
To expose a graph value to a Python caller, call
graph.add_output("scaled", "scale", "result", registry) and then use
graph.execute({}, registry)["scaled"] (or access the per-instance value from
WorkflowExecutor.run(graph).node_outputs). To accept a value supplied by the
caller rather than the built-in source, omit the source and edge and call
graph.add_input("number", "scale", "value", registry); then execute with
graph.execute({"number": 7}, registry). Exported inputs must not also be
connected to another node inside the graph. See the Workflows guide
for running saved workflows and exporting graph interfaces.
Export extra nodes for the Web Editor¶
The Web Editor reads catalog JSON for its menu, ports, and parameter editors.
A Python NodeRegistry.register() call alone does not update the Web Editor.
To export your own definitions alongside the built-in nodes, add this after
the SCALE_INTEGER definition in the example above (or import it from your
custom module):
from flowgraph.adapters import AdapterGroup
from flowgraph.application.workflow_web_catalog import write_workflow_web_catalog
write_workflow_web_catalog(
"node-catalog.json",
extra_groups=(AdapterGroup("My Nodes", (SCALE_INTEGER,)),),
)
Run the script with python example.py (or uv run python example.py from the
core checkout). The output path's parent directory must already exist. To
export several nodes, pass them in the group's tuple; to create multiple menu
groups, pass several AdapterGroup objects. Node definition IDs must be unique
across both built-in and extra groups; duplicates raise ValueError. The
exporter includes each node's static ports, parameters, icon, requirements, and
other metadata but not its Python executor. Dynamic ports may need editor
support beyond this static metadata.
Open the Web Editor website and select Load custom catalog in the Nodes
sidebar. Choose the generated node-catalog.json; your My Nodes group will
appear alongside the built-in groups. You do not need to clone or build the Web
Editor. The imported catalog is used only for the current page session: reload
the page to restore the bundled catalog, or select Use bundled catalog. The
editor will not switch catalogs if the current workflow (including a nested
workflow) contains nodes absent from the catalog you are switching to.
Importing a catalog enables authoring and displaying custom nodes, not browser execution: the current WASM worker creates a built-in-only registry. Browser execution requires bundling the custom Python implementation into the browser runtime and registering it there. For local Python execution of a workflow exported from the website, register the custom definition before loading the workflow as shown above. Treat third-party workflow files and user code as trusted executable content; the workflow format is not a sandbox.
If contributing a node to the built-in catalog instead, add its definition to
an AdapterGroup in src/flowgraph/adapters/__init__.py, document its ID and
behavior on the corresponding adapter page,
and add tests.