JavaScript Tool
What is a JavaScript Tool Action?
A JavaScript Tool Action lets you run custom JavaScript code within your tool. This gives you powerful data processing capabilities - you can transform data, perform calculations, make decisions, format outputs, and implement custom logic that goes beyond what standard actions provide.
Common use cases:
- Transform data from one format to another
- Perform calculations and mathematical operations
- Filter and manipulate arrays and objects
- Format text, dates, and numbers
- Implement conditional logic (if/then/else)
- Parse and extract information from complex data
- Combine data from multiple sources
- Validate data before sending to other actions
What you can do:
- Access tool inputs and previous action outputs
- Run any JavaScript code (with some security restrictions)
- Return structured data (objects, arrays) or simple values
- Use built-in JavaScript functions and libraries
- Log debugging information
- Make network requests using fetch or other standard APIs
What you cannot do:
- Access files or databases directly (except for network-accessible resources)
- Use browser-specific APIs (DOM, window, etc.)
- Access external npm packages (only built-in JavaScript features)
Adding a JavaScript Action to Your Tool
- In your Custom Tool, click "Add Action"
- Select "JavaScript Code" as the action type
- Enter a descriptive action name (e.g.,
processData,calculateTotal,formatOutput) - Write your JavaScript code in the code editor
- Click "Add" or "Save"


