Action Entity
The Action entity executes custom JavaScript code for data transformation, external integrations, and side effects.
Purpose
Action entities provide:
- Data Transformation — Reshape message data
- External Calls — Call webhooks, APIs, services
- Variable Setting — Prepare data for downstream entities
- Side Effects — Post to social media, send notifications
Configuration
Available Fields
| Field | Description |
|---|---|
| Name | Display name (e.g., "Format Social Post") |
| Description | Optional documentation |
| Pre-Script | Optional JavaScript run before arguments, condition, and downstream steps |
| Post-Script | Optional JavaScript run after prompt/model handling (if any), before branching |
| Arguments | Variables injected after pre-script, before condition evaluation |
The Action entity type is usually configured without condition, model, or prompt fields; the unified pipeline then runs preScript → arguments → condition (defaults to true) → postScript → children.
Script Execution
Action preScript and postScript run in an isolated V8 context (same unified pipeline as other entity types) with full access to:
// Incoming message (fields at top-level in V8)
const eventType = type;
const payload = data;
// Environment variables
const apiKey = MY_API_KEY;
const serverUrl = SERVICE_URL;
// Entity arguments (injected)
const formattedMessage = socialContent; // From arguments
// Built-in functions
print('Processing:', eventType);
// Previous results from upstream entities
const aiResponse = latestPromptResponse();
// Set data for downstream entities
var processedData = {
formatted: true,
content: aiResponse,
timestamp: new Date().toISOString()
};
Available in Script Context
| Variable | Description |
|---|---|
| (top-level fields) | Incoming message properties (type, organizationId, content, payload, user, …) are available directly in script scope |
__variables__ | Array of environment variables |
__variablesByName__ | Map of variable name → value |
{VARIABLE_NAME} | Each variable as top-level var |
| Arguments | Each argument as top-level var |
latestPromptResponse() | Get last AI response |
getPromptResponse(index) | Get AI response by index |
Built-in Functions
// Debugging
print('Debug info:', variable);
print('Snapshot:', JSON.stringify({ type, organizationId, payload, content }));
// AI/LLM (if you need manual calls)
const response = await promptCallToken(token, prompt, url);
const response = await promptCallKeys(key, secret, prompt, url);
// Social media posting
await postToMastodon(serverUrl, token, status);
await postLatestPromptToMastodon();
// Condition evaluation (if needed)
const matches = evaluateCondition(conditionTree, data);
Execution Flow
Actions use UnifiedEntityEvaluator like every other entity. With no condition, model, or prompt configured, the pipeline collapses to message injection, optional preScript, arguments, condition (defaults true), skip prompt/model, optional postScript, context capture, then continue to children.
┌─────────────────────────────────────────────────────────────┐
│ UnifiedEntityEvaluator (Action node) │
├─────────────────────────────────────────────────────────────┤
│ 1. Inject message │
│ 2. Run preScript (if any) │
│ 3. Inject arguments (if any) │
│ 4. Evaluate condition (if any; else true) │
│ 5. Prompt + model (skipped for typical Actions) │
│ 6. Run postScript (if any) │
│ 7. Capture context │
│ 8. Branching → process_children │
└─────────────────────────────────────────────────────────────┘
If preScript or postScript throws or times out, the consumer treats the entity as failed and stops that workflow path. Use try/catch inside scripts when you need to continue despite errors.
Examples
Example 1: Data Transformation
Transform message data for downstream processing:
// Extract and normalize user data
var userData = {
id: user.id,
name: user.displayName || user.email.split('@')[0],
email: user.email.toLowerCase(),
createdAt: new Date(timestamp).toISOString(),
source: source || 'unknown'
};
print('Normalized user:', JSON.stringify(userData));
Example 2: Format for Social Media
Prepare content for social posting:
// Get AI-generated content from upstream Prompt entity
const aiContent = latestPromptResponse();
// Format for Mastodon (280 char limit)
var socialContent = aiContent;
if (socialContent.length > 280) {
socialContent = socialContent.substring(0, 277) + '...';
}
// Add hashtags
const hashtags = '#welcome #newuser';
if (socialContent.length + hashtags.length + 1 <= 280) {
socialContent = socialContent + ' ' + hashtags;
}
print('Social content ready:', socialContent.length, 'chars');
Example 3: Post to Mastodon
Execute social media posting:
// Get server URL and token from environment variables
const mastodonServer = MASTODON_SERVER_URL;
const accessToken = MASTODON_ACCESS_TOKEN;
// Get content from upstream
const status = latestPromptResponse();
if (status && mastodonServer && accessToken) {
try {
await postToMastodon(mastodonServer, accessToken, status);
print('Posted to Mastodon successfully');
var postSuccess = true;
} catch (error) {
print('Mastodon post failed:', error.message);
var postSuccess = false;
}
} else {
print('Missing Mastodon configuration');
var postSuccess = false;
}
Example 4: Webhook Call (via Arguments)
Configure webhooks via arguments for flexibility:
Arguments:
[
{
"argumentName": "webhookUrl",
"argumentValue": "{{WEBHOOK_URL}}",
"argumentDescription": "Destination webhook URL"
},
{
"argumentName": "eventData",
"argumentValue": "{{message}}",
"argumentDescription": "Message payload to send"
}
]
Post-Script (or pre-script if no AI upstream):
// webhookUrl and eventData are injected from arguments
print('Sending to webhook:', webhookUrl);
// Note: Direct fetch not available in V8 isolate
// Store data for external processing or use built-in functions
var webhookPayload = {
url: webhookUrl,
data: eventData,
timestamp: new Date().toISOString()
};
// This will be available in the Result context
print('Webhook payload prepared');
Example 5: Conditional Logic
Complex branching within a script:
const eventType = type;
const userTier = user.tier || 'free';
// Different processing based on event type
switch (eventType) {
case 'order.created':
var notificationType = 'order_confirmation';
var priority = userTier === 'premium' ? 'high' : 'normal';
break;
case 'order.shipped':
var notificationType = 'shipping_update';
var priority = 'normal';
break;
case 'order.delivered':
var notificationType = 'delivery_confirmation';
var priority = 'low';
break;
default:
var notificationType = 'general';
var priority = 'low';
}
print('Notification type:', notificationType, 'Priority:', priority);
Example 6: Error Handling
Graceful error handling:
try {
// Attempt to parse nested JSON
const rawData = payload;
var parsedData = JSON.parse(rawData);
if (!parsedData.requiredField) {
throw new Error('Missing required field');
}
var processingSuccess = true;
var errorMessage = null;
} catch (error) {
print('Processing error:', error.message);
var processingSuccess = false;
var errorMessage = error.message;
var parsedData = null;
}
// Downstream entities can check processingSuccess
Example 7: Aggregation
Combine data from multiple sources:
// Combine message data with AI response
const aiAnalysis = latestPromptResponse();
var aggregatedResult = {
// Original message metadata
messageId: id,
timestamp: timestamp,
source: source,
// Processed content
content: content,
// AI-generated analysis
analysis: aiAnalysis,
// Computed fields
processedAt: new Date().toISOString(),
wordCount: content.split(' ').length,
hasAiAnalysis: !!aiAnalysis
};
print('Aggregated result keys:', Object.keys(aggregatedResult).join(', '));
Arguments with Handlebar References
Arguments support dynamic values via handlebar syntax:
| Pattern | Description |
|---|---|
{{user.id}} | Extract from message |
{{___result___}} | Previous entity result |
{{latestPromptResponse()}} | AI response |
{{VARIABLE_NAME}} | Environment variable |
Example Configuration:
{
"arguments": [
{
"argumentName": "userId",
"argumentValue": "{{user.id}}",
"argumentDescription": "User's unique ID"
},
{
"argumentName": "aiContent",
"argumentValue": "{{latestPromptResponse()}}",
"argumentDescription": "Generated AI content"
},
{
"argumentName": "apiEndpoint",
"argumentValue": "{{API_ENDPOINT}}",
"argumentDescription": "Target API from env var"
}
]
}
Best Practices
Script Organization
- Choose pre vs post — Use preScript before the condition; postScript when you need
latestPromptResponse()or final___result___ - Declare variables at top — Make them available downstream
- Use meaningful names —
userDatanotd - Add print statements — Help with debugging
- Handle edge cases — Check for null/undefined
Performance
- Keep scripts focused — One responsibility per Action
- Avoid loops over large arrays — Can hit timeout
- Don't block on external calls — V8 isolate has timeout
Variable Naming
// Good - descriptive names with var for downstream access
var processedUser = { ... };
var socialContent = '...';
var webhookPayload = { ... };
// Avoid - const doesn't persist to downstream
const result = { ... }; // Not accessible in next entity
Error Recovery
// Always have fallbacks
var result = someOperation() || defaultValue;
// Try-catch for risky operations
try {
var parsed = JSON.parse(maybeJson);
} catch {
var parsed = { raw: maybeJson };
}
Related Topics
- Mastodon Scripts — Social media integration
- Prompt Scripts — AI/LLM functions
- Consumer Evaluator — UnifiedEntityEvaluator implementation