Define custom node types
The default palette covers common robotics / game-AI primitives, but
every real project ends up needing domain-specific actions —
DriveToWaypoint, OpenGripper,
SpawnEnemyWave. This guide shows how to add your own and
ship them as a reusable JSON library.
Adding a single custom node
Click ➕ Add Custom Node at the bottom of the sidebar. A modal opens with three fields:
-
Name — a stable identifier. Used as the JSON key
when you export a tree. Cannot contain spaces; camelCase is
conventional (
DriveToWaypoint). - Type — Composite / Action / Condition / Decorator. Determines the visual color in the palette.
- Port config — optional JSON array describing each port.
Port config syntax
A port is {"name": "...", "type_name": "...", "mode": 0}:
[
{ "name": "target", "type_name": "string", "mode": 0 },
{ "name": "speed", "type_name": "float", "mode": 0 }
]
Fields:
| Field | Type | Notes |
|---|---|---|
name |
string | Port key, surfaced as a field on the node body. |
type_name |
string |
Free-form hint: string, int,
float, bool, any, or your
own type name.
|
mode |
0 or 1 | 0 = INPUT, 1 = OUTPUT. |
If you leave the port config blank, the node is a no-port leaf — the user sees a plain body without editable fields.
Worked example: DriveToWaypoint
A simple action node for a ground robot that takes a target waypoint and a max speed.
- Click Add Custom Node.
- Name:
DriveToWaypoint - Type: Action node (Action)
-
Port config (paste this):
[ { "name": "target", "type_name": "string", "mode": 0 }, { "name": "max_speed", "type_name": "float", "mode": 0 } ] - Save. The new node appears in the palette under Action.
Drag it onto the canvas, click the body, and you'll see two text
fields — target and max_speed. Type
"kitchen" and 0.5. Export the tree and your
JSON now has:
{ "name": "DriveToWaypoint", "type": 1, "ports": { "target": "kitchen", "max_speed": 0.5 } }
Bulk import: ship a node library
For teams, the efficient workflow is to maintain a single JSON file with all your custom nodes and import it once per environment. Click 📥 Import Nodes in the header and paste:
{
"nodes": [
{
"name": "DriveToWaypoint",
"type": 1,
"description": "Drive to named waypoint at given speed",
"icon": "🚗",
"ports": [
{ "name": "target", "type_name": "string", "mode": 0 },
{ "name": "max_speed","type_name": "float", "mode": 0 }
]
},
{
"name": "HasCargo",
"type": 3,
"description": "True if gripper is currently holding an object",
"icon": "📦"
}
]
}
The editor adds both nodes to the palette. Duplicates (by name) are skipped with a status message; invalid entries are reported.
Editing and removing custom nodes
Hover over a custom node in the palette and two small icons appear: ✎ (edit) and 🗑 (delete). Click ✎ to reopen the same modal pre-filled with the current values; click 🗑 to remove the template.
Deleting a custom template does not remove existing canvas nodes of that type. They'll keep displaying, and any port values you set will be preserved. But you can't add new ones of that type until you re-add the template.
Built-in templates are immutable
The default nodes (Sequence, Wait, IsEnable, …) ship with the editor and cannot be edited or deleted. If you need a near-clone, just add a custom node with a similar name. The intent is that upgrading the editor never silently breaks your existing trees.
Best practices
-
Stable names. Once you export a tree using
DriveToWaypoint, don't rename it — existing JSON files will break. -
Version your library. Keep the bulk-import JSON in
your repo and bump a version field. Bots that consume the trees can
read
tree._formatVersionto migrate. -
Reuse the BTCPP path. If you also run
BehaviorTree.CPP, use the same node names. The editor can import
BTCPP XML with a
TreeNodesModelblock directly — no need to maintain two libraries.