PNode Plugin API
Add custom nodes to PNode from your own Bukkit/Paper plugin using the public API jar. The API jar is free to use and requires no source access.
Download API Jar
Select a version and download the API jar to add as a compileOnly dependency.
1. Add the API Jar
Download pnode-api-<version>.jar and place it in your project's
libs/ folder. Add it as a compileOnly dependency — do not
shade or shadow it, PNode provides the classes at runtime.
Gradle (Kotlin DSL)
dependencies {
compileOnly(files("libs/pnode-api-<version>.jar"))
}
Maven
<dependency>
<groupId>dev.pnode</groupId>
<artifactId>pnode-api</artifactId>
<version>1.2.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/pnode-api-<version>.jar</systemPath>
</dependency>
Declare PNode as a dependency in your plugin.yml:
# soft-depend if PNode is optional, depend if required
softdepend: [PNode]
2. Obtain the API Instance
Retrieve PNodeAPI from Bukkit's ServicesManager in your onEnable:
import dev.pnode.api.PNodeAPI;
import org.bukkit.plugin.RegisteredServiceProvider;
@Override
public void onEnable() {
RegisteredServiceProvider<PNodeAPI> rsp =
getServer().getServicesManager().getRegistration(PNodeAPI.class);
if (rsp == null) {
getLogger().warning("PNode not found — custom nodes will not be registered.");
return;
}
PNodeAPI pnode = rsp.getProvider();
pnode.registerNode(this, new MyCustomNode());
}
You do not need to unregister nodes in onDisable().
PNode automatically removes all nodes from a plugin when that plugin shuts down.
3. Implement a Custom Node
Implement dev.pnode.node.NodeHandler. Define the node's appearance and
ports once in a static NodeDefinition, then handle execution in
execute().
import dev.pnode.node.*;
public class GreetPlayerNode implements NodeHandler {
// Define once — type ID must be unique; prefix with your plugin name
private static final NodeDefinition DEF = new NodeDefinition(
"myplugin/greet-player", // type ID
"Greet Player", // display name in editor palette
"My Plugin", // sidebar category
"#9b59b6", // header colour (hex)
"Sends a greeting to a player."
)
.addInput(NodePort.execIn())
.addInput(NodePort.input("player", PortType.PLAYER.id, "Player to greet"))
.addOutput(NodePort.execOut());
@Override
public NodeDefinition getDefinition() { return DEF; }
@Override
public void execute(NodeInstance node, ExecutionContext ctx, NodeExecutorCallback cb) {
Object playerObj = cb.resolveInput(node, 1); // slot 1 = "player" input
if (playerObj instanceof org.bukkit.entity.Player player) {
player.sendMessage("Hello, " + player.getName() + "!");
}
cb.followExec(node); // advance execution to the next connected exec node
}
}
yourplugin/node-name (lowercase, hyphens ok).
This avoids collisions with PNode's built-in nodes and other plugins.
4. Port Types
Use PortType constants when defining input/output ports:
| Constant | Java Type | Notes |
|---|---|---|
PortType.EXEC | — | Execution flow wire (use NodePort.execIn() / execOut()) |
PortType.STRING | String | |
PortType.NUMBER | Double | |
PortType.BOOLEAN | Boolean | |
PortType.PLAYER | org.bukkit.entity.Player | |
PortType.SENDER | org.bukkit.command.CommandSender | |
PortType.ENTITY | org.bukkit.entity.Entity | |
PortType.WORLD | org.bukkit.World | |
PortType.LOCATION | org.bukkit.Location | |
PortType.VECTOR | org.bukkit.util.Vector | |
PortType.EVENT | org.bukkit.event.Event | |
PortType.LIST | java.util.List | |
PortType.MATERIAL | org.bukkit.Material | |
PortType.ANY | any | Use sparingly |
5. Node Properties
Properties appear in the editor's right-hand panel when a node is selected.
Define them on NodeDefinition and read them inside execute().
// In NodeDefinition builder:
.addProperty("message", "string", "Hello!", "Message text")
.addProperty("count", "number", 1, "How many times")
.addProperty("loud", "boolean", false, "Use ALL CAPS")
// In execute():
String message = node.prop("message", "Hello!");
double count = node.propDouble("count", 1.0);
boolean loud = node.propBool("loud", false);
Supported property types: string, number, boolean, textarea, select.
6. Exec Nodes vs Data Nodes
Exec nodes
Have at least one exec input and one exec output. Call cb.followExec(node) at the end to advance the flow. Can also emit data via cb.emitOutput(slot, value).
Data nodes
Have only data ports (no exec ports). Call cb.emitOutput(slot, value) to publish a computed value. Do not call cb.followExec — data nodes are pulled lazily.
7. Trigger Nodes (onEnable / onDisable)
Override onEnable and onDisable if your node needs to
register Bukkit listeners or schedule repeating tasks. PNode calls these when the
graph containing the node is enabled or reloaded.
@Override
public void onEnable(NodeInstance node, NodeGraph graph) {
// register a listener, start a task, etc.
}
@Override
public void onDisable(NodeInstance node, NodeGraph graph) {
// cancel tasks, unregister listeners, free resources
}