Why Custom Tools Matter
Flowise ships with a useful set of built-in tool nodes — web search, calculator, Wikipedia, weather, and more. But most real-world agent use cases require connecting to your own systems: internal APIs, custom databases, proprietary services, or business logic that does not map to a generic tool.
Custom Tool nodes let you write any JavaScript function and expose it to your Agentflow agent as a callable tool. The agent sees the tool name and description, decides when to use it, and your function handles the execution.
Creating a Custom Tool
In your Agentflow canvas:
- Drag a Custom Tool node from the panel onto the canvas.
- Connect it to your Agent node in the Tools input.
- Configure three required fields: Tool Name, Tool Description, and the JavaScript function body.
The tool description is critical — the agent uses it to decide when to call the tool. Write it as a clear instruction: 'Use this tool to look up customer account details by customer ID. Returns name, email, and subscription plan.'
Example 1: Calling an Internal REST API
// Tool Name: get_customer_details
// Tool Description: Look up a customer's account details by their customer ID.
// Returns name, email, plan, and account status.
// Input: customer_id (string)
const response = await fetch(
`https://api.yourcompany.com/customers/${input}`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${process.env.INTERNAL_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
return JSON.stringify({ error: `API returned ${response.status}`, customer_id: input });
}
const data = await response.json();
return JSON.stringify({
name: data.full_name,
email: data.email,
plan: data.subscription_plan,
status: data.account_status
});
Always return a string from custom tools — JSON.stringify() your objects. The agent receives the return value as a string and uses it in its next reasoning step.Example 2: Querying a Database via an API Wrapper
// Tool Name: search_knowledge_base
// Tool Description: Search the company knowledge base for articles and documentation.
// Use this when the user asks about internal processes, policies, or product features.
// Input: search_query (string)
const response = await fetch('https://api.yourcompany.com/kb/search', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KB_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: input, limit: 5 })
});
const results = await response.json();
if (!results.articles || results.articles.length === 0) {
return 'No relevant articles found for: ' + input;
}
// Format for LLM consumption
const formatted = results.articles.map(a =>
`Title: ${a.title}\nSummary: ${a.summary}\nURL: ${a.url}`
).join('\n---\n');
return formatted;
Example 3: Sending a Slack Notification
// Tool Name: send_slack_notification
// Tool Description: Send a notification message to the #alerts Slack channel.
// Use this when the user asks you to notify the team about something.
// Input: message (string) — the message to send
const response = await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: input,
username: 'Flowise Agent',
icon_emoji: ':robot_face:'
})
});
if (response.ok) {
return 'Slack notification sent successfully';
} else {
return `Failed to send Slack notification: HTTP ${response.status}`;
}
Handling Tool Inputs: Single vs Structured
By default, a custom tool receives a single string input (the {input} variable). For tools that need multiple parameters, there are two approaches:
Option A: Instruct the Agent to Pass JSON
Set the tool description to: 'Input must be a JSON string with keys: customer_id (string) and date_range (string, format YYYY-MM-DD/YYYY-MM-DD).' Then parse it in your function:
// Parse structured input
let params;
try {
params = JSON.parse(input);
} catch (e) {
return 'Error: input must be valid JSON. Example: {"customer_id": "123", "date_range": "2026-01-01/2026-03-31"}';
}
const { customer_id, date_range } = params;
// ... rest of tool logic
Option B: Use Flowise's Tool Schema (newer versions)
Newer Flowise versions allow you to define a JSON Schema for tool inputs. When configured, the agent automatically passes structured arguments that match your schema rather than a single string.
Storing API Keys Safely
Never hardcode API keys inside a Custom Tool's function body. Use environment variables instead:
- On self-hosted n8n: set environment variables in your .env file or Docker configuration.
- In Flowise Cloud: go to Settings → Environment Variables and add your keys.
- Reference in tool code with: process.env.YOUR_API_KEY
Custom Tool code runs on your Flowise server with access to all process.env variables. Only expose Flowise to trusted users — a malicious tool input could potentially access environment variables if your code does not sanitise input before using it in subprocess calls or eval().Error Handling in Custom Tools
The agent treats your tool's return value as factual information. If your tool fails silently and returns an empty string or undefined, the agent may hallucinate an answer. Always return explicit error messages:
try {
const response = await fetch(apiUrl, options);
if (!response.ok) {
// Return a clear error the agent can reason about
return `Error: API returned status ${response.status}. ` +
`The request to ${apiUrl} failed. ` +
`Inform the user the service is temporarily unavailable.`;
}
const data = await response.json();
return JSON.stringify(data);
} catch (err) {
return `Error: Network request failed — ${err.message}. ` +
`Please try again or inform the user.`;
}
Testing Custom Tools
Before connecting a tool to a live agent, test the JavaScript function independently:
- Open the Flowise canvas and click the Test button on the Custom Tool node.
- Enter a sample input value and click Run.
- Check the output matches what you expect — especially the format (should be a string, ideally human-readable JSON).
- Test with edge cases: empty input, invalid IDs, API timeouts.
Once the tool works correctly in isolation, connect it to the Agent node and test the full agent conversation to verify the agent calls the tool at the right time with the right inputs.