Workflow Nodes
Nodes are the building blocks of workflows. Each node performs a specific function and can be connected to other nodes to create automation sequences.
Node Anatomy
Every node has:
┌─────────────────────────────────────┐
│ ○ Input Handle(s) │
├─────────────────────────────────────┤
│ [Icon] Node Name │
│ Description text... │
│ ⚙️ Configured / ⚠️ Not configured │
├─────────────────────────────────────┤
│ Output Handle(s) ○│
└─────────────────────────────────────┘
- Input Handles: Where data flows in (left side)
- Output Handles: Where data flows out (right side)
- Configuration: Node-specific settings
- Category Badge: Visual indicator of node type
Node Categories
Triggers (Green)
Triggers start workflow execution. Every workflow needs at least one trigger.
Characteristics:
- No input handles (they start the flow)
- One or more output handles
- Provide initial data to the workflow
- Marked as
is_entry_pointin database
Actions (Blue)
Actions perform operations - send emails, make API calls, update records.
Characteristics:
- One input handle
- One output handle
- May have side effects (sending emails, HTTP requests)
- Can be async (delays, waiting for responses)
Conditions (Yellow)
Conditions branch the workflow based on logic.
Characteristics:
- One input handle
- Multiple output handles (one per branch)
- Evaluate input data against rules
- Output includes
_branchindicator
Transformers (Purple)
Transformers modify data without side effects.
Characteristics:
- One input handle
- One output handle
- Pure functions (same input = same output)
- Used for data mapping, filtering, formatting
Built-in Nodes Reference
Manual Trigger
Identifier: workflows:manual-trigger
Category: Trigger
Starts workflow when user clicks Run or calls the execute API.
Configuration:
| Field | Type | Description |
|---|---|---|
sampleData |
object | Sample data for testing |
Output:
{
"trigger_type": "manual",
"triggered_at": "2025-01-16T10:00:00Z",
"triggered_by": "user-uuid",
...triggerData
}
Schedule Trigger
Identifier: workflows:schedule-trigger
Category: Trigger
Runs workflow on a cron schedule.
Configuration:
| Field | Type | Description | Example |
|---|---|---|---|
cron |
string | Cron expression | 0 9 * * * (daily at 9am) |
timezone |
string | Timezone for schedule | Asia/Kolkata |
Common Cron Patterns:
* * * * * Every minute
0 * * * * Every hour
0 9 * * * Daily at 9:00 AM
0 9 * * 1 Every Monday at 9:00 AM
0 0 1 * * First day of month at midnight
*/15 * * * * Every 15 minutes
Output:
{
"trigger_type": "schedule",
"scheduled_time": "2025-01-16T09:00:00Z",
"cron": "0 9 * * *"
}
Webhook Trigger
Identifier: workflows:webhook-trigger
Category: Trigger
Receives HTTP POST requests to trigger the workflow.
Configuration:
| Field | Type | Description |
|---|---|---|
requireSignature |
boolean | Validate webhook signature |
signatureHeader |
string | Header containing signature |
signatureSecret |
string | Secret for HMAC validation |
Webhook URL:
POST https://your-domain.com/api/webhooks/workflows/{workflow-token}
Output:
{
"trigger_type": "webhook",
"method": "POST",
"headers": { ... },
"query": { ... },
"body": { ...posted_data }
}
Send Email
Identifier: workflows:send-email
Category: Action
Sends an email using the configured mail provider.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
to |
string | Yes | Recipient email (supports expressions) |
subject |
string | Yes | Email subject |
body |
string | Yes | Email body content |
bodyType |
enum | No | text or html (default: text) |
Expression Example:
to: "{{customer.email}}"
subject: "Order {{order.id}} Confirmation"
body: |
Hi {{customer.name}},
Your order #{{order.id}} has been confirmed.
Total: ${{order.total}}
Thank you!
Output:
{
...input,
"_email_sent": true,
"_email_to": "john@example.com",
"_email_subject": "Order 123 Confirmation",
"_sent_at": "2025-01-16T10:30:00Z"
}
HTTP Request
Identifier: workflows:http-request
Category: Action
Makes HTTP requests to external APIs.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
method |
enum | Yes | GET, POST, PUT, PATCH, DELETE |
url |
string | Yes | Request URL (supports expressions) |
headers |
object | No | Request headers |
body |
string | No | Request body (for POST/PUT/PATCH) |
bodyType |
enum | No | json, form, raw |
timeout |
number | No | Timeout in seconds (default: 30) |
retryOnError |
boolean | No | Retry on HTTP errors |
Output:
{
...input,
"_http_status": 200,
"_http_response": { ...response_body },
"_http_headers": { ...response_headers },
"_http_duration_ms": 234
}
Delay
Identifier: workflows:delay
Category: Action
Pauses workflow execution for a specified duration.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
duration |
number | Yes | Delay amount |
unit |
enum | Yes | seconds, minutes, hours, days |
Example:
duration: 5
unit: minutes
Output:
{
...input,
"_delayed": true,
"_delay_duration": 300,
"_delay_started_at": "2025-01-16T10:00:00Z",
"_delay_ended_at": "2025-01-16T10:05:00Z"
}
Set Variable
Identifier: workflows:set-variable
Category: Action
Stores a value in the workflow execution context.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Variable name |
value |
any | Yes | Value to store (supports expressions) |
Example:
name: "discount_percentage"
value: "{{order.total > 100 ? 10 : 0}}"
Output:
{
...input,
"_variable_set": "discount_percentage",
"_variable_value": 10
}
Variables are accessible in subsequent nodes via {{variables.discount_percentage}}.
If/Else
Identifier: workflows:if-else
Category: Condition
Binary branching based on a condition.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
condition |
string | Yes | Expression that evaluates to boolean |
operator |
enum | No | Comparison operator |
value |
any | No | Value to compare against |
Output Handles:
true- When condition is truefalse- When condition is false
Example Conditions:
// Simple comparison
{{order.total}} > 100
// String matching
{{customer.type}} == "premium"
// Array check
{{items.length}} > 0
// Nested property
{{order.shipping.method}} == "express"
Output:
{
...input,
"_branch": "true", // or "false"
"_condition_result": true
}
Switch
Identifier: workflows:switch
Category: Condition
Multi-way branching based on value matching.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
value |
string | Yes | Expression to evaluate |
cases |
array | Yes | List of case values |
defaultCase |
string | No | Default output if no match |
Example:
value: "{{order.status}}"
cases:
- "pending"
- "processing"
- "shipped"
- "delivered"
defaultCase: "other"
Output Handles: One handle per case plus optional default.
Output:
{
...input,
"_branch": "processing",
"_switch_value": "processing"
}
Map
Identifier: workflows:map
Category: Transformer
Transforms the structure of input data.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
mapping |
object | Yes | Key-value mapping of output fields |
keepOriginal |
boolean | No | Keep unmapped fields |
Example:
mapping:
email: "{{customer.email}}"
name: "{{customer.first_name}} {{customer.last_name}}"
orderTotal: "{{order.total}}"
keepOriginal: false
Input:
{
"customer": {
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe"
},
"order": { "total": 99.99 }
}
Output:
{
"email": "john@example.com",
"name": "John Doe",
"orderTotal": 99.99
}
Filter
Identifier: workflows:filter
Category: Transformer
Filters items in an array based on conditions.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
arrayPath |
string | Yes | Path to array in input |
condition |
string | Yes | Filter condition per item |
outputPath |
string | No | Where to put filtered array |
Example:
arrayPath: "items"
condition: "{{item.price}} > 50"
outputPath: "expensive_items"
Input:
{
"items": [
{ "name": "A", "price": 30 },
{ "name": "B", "price": 75 },
{ "name": "C", "price": 120 }
]
}
Output:
{
"items": [...],
"expensive_items": [
{ "name": "B", "price": 75 },
{ "name": "C", "price": 120 }
],
"_filtered_count": 2,
"_original_count": 3
}
Creating Custom Nodes
See Extending Workflows for a complete guide to creating custom nodes.
Quick Example
<?php
namespace Modules\MyModule\Nodes\Actions;
use Modules\Workflows\Nodes\BaseNode;
class MyCustomNode extends BaseNode
{
public static function getIdentifier(): string
{
return 'my-module:custom-action';
}
public static function getName(): string
{
return 'My Custom Action';
}
public static function getCategory(): string
{
return 'action';
}
public static function getConfigSchema(): array
{
return [
'type' => 'object',
'properties' => [
'myField' => [
'type' => 'string',
'title' => 'My Field',
],
],
'required' => ['myField'],
];
}
public function execute(array $input, array $config, array $context): array
{
// Your logic here
return array_merge($input, [
'_custom_result' => 'done',
]);
}
}
Node Connection Rules
Valid Connections
| Source Category | Can Connect To |
|---|---|
| Trigger | Action, Condition, Transformer |
| Action | Action, Condition, Transformer |
| Condition | Action, Condition, Transformer |
| Transformer | Action, Condition, Transformer |
Invalid Connections
- Triggers cannot be targets - Nothing can connect TO a trigger
- Circular connections - A node cannot eventually connect back to itself
- Type mismatches - Some nodes require specific input types
Best Practices
Node Configuration
- Use expressions - Make configs dynamic with
{{path.to.value}} - Validate early - Check required data at the start of workflow
- Handle errors - Configure retry settings for unreliable operations
- Log important data - Use Set Variable to track state
Workflow Design
- Start with triggers - Always have a clear entry point
- Validate data - Use conditions to handle edge cases
- Keep it simple - Break complex workflows into smaller ones
- Document - Use node labels to explain purpose
Next Steps
- Execution Engine - How nodes are executed
- Extending Workflows - Create custom nodes
- Examples - See nodes in action