Skip to content

FlowDropNodeProcessor Attribute Plugin

Overview

The FlowDropNodeProcessor attribute is a PHP 8+ attribute that provides metadata for FlowDrop node processor plugins. It serves as the primary mechanism for defining node processors in the FlowDrop workflow system, providing LangflowComponent-equivalent metadata for visual workflow nodes.

Class Definition

#[\Attribute(\Attribute::TARGET_CLASS)]
final class FlowDropNodeProcessor extends Plugin

Location: src/Attribute/FlowDropNodeProcessor.php

Constructor Parameters

Parameter Type Required Description
$id string Unique plugin identifier
$label TranslatableMarkup Human-readable label for the UI
$type string Node type for frontend rendering
$supportedTypes array All supported node types
$category string Component category (e.g., "inputs", "models", "tools")
$description string Component description (default: "")
$version string Component version (default: "1.0.0")
$tags array Component tags for categorization (default: [])

Unified Parameter System

FlowDrop uses a Unified Parameter System where:

  1. Plugins define the data contract via getParameterSchema() (types, defaults, constraints)
  2. Config entities control UI/workflow behavior (connectable, configurable, required)
  3. Runtime resolves values from inputs, workflow config, and defaults

Key Concepts

  • getParameterSchema(): Returns a JSON Schema defining parameter types and defaults
  • Config Entity: Controls which parameters are input ports vs config fields
  • ParameterBagInterface: DTO containing resolved parameter values at runtime

Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  Plugin Schema  │     │  Entity Config   │     │ System Defaults │
│  (type, default,│  +  │  (connectable,   │  +  │  (connectable=  │
│   enum, etc.)   │     │   configurable,  │     │   FALSE, etc.)  │
└────────┬────────┘     │   required)      │     └────────┬────────┘
         │              └────────┬─────────┘              │
         │                       │                        │
         └───────────────────────┴────────────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │   ParameterResolver     │
                    │   (merges all sources)  │
                    └────────────┬────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │    ParameterBag         │
                    │    (resolved values)    │
                    └─────────────────────────┘

Schema Definition Example

Plugins define standard JSON Schema properties only. UI behavior is controlled by the config entity.

public function getParameterSchema(): array {
  return [
    "type" => "object",
    "properties" => [
      "prompt" => [
        "type" => "string",
        "description" => "The input prompt text",
      ],
      "model" => [
        "type" => "string",
        "description" => "The AI model to use",
        "default" => "gpt-4",
        "enum" => ["gpt-4", "gpt-3.5-turbo", "claude-3"],
      ],
      "temperature" => [
        "type" => "number",
        "description" => "Sampling temperature",
        "default" => 0.7,
        "minimum" => 0,
        "maximum" => 2,
      ],
    ],
  ];
}

Entity Configuration

The config entity controls how parameters behave in the workflow UI:

# flowdrop_node_type.flowdrop_node_type.chat_model.yml
id: chat_model
label: 'Chat Model'
executor_plugin: chat_model
parameters:
  prompt:
    connectable: true    # Shows as input port
    configurable: true   # Shows in config form
    required: true       # Must have a value
  model:
    connectable: false   # Config only, no input port
    configurable: true
    required: false
  temperature:
    connectable: false
    configurable: true
    required: false

System Defaults

When entity config doesn't specify a flag:

Flag Default Meaning
connectable FALSE Parameter is NOT an input port
configurable FALSE Parameter does NOT appear in config
required FALSE Parameter is optional

Usage Examples

Basic Node Processor

<?php

namespace Drupal\my_module\Plugin\FlowDropNodeProcessor;

use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

#[FlowDropNodeProcessor(
  id: "my_processor",
  label: new TranslatableMarkup("My Processor"),
  type: "default",
  supportedTypes: ["default"],
  category: "processing",
  description: "A custom node processor for data transformation",
  version: "1.0.0",
  tags: ["custom", "data", "transformation"]
)]
class MyProcessor extends AbstractFlowDropNodeProcessor {

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    protected LoggerChannelInterface $logger,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get("logger.channel.flowdrop"),
    );
  }

  protected function getLogger(): LoggerChannelInterface {
    return $this->logger;
  }

  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "description" => "Input data to transform",
        ],
        "format" => [
          "type" => "string",
          "description" => "Output format",
          "default" => "json",
          "enum" => ["json", "xml", "csv"],
        ],
      ],
    ];
  }

  protected function process(ParameterBagInterface $params): array {
    $data = $params->get("data");
    $format = $params->getString("format", "json");

    // Transform data based on format...
    $transformed = $this->transform($data, $format);

    return ["result" => $transformed];
  }

  private function transform(mixed $data, string $format): mixed {
    // Implementation...
    return $data;
  }
}