Writing Your JavaScript Code
Accessing Inputs and Previous Action Outputs
Your JavaScript code has access to a special object called ctx (short for "context") that contains:
- All tool inputs
- All outputs from previous actions
Structure of ctx:
{
inputName1: "value from input",
inputName2: 123,
actionName1: { /* result from first action */ },
actionName2: { /* result from second action */ }
}
Example:
If your tool has:
- Input:
customerIdwith value"12345" - Previous action:
fetchUserthat returned{ "name": "John", "email": "john@example.com" }
Your ctx object will be:
{
customerId: "12345",
fetchUser: {
name: "John",
email: "john@example.com"
}
}
Two Ways to Return Data
Option 1: Using console.log() (Simple)
For simple string outputs, just use console.log():
console.log("Hello, World!");
What you get back: A single string with everything you logged.
Example: Basic calculation
const total = ctx.price * ctx.quantity;
console.log(`Total cost: $${total}`);
Output: "Total cost: $150"
Option 2: Using main() function (Structured)
For complex outputs or structured data, define a main() function that returns a value:
async function main() {
// Your code here
return { /* your result */ };
}
What you get back: Whatever your main() function returns (objects, arrays, numbers, strings, etc.)
Example: Returning an object
async function main() {
const firstName = ctx.fullName.split(' ')[0];
const lastName = ctx.fullName.split(' ')[1];
return {
firstName: firstName,
lastName: lastName,
email: ctx.email,
timestamp: new Date().toISOString()
};
}
Output:
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"timestamp": "2024-01-15T10:30:00.000Z"
}
When to use which method:
- Use
console.log()for: Simple text outputs, debugging messages, quick calculations - Use
main()for: Structured data, complex transformations, returning objects/arrays
Common JavaScript Patterns
Pattern 1: Transforming API Responses
Extract and restructure data from a previous API call:
async function main() {
const apiResponse = ctx.fetchCustomer.responseBody;
return {
id: apiResponse.customer.id,
name: `${apiResponse.customer.firstName} ${apiResponse.customer.lastName}`,
email: apiResponse.customer.contact.email,
status: apiResponse.customer.isActive ? "Active" : "Inactive"
};
}
Pattern 2: Calculating Values
Perform calculations on numeric inputs or previous results:
async function main() {
const subtotal = ctx.price * ctx.quantity;
const tax = subtotal * 0.08; // 8% tax
const shipping = subtotal > 100 ? 0 : 10; // Free shipping over $100
const total = subtotal + tax + shipping;
return {
subtotal: subtotal.toFixed(2),
tax: tax.toFixed(2),
shipping: shipping.toFixed(2),
total: total.toFixed(2)
};
}
Pattern 3: Filtering and Mapping Arrays
Process lists of data:
async function main() {
const orders = ctx.fetchOrders.responseBody.orders;
// Filter only completed orders
const completedOrders = orders.filter(order => order.status === "completed");
// Extract just the order IDs and totals
const orderSummary = completedOrders.map(order => ({
orderId: order.id,
total: order.totalAmount,
date: order.completedDate
}));
return {
count: completedOrders.length,
orders: orderSummary
};
}
Pattern 4: Conditional Logic
Make decisions based on data:
async function main() {
const score = ctx.getUserScore.responseBody.score;
let category;
let message;
if (score >= 90) {
category = "Excellent";
message = "Outstanding performance!";
} else if (score >= 70) {
category = "Good";
message = "Solid work!";
} else if (score >= 50) {
category = "Fair";
message = "Room for improvement.";
} else {
category = "Needs Improvement";
message = "Additional support recommended.";
}
return {
score: score,
category: category,
message: message
};
}
Pattern 5: String Manipulation
Format and process text:
async function main() {
const email = ctx.userEmail.toLowerCase().trim();
const name = ctx.userName
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
const username = email.split('@')[0];
const domain = email.split('@')[1];
return {
email: email,
name: name,
username: username,
domain: domain
};
}
Pattern 6: Date and Time Operations
Work with dates:
async function main() {
const startDate = new Date(ctx.startDate);
const endDate = new Date(ctx.endDate);
const diffTime = Math.abs(endDate - startDate);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return {
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
durationDays: diffDays,
durationWeeks: Math.floor(diffDays / 7)
};
}
Pattern 7: Data Validation
Check if data meets requirements:
async function main() {
const errors = [];
if (!ctx.email || !ctx.email.includes('@')) {
errors.push("Invalid email address");
}
if (!ctx.phone || ctx.phone.length < 10) {
errors.push("Phone number must be at least 10 digits");
}
if (ctx.age < 18) {
errors.push("Must be 18 or older");
}
return {
isValid: errors.length === 0,
errors: errors,
data: {
email: ctx.email,
phone: ctx.phone,
age: ctx.age
}
};
}
Pattern 8: Combining Multiple Data Sources
Merge data from different actions:
async function main() {
const user = ctx.fetchUser.responseBody;
const orders = ctx.fetchOrders.responseBody;
const preferences = ctx.fetchPreferences.responseBody;
return {
profile: {
userId: user.id,
name: user.name,
email: user.email
},
activity: {
totalOrders: orders.length,
lastOrderDate: orders[0]?.date || null
},
settings: {
notifications: preferences.notificationsEnabled,
theme: preferences.theme
}
};
}
Debugging Your JavaScript Code
Using console.log() for debugging
Add console.log() statements to see what your code is doing:
async function main() {
console.log("Starting calculation...");
console.log("Input value:", ctx.inputValue);
const result = ctx.inputValue * 2;
console.log("Result:", result);
return result;
}
When you test the action, you'll see all the logged messages along with the final output.
Common debugging techniques
1. Log the entire context:
console.log("Full context:", JSON.stringify(ctx, null, 2));
2. Check if a value exists:
if (!ctx.expectedValue) {
console.log("Warning: expectedValue is missing!");
}
3. Log intermediate steps:
const step1 = processData(ctx.input);
console.log("After step 1:", step1);
const step2 = transform(step1);
console.log("After step 2:", step2);
return step2;
4. Catch and log errors:
async function main() {
try {
return performComplexOperation(ctx);
} catch (error) {
console.log("Error occurred:", error.message);
return { error: error.message };
}
}
Testing Your JavaScript Action
To test an individual action:
- Click the "Test" button at the bottom of the action editor
- Provide sample values for:
- Tool inputs
- Previous action outputs (provide mock JSON data)
- Click "Run Test"
- Review the output
To test the entire tool:
- Click the "Test Tool" button at the top right of the page
- Provide values for all tool inputs
- Review the complete execution flow and results
Tips for testing:
- Start with simple code and gradually add complexity
- Test with realistic data that matches production scenarios
- Verify the output structure matches what later actions expect
- Check for edge cases (empty arrays, null values, etc.)
Troubleshooting JavaScript Actions
Problem: "Cannot read property 'X' of undefined"
- Cause: Trying to access a property that doesn't exist
- Solution: Check that the previous action or input exists and has the property you're accessing
// Instead of:
const value = ctx.action.data.field;
// Use safe access:
const value = ctx.action?.data?.field || "default value";
Problem: Output is [object Object] instead of actual data
- Cause: Using
console.log()with objects without JSON.stringify - Solution: Either use
main()function or stringify objects:
console.log(JSON.stringify(myObject, null, 2));
Problem: Code works in testing but fails in production
- Cause: Test data structure doesn't match real data
- Solution:
- Review actual API responses from previous actions
- Add defensive checks for missing/undefined values
- Use try/catch blocks to handle unexpected data
Problem: Unexpected output format
- Cause: Mixing
console.log()andmain()return - Solution: Choose one approach:
- For simple outputs: Only use
console.log(), don't definemain() - For structured outputs: Use
main()and return the value
- For simple outputs: Only use
Problem: "Timeout" or code takes too long
- Cause: Infinite loops, very large data processing, or inefficient code
- Solution:
- Check for infinite loops
- Process data in smaller chunks
- Optimize algorithms (reduce nested loops)
- Consider if some processing could be done in the API instead
Problem: Date operations return unexpected results
- Cause: Time zone differences or invalid date strings
- Solution:
- Use ISO 8601 format for dates:
"2024-01-15T10:30:00Z" - Be explicit about time zones
- Validate date strings before parsing
- Use ISO 8601 format for dates:
JavaScript Action Best Practices
1. Keep it simple
- Write clear, readable code
- Avoid overly complex logic
- Break complex operations into multiple actions if needed
2. Handle errors gracefully
async function main() {
try {
// Your code here
return result;
} catch (error) {
return {
error: true,
message: error.message
};
}
}
3. Validate inputs
async function main() {
if (!ctx.requiredInput) {
return { error: "Missing required input" };
}
// Continue with logic...
}
4. Document your code
async function main() {
// Calculate total price including tax and shipping
const subtotal = ctx.price * ctx.quantity;
const tax = subtotal * 0.08; // 8% sales tax
const total = subtotal + tax;
return { subtotal, tax, total };
}
5. Use descriptive variable names
// Good
const userEmail = ctx.fetchUser.responseBody.email;
const orderTotal = calculateTotal(items);
// Bad
const e = ctx.fetchUser.responseBody.email;
const t = calculateTotal(items);
6. Return consistent structures
// Always return the same shape
return {
success: true,
data: { /* your data */ },
message: "Operation completed"
};
Security Considerations for JavaScript Actions
1. Don't expose sensitive data in logs
// ❌ Bad - logs might contain passwords/tokens
console.log("Full context:", ctx);
// ✅ Good - log only what's needed
console.log("Processing user:", ctx.userId);
2. Validate and sanitize data
// Remove potentially dangerous characters from user input
const sanitized = ctx.userInput.replace(/[<>]/g, '');
3. Be careful with eval() or Function()
- Never use
eval()ornew Function()with user-provided strings - This can execute arbitrary code and is a security risk
4. Limit data exposure
// Don't return more than necessary
return {
userId: user.id,
name: user.name
// Don't include: password, apiKey, internalId, etc.
};
Updated on: 03/11/2025
Thank you!