Appearance
Creating Custom Nodes
This document explains how to create your own custom nodes in FlowBuilder, allowing you to extend the platform's functionality.
Introduction
A custom node allows you to add specific functionality to your FlowBuilder system. The creation process involves extending the BaseNode
class and implementing the necessary logic.
Creating a Basic Node
You can create your own custom node by extending the BaseNode
class and implementing the CanBeExecuted
interface.
php
namespace App\FlowBuilder\Nodes;
class CustomNode extends BaseNode implements CanBeExecuted
{
protected string $id = 'custom-node';
protected string $label = 'Custom Node';
protected string $icon = 'custom-icon';
protected string $view = 'flow-builder::nodes.custom';
protected ?string $category = 'Custom Category';
// Number of inputs and outputs (optional - default is 1 for both)
protected int $inputCount = 1;
protected int $outputCount = 1;
public function execute(): NodeExecutionResponse
{
// Implement your node logic here
// $this->data['field'] will contain the value entered in the node's field
// $this->nodeInstance will contain the Drawflow node instance
// Return a response indicating whether the execution was successful
return NodeExecutionResponse::continue(
outputVariable: 'Define a variable if needed',
);
}
public function fields(): array
{
return [
TextInput::make('field')
->label('Field Name')
->placeholder('Enter value')
->helperText('This is a custom field for the node.'),
]
}
}
Essential Node Properties
Property | Description |
---|---|
id | Unique identifier for the node (must be unique across the system) |
label | Friendly name that will be displayed in the interface |
icon | Icon name (Heroicon or other configured icon system) |
category | Category under which the node will appear in the sidebar menu |
inputCount | Number of input connections the node accepts |
outputCount | Number of output connections the node has |
validationRules | Array of validation rule class names to apply to this node |
supportsOutputVariable | Whether the node supports output variables (default is false) |
Pre-Built Base Classes
FlowBuilder includes pre-built base classes that make it easier to create specific types of nodes:
TIP
All of these classes are in: Modules\FlowBuilder\Support
.
- BaseNode: Base class for all nodes, providing common functionality.
- BaseTrigger: Extends
BaseNode
and adds common trigger features, such as setting the category to 'Triggers' and disabling inputs (inputCount = 0).
Adding Custom Data Fields
To add custom data fields to your node that can capture user input, follow these steps:
1. Define fields method in your node class
In your node class, define a fields()
method that returns an array of field definitions. Nodes has out of box support for the following filament fields:
TextInput
: For single-line text input.Textarea
: For multi-line text input.Select
: For dropdown selection.CodeEditor (Custom)
: For code input with syntax highlighting.
php
class CustomNode extends BaseNode implements CanBeExecuted
{
protected string $id = 'custom-node';
protected string $label = 'Custom Node';
protected string $icon = 'custom-icon';
protected string $view = 'flow-builder::nodes.custom';
protected string $category = 'Custom Category';
public function execute(): NodeExecutionResponse
{
// The values in $this->data are already parsed and set
// You can use them directly in your execution logic
$field = $this->data['field'];
$anotherField = $this->data['anotherfield'];
// Implement node logic using this data
// ...
return NodeExecutionResponse::continue(
outputVariable: $field . $anotherField // Example of using the fields in the response,
);
}
public function fields(): array
{
return [
TextInput::make('field')
->label('Field Name')
->placeholder('Enter value')
->helperText('This is a custom field for the node.'),
Select::make('anotherfield')
->label('Another Field')
->options([
'option1' => 'Option 1',
'option2' => 'Option 2',
])
->placeholder('Select an option'),
];
}
}
2. How It Works
- The flow builder will automatically render these fields in the node's HTML view.
- All
make('field_name')
methods define adf-field_name
attribute in the HTML. - The
df-
prefix is a special attribute recognized by the Drawflow library - When a node is created, Drawflow looks for elements with
df-
- The values from your fields will be automatically parsed and stored in the node's
$data
array on execution. - When users modify these inputs, the values are stored in the node's data
Implementing Node Execution
All nodes (except triggers) must implement the CanBeExecuted
interface. This ensures that the node can be properly executed within the flow.
The CanBeExecuted
interface defines a single method: execute()
. This method is responsible for the node's logic and is called when the flow reaches that node.
Executing Node Logic
When a node is executed, any properties defined in the $data
array will have their values already parsed and set, making them available for use in your execution logic. In the execute()
method, you can access these properties directly:
$this->data['field_name']
will contain the value entered by the user in the node's fields.$this->getFieldValue('field_name')
is a helper method to retrieve field values.$this->flowProcess
provides access to the current flow process instance, allowing you to interact with the flow state.$this->nodeInstance
contains the Drawflow node instance, which can be useful for advanced operations.
php
public function execute(): NodeExecutionResponse
{
$result = $this->getFieldValue('field_name');
$flowName = $this->flowProcess->flow->name;
$anotherField = $this->getFieldValue('anotherfield');
$result = $result . ' ' . $anotherField; // Example of using the fields in the response
return NodeExecutionResponse::continue(outputVariable: $result);
}
NodeExecutionResponse
The NodeExecutionResponse supports the following static factories:
continue(mixed $outputVariable, ?string $redirectNodeId = null)
— Continue the flow, optionally passing an output variable and/or redirecting to a specific node.waitForUserAction(?string $message = null, mixed $outputVariable = null, ?\Closure $onWaitCallback = null)
— Pause the flow and wait for user action.completeFlow(?string $message = null, mixed $outputVariable = null, ?\Closure $onCompleteCallback = null)
— Mark the flow as completed.failed(string $message)
— Mark the flow as failed with a message.
Registering Custom Nodes
After creating your custom node, you need to register it with the FlowBuilder service:
php
// In a service provider
use Modules\FlowBuilder\Facades\Node;
use App\FlowBuilder\Nodes\CustomNode;
public function boot()
{
Node::register(CustomNode::class);
}
This registration allows your custom node to appear in the Flow Builder sidebar menu and be used in flows.
Complete Custom Node Example
Here's a complete example of a custom node that performs a simple mathematical operation:
php
<?php
namespace App\FlowBuilder\Nodes;
use Modules\FlowBuilder\Support\BaseNode;
use Modules\FlowBuilder\Contracts\CanBeExecuted;
use Modules\FlowBuilder\Responses\NodeExecutionResponse;
class MathOperationNode extends BaseNode implements CanBeExecuted
{
protected string $id = 'math-operation';
protected string $label = 'Math Operation';
protected string $icon = 'heroicon-o-calculator';
protected string $category = 'Math';
public function fields(): array
{
return [
TextInput::make('number1')
->label('First Number')
->placeholder('Enter first number')
->required(),
TextInput::make('number2')
->label('Second Number')
->placeholder('Enter second number')
->required(),
Select::make('operation')
->label('Operation')
->options([
'add' => 'Add',
'subtract' => 'Subtract',
'multiply' => 'Multiply',
'divide' => 'Divide',
])
->defaultValue('add'),
];
}
public function execute(): NodeExecutionResponse
{
$num1 = (float) $this->getFieldValue('number1');
$num2 = (float) $this->getFieldValue('number2');
$operation = $this->getFieldValue('operation');
$result = 0;
switch ($operation) {
case 'add':
$result = $num1 + $num2;
break;
case 'subtract':
$result = $num1 - $num2;
break;
case 'multiply':
$result = $num1 * $num2;
break;
case 'divide':
if ($num2 == 0) {
return NodeExecutionResponse::failed(
message: 'Division by zero is not allowed.'
);
}
$result = $num1 / $num2;
break;
}
return NodeExecutionResponse::continue(
outputVariable: $result,
);
}
}
Error Handling in Nodes
When implementing node execution logic, proper error handling is essential for creating robust and user-friendly flows. FlowBuilder provides a standardized way to handle errors through the NodeExecutionException
class.
Using NodeExecutionException Factory Methods
The NodeExecutionException
class provides factory methods for creating exception instances with consistent error types and formatting. This improves error handling consistency across all nodes.
php
use Modules\Flowbuilder\Exceptions\NodeExecutionException;
public function execute(): NodeExecutionResponse
{
// Example of validation error
if (empty($this->getFieldValue('required_field'))) {
throw NodeExecutionException::validationError(
'The required field cannot be empty',
$this->nodeInstance->getId(),
$this->getLabel()
);
}
// Example of a type mismatch error
if (!is_numeric($this->getFieldValue('number_field'))) {
throw NodeExecutionException::typeMismatch(
'The number field must contain a numeric value',
$this->nodeInstance->getId(),
$this->getLabel()
);
}
// Continue with execution if no errors
return NodeExecutionResponse::continue();
}
Available Error Types
The exception class provides factory methods for all error types defined in the NodeExecutionErrorType
enum:
Method | Description |
---|---|
validationError() | For input validation errors |
conditionFailed() | When a required condition is not met |
variableNotFound() | When a required variable is missing |
invalidOperator() | For invalid operation errors |
typeMismatch() | When data is not of the expected type |
executionError() | General execution errors |
unexpectedError() | Unexpected or unhandled errors |
timeout() | When an operation times out |
unauthorized() | For authentication/authorization failures |
notFound() | When a required resource is not found |
rateLimited() | When rate limits are exceeded |
serviceUnavailable() | When a dependent service is unavailable |
dependencyError() | When a dependency fails |
parsingError() | For data parsing errors |
dataIntegrity() | For data integrity issues |
configurationError() | When configuration is invalid |
authenticationError() | For authentication failures |
permissionDenied() | When permissions are insufficient |
resourceExhausted() | When resources are exhausted |
insufficientPrivileges() | For privilege-related errors |
networkError() | For network-related issues |
Interacting with Flow Lifecycle
You can interact with the flow updated
and deleted
events by implementing the following methods in your node class:
php
// Called when the flow is updated and have the node
public function onFlowUpdated(Flow $flow): void
{}
// Called when the flow is updated and does not have the node
public function onNotFoundFlowUpdated(Flow $flow): void
{}
// Called when the flow is deleted and have the node
public function onFlowDeleted(Flow $flow): void
{}
// Called when the flow is deleted and does not have the node
public function onNotFoundFlowDeleted(Flow $flow): void
{}