AI Model Node Processor

#[FlowDropNodeProcessor(
  id: "chat_model",
  label: new TranslatableMarkup("Chat Model"),
  type: "default",
  supportedTypes: ["default"],
  category: "ai",
  description: "AI chat model integration for conversational AI",
  version: "1.0.0",
  tags: ["ai", "chat", "model", "conversation"]
)]
class ChatModel extends AbstractFlowDropNodeProcessor {

  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "prompt" => [
          "type" => "string",
          "description" => "The input prompt",
        ],
        "system_message" => [
          "type" => "string",
          "description" => "System message for the AI",
          "default" => "",
        ],
        "model" => [
          "type" => "string",
          "description" => "Model identifier",
          "default" => "gpt-4",
          "enum" => ["gpt-4", "gpt-3.5-turbo"],
        ],
        "temperature" => [
          "type" => "number",
          "description" => "Sampling temperature",
          "default" => 0.7,
          "minimum" => 0,
          "maximum" => 2,
        ],
      ],
    ];
  }

  protected function process(ParameterBagInterface $params): array {
    $prompt = $params->getString("prompt");
    $systemMessage = $params->getString("system_message", "");
    $model = $params->getString("model", "gpt-4");
    $temperature = $params->getFloat("temperature", 0.7);

    // Call AI model...
    $response = $this->callModel($prompt, $systemMessage, $model, $temperature);

    return ["response" => $response];
  }
}

Input Node Processor

#[FlowDropNodeProcessor(
  id: "text_input",
  label: new TranslatableMarkup("Text Input"),
  type: "default",
  supportedTypes: ["default"],
  category: "inputs",
  description: "Simple text input field for user data entry",
  version: "1.0.0",
  tags: ["input", "text", "user", "form"]
)]
class TextInput extends AbstractFlowDropNodeProcessor {

  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "value" => [
          "type" => "string",
          "description" => "The input text value",
          "default" => "",
        ],
        "placeholder" => [
          "type" => "string",
          "description" => "Placeholder text",
          "default" => "Enter text...",
        ],
      ],
    ];
  }

  protected function process(ParameterBagInterface $params): array {
    return ["text" => $params->getString("value", "")];
  }
}

ParameterBag Methods

The ParameterBagInterface provides typed accessors for retrieving parameter values:

Method Return Type Description
get(string $key, mixed $default = null) mixed Get any parameter value
getString(string $key, string $default = "") string Get string parameter
getInt(string $key, int $default = 0) int Get integer parameter
getFloat(string $key, float $default = 0.0) float Get float parameter
getBool(string $key, bool $default = false) bool Get boolean parameter
getArray(string $key, array $default = []) array Get array parameter
has(string $key) bool Check if parameter exists
all() array Get all parameters

Supported JSON Schema Properties

Plugins can use these standard JSON Schema properties:

Property Type Description
type string Data type: string, number, integer, boolean, array, object, mixed
description string Human-readable description
default mixed Default value
enum array Allowed values
minimum number Minimum numeric value
maximum number Maximum numeric value
minLength integer Minimum string length
maxLength integer Maximum string length
pattern string Regex pattern for strings
format string Format: email, uri, date, date-time, uuid, etc.
minItems integer Minimum array items
maxItems integer Maximum array items
uniqueItems boolean Require unique array items

Category Guidelines

Use these standardized categories for consistent organization:

Core Categories

  • inputs - Data input components (TextInput, FileUpload, etc.)
  • outputs - Data output components (TextOutput, ChatOutput, etc.)
  • models - AI model components (ChatModel, OpenAiChat, etc.)
  • tools - Utility and tool components (HttpRequest, Webhook, etc.)
  • processing - Data processing components (DataOperations, Calculator, etc.)
  • logic - Logic and control flow components (Conditional, Loop, etc.)
  • ai - AI-specific components (embeddings, vector stores, etc.)
  • eca - ECA (Event-Condition-Action) components
  • helpers - Helper and utility components
  • memories - Memory and state management components
  • prompts - Prompt-related components
  • vector_store - Vector database components

Custom Categories

You can create custom categories for specialized components: - custom - Custom business logic components - integration - Third-party service integrations - analytics - Data analytics components

Tag Guidelines

Tags help with categorization and search functionality. Use descriptive, lowercase tags:

Common Tag Patterns

  • Functionality: ai, chat, data, file, http, webhook
  • Data Types: text, json, csv, image, audio
  • Operations: input, output, transform, filter, aggregate
  • Providers: openai, huggingface, anthropic, google
  • Use Cases: conversation, automation, analytics, reporting

