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:
- Plugins define the data contract via
getParameterSchema()(types, defaults, constraints) - Config entities control UI/workflow behavior (connectable, configurable, required)
- 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) componentshelpers- Helper and utility componentsmemories- Memory and state management componentsprompts- Prompt-related componentsvector_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¶
- Scan Directories: Searches
Plugin/FlowDropNodeProcessordirectories in all modules - Attribute Detection: Identifies classes with
#[FlowDropNodeProcessor]attribute - Metadata Extraction: Extracts all attribute parameters as plugin metadata
- 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¶
-
Missing Required Parameters
// ❌ Error - Missing required parameters #[FlowDropNodeProcessor( id: "my_processor", label: new TranslatableMarkup("My Processor") // Missing type, supportedTypes, and category )] -
Invalid Category
// ❌ Error - Invalid category category: "invalid_category" -
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\"]");
}
}