A Node-RED-like visual workflow editor with a Python backend. Create workflows by connecting Python nodes that process and route messages.
PyNodeExample720.mp4
- Visual Node Editor: Drag-and-drop interface for creating workflows
- Python Backend: All nodes are Python classes that can be easily extended
- Fully Extensible: Third-party nodes can be added without modifying core code
- UI Components: Nodes can define interactive controls (buttons, toggles, displays) in their cards
- Node-RED Compatible Messages: Message structure with
payloadandtopicfields - Multiple Workspaces: Several independent flows, each with its own deploy/run state
- REST API: Complete API for programmatic workflow management
- Export/Import: Save and load workflows as JSON
- Dynamic Properties: Node properties and UI components defined in node classes
Around 85 node types ship with PyNode, grouped by palette category. Every node carries its own documentation in the editor's Information panel (βΉοΈ tab).
| Category | Nodes |
|---|---|
| Common | Inject, Debug, Link In / Link Out (named channels between flows) |
| Function | Function (custom Python), Change, Filter, Delay, Batch, Join, Split, Range, Slider |
| Input | Camera, Frame Source, Video Reader, Image Upload, Message Reader, Omron Camera |
| Output | Image Viewer, Image Writer, Video Writer, Message Writer, Roboflow Upload |
| Logic | Switch (route on conditions), Gate, Sync, Auto Sync |
| Network | MQTT In / Out, UDP In / Out, TCP In / Out, REST Endpoint, Webhook, mDNS Broadcast / Discovery |
| Vision | YOLO, Inference, Tracker, Supervision Tracker, Crop, Slice Image / Slice Collector, Draw Predictions, Confidence Filter, Label Filter, Merge Predictions, Image Format, Qwen3-VL, vLLM |
| Analysis | BBox Metrics, Polygon Metrics, Point in Shape |
| OpenCV | ~28 image-processing nodes: Blur, Threshold, Morphology, Edge Detector, Find Contours, Perspective, Resize, Rotate, Colormap, Histogram, FFT, Template Match, RealSense Depth, and more |
| Node Probes | Rate Probe, Queue Length Probe, Counter |
Highlights:
- Slider β an interactive on-card slider that stamps a live value onto any
msgpath; drag it and downstream nodes update immediately. - UDP In / Out, TCP In / Out β move messages (including video frames) between PyNode instances or to anything else that speaks the wire format. Their Information panel contains a ready-made Node-RED flow you can copy and import, pre-set to the node's port.
- Video Reader β video-file playback with on-card transport controls.
- Inference β a multi-backend inference node (ONNX Runtime, Ultralytics) that discovers available engines at startup.
PyNode is published on PyPI as pynode-flow (the import package stays pynode):
# Core install
pip install pynode-flow
# ...or everything PyPI-installable (all optional nodes)
pip install "pynode-flow[full]"
# Run it
pynodeor specify a port:
pynode --port 8080See INSTALL.md for the full list of extras (vision, mqtt,
camera, inference, vlm, upload, discovery, full) and how to install
per-node dependencies with pynode-install-nodes.
Clone the repository:
git clone https://github.com/olkham/pynode.git
cd pynodeThe setup scripts will create a virtual environment, detect CUDA if available, install PyTorch with appropriate GPU support, and install all dependencies.
Windows:
# Use Python from PATH
setup.bat
# Or specify Python path
setup.bat "C:\Python312\python.exe"Linux/Mac:
chmod +x setup.sh
./setup.shThe scripts will:
- Create a virtual environment in
appenv/ - Detect CUDA version and install matching PyTorch build
- Install CPU-only PyTorch if CUDA is not detected
- Install all required dependencies
- Optionally install node-specific dependencies
Activate the environment:
- Windows:
appenv\Scripts\activate.bat - Linux/Mac:
source appenv/bin/activate
If you prefer manual installation or have specific requirements:
# Core install (extras optional β see INSTALL.md)
pip install -e .
# With optional extras: specific node groups...
pip install -e ".[vision,mqtt]"
# ...or everything PyPI-installable
pip install -e ".[full]"
pip installonly pulls the dependencies and extras declared inpyproject.toml; it does not run each node'srequirements.txt. The[full]extra covers every PyPI-installable node. For the few nodes that need a vendor SDK (e.g. Omron'sstapipy), runpynode-install-nodesafter installing.
pynode
# or
python -m pynodeNavigate to http://localhost:5000
Twelve ready-made learning workflows ship with PyNode β from a two-node hello world up to a camera β YOLO β filter β MQTT alert pipeline (including a "blur every detected person" privacy flow, a UDP socket bridge and a slider-driven crop). Load any of them straight from the editor: β° menu β Examples β pick one, then press Deploy. See pynode/static/examples/README.md for the guided tour and per-example prerequisites.
PyNode persists workflows under <data dir>/workflows/ (workflow.json plus timestamped backups in _backups/). The data directory is resolved in this order:
pynode --data-dir <path>CLI flag,PYNODE_DATA_DIRenvironment variable,- the source checkout root when running from a git clone / editable install (i.e.
pyproject.tomlsits next to thepynodepackage β this keeps the familiarworkflows/folder in the repo), ~/.pynodeotherwise (e.g. a regularpip install).
The resolved location is logged at startup (Workflow data directory: ...).
Nodes that download or generate model weights (e.g. the YOLO node's .pt
files and exported OpenVINO models) write them into a shared models
directory instead of the process working directory. It is resolved in this
order:
pynode --models-dir <path>CLI flag,PYNODE_MODELS_DIRenvironment variable,<data dir>/modelsotherwise β so<repo>/modelsfor a source checkout and~/.pynode/modelsfor a regularpip install.
Upgrading: older PyNode versions downloaded model files into whatever the working directory happened to be, so stray
.ptfiles and*_openvino_model/folders may exist in the repo root,pynode/models/orpynode/nodes/. PyNode still reads models from those legacy locations, but nothing is migrated automatically β you can move them into the models directory manually when convenient.
PyNode executes arbitrary Python by design (e.g. FunctionNode runs whatever code is in the workflow), so anyone who can reach the API can run code on the host. Authentication is the trust boundary β secure the server before exposing it beyond your own machine:
- API key: start with
pynode --api-key <secret>(or set thePYNODE_API_KEYenv var). All/api/requests then require the key via theX-API-Keyheader or anapi_keyquery parameter; the web UI prompts for it on first load and remembers it in the browser. Unset/empty = no authentication (the default). - CORS: restrict allowed browser origins with
pynode --cors-origins http://localhost:5000,https://myhost(or thePYNODE_CORS_ORIGINSenv var). Default is*(all origins). - Bind locally: when you don't need network access, run
pynode --host 127.0.0.1so the server is only reachable from the local machine.
PyNode can be run in a Docker container with GPU support (CUDA 12.6).
For mDNS service discovery to work correctly inside Docker, set the HOST_IP environment variable to your host machine's IP address.
# Set the host IP address
export HOST_IP=$(hostname -I | awk '{print $1}')
# Start the container
docker compose up -dThe container will:
- Use NVIDIA CUDA 12.6 runtime (requires nvidia-docker)
- Install PyTorch with CUDA 12.6 support
- Install all dependencies including node-specific packages
- Expose port 5000 for web interface
- Support mDNS broadcasting with the correct host IP
Why set HOST_IP?
When using the mDNS Broadcast Node inside Docker, it needs to advertise the host machine's IP address rather than the container's internal IP so other devices on your network can discover and connect to the service.
Access the application:
- Web UI:
http://localhost:5000 - From other devices:
http://<your-host-ip>:5000
GPU Access:
The Docker setup requires NVIDIA Container Toolkit to be installed on the host system.
For more details, see DOCKER.md.
PyNode is designed to be easily extended with custom nodes:
- Creating Custom Nodes - Complete guide to creating your own nodes
- UI Components Guide - Add interactive controls to your nodes
- Extensibility Overview - Architecture and design principles
pynode/ # Project root
βββ pynode/ # Main package
β βββ __main__.py # Entry point for 'python -m pynode'
β βββ _version.py # Version (generated by setuptools_scm at build)
β βββ main.py # CLI application
β βββ config.py # Data/models directory resolution
β βββ server.py # Flask app factory, static routes, auth
β βββ node_registry.py # Node type discovery and metadata cache
β βββ workflow_engine.py # Message routing and node execution
β βββ workflow_manager.py # Multiple workflows, persistence and backups
β βββ install_nodes.py # 'pynode-install-nodes' per-node dependency installer
β βββ api/ # REST API blueprints
β β βββ nodes.py # Node CRUD, actions, frames, uploads
β β βββ workflows.py # Workflow + multi-workflow endpoints
β β βββ services.py # Shared services (MQTT brokers)
β β βββ sse.py # Server-sent events (debug stream)
β β βββ uploads.py
β βββ nodes/ # Node implementations (each in its own folder)
β β βββ base_node.py # BaseNode class
β β βββ info.py # Info builder for node help panels
β β βββ messages.py # Message construction helpers
β β βββ InjectNode/ # Generate messages
β β βββ FunctionNode/ # Custom Python code
β β βββ SocketNode/ # UDP/TCP nodes + Node-RED interop flow
β β βββ UltralyticsNode/ # YOLO detection
β β βββ InferenceNode/ # Multi-backend inference engines
β β βββ OpenCV/ # ~28 OpenCV operations
β β βββ ... # ~50 node folders in total
β βββ static/ # Web UI
β βββ index.html
β βββ style.css
β βββ js/ # JavaScript modules (ES modules, no build step)
β β βββ main.js # Entry point
β β βββ nodes.js # Node rendering
β β βββ properties.js # Properties panel
β β βββ selection.js # Selection + Information panel
β β βββ ...
β βββ examples/ # Bundled example workflows + manifest.json
β βββ images/ # UI assets
βββ tests/ # pytest suite
βββ examples/ # Programmatic (Python API) examples
βββ docs/ # Documentation files
β βββ CUSTOM_NODES.md # Guide to creating custom nodes
β βββ CREATING_NODES.md # Node authoring reference
β βββ UI_COMPONENTS.md # Guide to node UI components
β βββ EXTENSIBILITY.md # Extensibility overview
βββ workflows/ # Persisted workflows (workflow.json + _backups/)
βββ models/ # Downloaded/exported model weights
βββ setup.py # Shim; metadata lives in pyproject.toml
βββ setup.bat / setup.sh # Setup scripts
βββ requirements.txt # Convenience installer (deps live in pyproject.toml)
βββ pyproject.toml # Package metadata, dependencies and build config
βββ INSTALL.md # Installation guide
βββ DOCKER.md # Docker setup
βββ docker-compose.yml # Docker compose config
βββ Dockerfile # Docker build (CUDA)
βββ Dockerfile.cpu # Docker build (CPU only)
βββ README.md
workflows/andmodels/are the defaults for a source checkout; both move with--data-dir/--models-dir(see above).
PyNode is fully extensible. All node information (visual properties, property schemas, and behavior) is contained within the node class itself. The main application has no hardcoded knowledge of specific node types.
For a complete guide, see docs/CUSTOM_NODES.md
Here is a simple example:
from pynode.nodes.base_node import BaseNode
class MyCustomNode(BaseNode):
"""Example custom node."""
category = 'custom'
color = '#FFA07A'
border_color = '#FF7F50'
text_color = '#000000'
properties = [
{
'name': 'multiplier',
'label': 'Multiplier',
'type': 'text'
}
]
def __init__(self, node_id=None, name="custom"):
super().__init__(node_id, name)
self.configure({
'multiplier': 2
})
def on_input(self, msg, input_index=0):
payload = msg.get('payload')
multiplier = float(self.config.get('multiplier', 2))
new_payload = payload * multiplier
new_msg = self.create_message(
payload=new_payload,
topic=msg.get('topic', '')
)
self.send(new_msg)Register the node with the workflow engine used by your application:
from pynode.workflow_engine import WorkflowEngine
from my_custom_node import MyCustomNode
engine = WorkflowEngine()
engine.register_node_type(MyCustomNode)Messages follow the Node-RED format:
{
'payload': 'any data type',
'topic': 'string',
'_msgid': 'unique-id',
# ... any additional properties
}All /api/ routes require the API key when the server is started with
--api-key (see Securing PyNode).
GET /api/node-types- Palette metadata for every registered node type (properties, colours, info HTML)
GET /api/nodes- List all nodesPOST /api/nodes- Create a nodeGET /api/nodes/<id>- Get node detailsPUT /api/nodes/<id>- Update nodeDELETE /api/nodes/<id>- Delete nodePOST /api/nodes/<id>/<action>- Trigger a node action (e.g. inject)GET|POST /api/nodes/<id>/enabled- Read or set the node's enabled statePUT /api/nodes/<id>/position- Move a node on the canvasGET /api/nodes/<id>/frame- Latest frame from an image-producing nodeGET /api/nodes/<id>/stream- MJPEG stream from an image-producing nodeGET /api/nodes/<id>/rate- Throughput for a probe node
POST /api/connections- Create connectionDELETE /api/connections- Delete connection
GET /api/workflow- Export workflowPOST /api/workflow- Import workflowPOST /api/workflow/deploy-changes- Deploy (optionally only modified nodes)POST /api/workflow/restart- Restart the deployed workflowPOST /api/workflow/stop- Stop the deployed workflowPOST /api/workflow/save- Persist to diskGET /api/workflow/deployed- The currently deployed definitionGET /api/workflow/stats- Get statistics
GET /api/workflows- List workflowsPOST /api/workflows- Create a workflowPUT /api/workflows/<id>- Rename/update a workflowDELETE /api/workflows/<id>- Delete a workflowPUT /api/workflows/active- Switch the active workflow
GET /api/nodes/<id>/debug- Get debug messagesDELETE /api/nodes/<id>/debug- Clear debug messagesGET /api/debug/stream- Server-sent event stream of debug messages
GET /api/version- Running PyNode versionGET /api/link-channels- Known Link In/Out channel namesGET|POST /api/services/mqtt,GET|PUT|DELETE /api/services/mqtt/<id>,POST /api/services/mqtt/test- Shared MQTT broker definitionsPOST /api/upload/file,POST /api/nodes/<id>/upload_image,POST /api/nodes/<id>/upload_video- File uploads
from pynode.workflow_engine import WorkflowEngine
from pynode.nodes import InjectNode, FunctionNode, DebugNode
engine = WorkflowEngine()
engine.register_node_type(InjectNode)
engine.register_node_type(FunctionNode)
engine.register_node_type(DebugNode)
inject = engine.create_node('InjectNode', name='source')
inject.configure({'payload': 10, 'payloadType': 'num'})
func = engine.create_node('FunctionNode', name='multiply')
func.configure({'func': 'msg["payload"] = msg["payload"] * 2\nreturn msg'})
debug = engine.create_node('DebugNode', name='output')
engine.connect_nodes(inject.id, func.id)
engine.connect_nodes(func.id, debug.id)
engine.start()
engine.trigger_inject_node(inject.id)
messages = engine.get_debug_messages(debug.id)
print(messages)- Add Nodes: Drag nodes from the palette onto the canvas
- Connect Nodes: Click an output port and drag to an input port
- Configure Nodes: Click a node to show its properties panel; the βΉοΈ Information tab in the right sidebar documents the selected node
- Test Workflow:
- Click Deploy to activate the workflow (the βΌ menu offers Deploy Modified / Full, Restart and Stop)
- Use the Inject button on inject nodes to send messages
- View output in the π Debug tab of the right sidebar
- Multiple Flows: Use the + tab to add another workspace; each flow deploys and runs independently
- Save/Load: Use β° menu β Export / Import to save workflows as JSON
- Create a new Python class in
pynode/nodes/ - Inherit from
BaseNode - Override
on_input()for message processing - Define
propertiesfor UI configuration - Create
requirements.txtin the node's directory if needed - Reload the server to detect the new node
Nodes can:
- Modify message payload
- Add or remove message properties
- Send to multiple outputs
- Send multiple messages
- Filter messages
- Store state between messages
- Background Processing: Use threading for long-running operations
- External APIs: Make HTTP requests from function nodes
- Database Integration: Store and retrieve data from databases
- File I/O: Read and write files in custom nodes
- Scheduling: Implement timed node execution
- β¬ Centralize more strings / constants
- β¬ Test all nodes
- β Add multiple workspaces / canvases
- β¬ OCR (PaddlePaddle) Node
- β Qwen VLM Node
- β¬ SAM3 Node
- β REST Endpoint Node
- β Webhook Node
- β UDP/TCP Node
- β¬ Bird seed level monitor
- β¬ Capture data and send to Roboflow
- β¬ Track objects time in zone
- β¬ Live VLMs
- β¬ ANPR (Detect, Crop, OCR, MQTT)
- β YOLO: Add custom model support
- β YOLO: Add custom target HW string
- β¬ Roboflow: RF-DETR
- β Roboflow: Upload images
- β¬ DeepSort: Add option to use a different feature extractor model
Versions follow the vX.Y.Z git tags. The running build is shown next to the
title in the editor and returned by GET /api/version.
New nodes
- UDP In / Out and TCP In / Out (
SocketNode) β move messages, including video frames, between PyNode instances or any program that speaks the wire format. UDP uses a chunked binary protocol (PNB1) for large payloads; TCP uses newline-delimited JSON. Each node's Information panel carries a copy-and-import Node-RED flow pre-set to that node's port, with a worked interop example and a standaloneudp_probe.pydiagnostic inpynode/nodes/SocketNode/interop/. - Slider (
ControlSliderNode) β an interactive on-card slider that stamps a live value onto anymsgpath, with Send on Change to emit while dragging even with no input wired.
Improvements
- Range node: configurable output format.
- Crop node: custom-path bbox source with format and coordinate-space selectors.
- Image Upload node: Repeat Send re-emits the image at a set rate (and the pacing bug that capped 30fps at ~11fps is fixed).
- Function node: built-in code templates.
- Wider type hints, payload validation and error handling across nodes.
- Docker: CUDA 12.6 runtime base image, version reporting, and better CUDA
detection in
setup.sh. - Two new bundled examples: UDP Socket Bridge and Slider-Driven Crop.
- Performance: fewer message copies for nodes with a single output.
- YOLO node: fixed model storage locations (weights now land in the models directory rather than the working directory).
- Link In / Link Out nodes for passing messages between flows by channel name.
- Per-flow debug panel and per-flow run state.
- Camera and rate-probe fixes; MQTT improvements.
- The first bundled example workflows and general UI/UX fixes.
- Removed the end-of-life Geti node and its SDK dependency.
- First PyPI release as
pynode-flow.
MIT License - Feel free to use and modify.
Contributions are welcome. Add new node types, improve the UI, or enhance the engine.