Plugin Discovery

The attribute is discovered by the FlowDropNodeProcessorPluginManager using attribute-based discovery:

$this->discovery = new AttributeClassDiscovery(
  "Plugin/FlowDropNodeProcessor",
  $namespaces,
  FlowDropNodeProcessor::class
);

Discovery Process

  1. Scan Directories: Searches Plugin/FlowDropNodeProcessor directories in all modules
  2. Attribute Detection: Identifies classes with #[FlowDropNodeProcessor] attribute
  3. Metadata Extraction: Extracts all attribute parameters as plugin metadata
  4. Caching: Caches discovered plugins for performance

Integration with Workflow Editor

The attribute metadata is used by the FlowDrop workflow editor to:

Frontend Rendering

  • Component Library: Displays available components organized by category
  • Node Configuration: Provides configuration forms based on entity config
  • Input/Output Ports: Renders connection points for connectable parameters
  • Search & Filtering: Enables component discovery using $tags

API Integration

  • Node Metadata: Exposes component information via REST API
  • Configuration Validation: Validates node configurations against schemas
  • Execution Context: Provides metadata for workflow execution

Best Practices

1. Unique Plugin IDs

// ✅ Good - Descriptive and unique
id: "custom_data_transformer"

// ❌ Bad - Generic and may conflict
id: "processor"

2. Descriptive Labels

// ✅ Good - Clear and descriptive
label: new TranslatableMarkup("Customer Data Transformer")

// ❌ Bad - Too generic
label: new TranslatableMarkup("Processor")

3. Appropriate Categories

// ✅ Good - Specific category
category: "processing"

// ❌ Bad - Generic category
category: "other"

4. Meaningful Tags

// ✅ Good - Descriptive tags
tags: ["data", "transformation", "customer", "business-logic"]

// ❌ Bad - Too generic
tags: ["custom"]

5. Version Management

// ✅ Good - Semantic versioning
version: "1.2.0"

// ❌ Bad - No version tracking
version: "1.0.0" // Always default

6. Clear Parameter Schemas

// ✅ Good - Well-defined schema with descriptions and constraints
public function getParameterSchema(): array {
  return [
    "type" => "object",
    "properties" => [
      "input_text" => [
        "type" => "string",
        "description" => "Text to process",
        "minLength" => 1,
      ],
      "max_tokens" => [
        "type" => "integer",
        "description" => "Maximum output tokens",
        "default" => 1000,
        "minimum" => 1,
        "maximum" => 4096,
      ],
    ],
  ];
}

// ❌ Bad - Missing descriptions and constraints
public function getParameterSchema(): array {
  return [
    "type" => "object",
    "properties" => [
      "input_text" => ["type" => "string"],
    ],
  ];
}

Error Handling

Common Issues

  1. Missing Required Parameters

    // ❌ Error - Missing required parameters
    #[FlowDropNodeProcessor(
      id: "my_processor",
      label: new TranslatableMarkup("My Processor")
      // Missing type, supportedTypes, and category
    )]
    

  2. Invalid Category

    // ❌ Error - Invalid category
    category: "invalid_category"
    

  3. Duplicate Plugin IDs

    // ❌ Error - Duplicate ID
    id: "text_input" // Already exists in core
    

Validation

The plugin manager validates: - Required parameters are present - Plugin IDs are unique - Categories are valid - Attribute syntax is correct

Testing

Unit Testing

use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop\DTO\ParameterBag;

class FlowDropNodeProcessorTest extends UnitTestCase {

  public function testPluginDiscovery() {
    $plugin_manager = $this->createMock(FlowDropNodeProcessorPluginManager::class);
    $plugins = $plugin_manager->getDefinitions();

    $this->assertArrayHasKey("my_processor", $plugins);
    $this->assertEquals("processing", $plugins["my_processor"]["category"]);
  }

  public function testNodeExecution() {
    $plugin = $this->createPlugin();
    $params = new ParameterBag([
      "data" => ["key" => "value"],
      "format" => "json",
    ]);

    $output = $plugin->execute($params);

    $this->assertEquals("success", $output->getStatus());
  }
}

Integration Testing

use Drupal\Tests\BrowserTestBase;

class FlowDropNodeProcessorIntegrationTest extends BrowserTestBase {

  public function testPluginInWorkflowEditor() {
    $this->drupalGet("/admin/structure/flowdrop-workflow/foo/flowdrop-editor");
    $this->assertSession()->elementExists("css", "[data-component-id=\"my_processor\"]");
  }
}