Articles on: Tools

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

  1. In your Custom Tool, click "Add Action"
  2. Select "JavaScript Code" as the action type
  3. Enter a descriptive action name (e.g., processData, calculateTotal, formatOutput)
  4. Write your JavaScript code in the code editor
  5. 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: customerId with value "12345"
  • Previous action: fetchUser that 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:

  1. Click the "Test" button at the bottom of the action editor
  2. Provide sample values for:
    • Tool inputs
    • Previous action outputs (provide mock JSON data)
  1. Click "Run Test"
  2. Review the output

To test the entire tool:

  1. Click the "Test Tool" button at the top right of the page
  2. Provide values for all tool inputs
  3. 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() and main() return
  • Solution: Choose one approach:
    • For simple outputs: Only use console.log(), don't define main()
    • For structured outputs: Use main() and return the value

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


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() or new 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

Was this article helpful?

Share your feedback

Cancel

Thank you!