Skip to content

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

PropertyDescription
idUnique identifier for the node (must be unique across the system)
labelFriendly name that will be displayed in the interface
iconIcon name (Heroicon or other configured icon system)
categoryCategory under which the node will appear in the sidebar menu
inputCountNumber of input connections the node accepts
outputCountNumber of output connections the node has
validationRulesArray of validation rule class names to apply to this node
supportsOutputVariableWhether 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 a df-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:

MethodDescription
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
{}