| layout | default |
|---|---|
| title | n8n AI Tutorial - Chapter 7: Custom AI Tools |
| nav_order | 7 |
| has_children | false |
| parent | n8n AI Tutorial |
Welcome to Chapter 7: Building Custom AI Tools and Integrations. In this part of n8n AI Tutorial: Workflow Automation with AI, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.
Extend n8n's capabilities with custom AI tools, integrations, and specialized functions.
flowchart TD
CUSTOM[Custom AI Tools] --> HTTP[HTTP Request Tool\ncall any REST API]
CUSTOM --> CODE[Code Tool\nrun JavaScript/Python]
CUSTOM --> WF[n8n Workflow Tool\nexpose workflow as tool]
CUSTOM --> MCP[MCP Tool Node\nModel Context Protocol servers]
HTTP --> AGENT[AI Agent Node]
CODE --> AGENT
WF --> AGENT
MCP --> AGENT
{
"parameters": {
"name": "custom_api_tool",
"description": "Call custom API for specialized processing",
"parameters": {
"endpoint": {
"type": "string",
"description": "API endpoint to call"
},
"data": {
"type": "object",
"description": "Request payload"
}
}
},
"name": "Custom API Tool",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "={{ $parameter.endpoint }}",
"method": "POST",
"bodyContentType": "json",
"bodyParameters": {
"parameters": [
{
"name": "payload",
"value": "={{ $parameter.data }}"
}
]
}
}
}{
"parameters": {
"name": "knowledge_search",
"description": "Search internal knowledge base",
"parameters": {
"query": {
"type": "string",
"description": "Search query"
},
"limit": {
"type": "number",
"description": "Maximum results"
}
}
},
"name": "Knowledge Base Tool",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "select",
"query": "SELECT * FROM knowledge WHERE text ILIKE '%' || {{ $parameter.query }} || '%' LIMIT {{ $parameter.limit || 5 }}"
},
"credentials": {
"postgres": "knowledge-db"
}
}// Custom text analysis tool
const text = $input.item.json.input_text;
const analysis = {
word_count: text.split(/\s+/).length,
character_count: text.length,
sentence_count: text.split(/[.!?]+/).length - 1,
readability_score: calculateReadability(text),
language: detectLanguage(text),
sentiment: analyzeSentiment(text),
keywords: extractKeywords(text, 5)
};
function calculateReadability(text) {
// Simplified readability calculation
const words = text.split(/\s+/).length;
const sentences = text.split(/[.!?]+/).length;
const avgWordsPerSentence = words / sentences;
return Math.max(0, Math.min(100, 206.835 - 1.015 * avgWordsPerSentence));
}
function detectLanguage(text) {
// Simple language detection
if (text.includes('the ') && text.includes(' and ')) return 'en';
if (text.includes('el ') && text.includes(' y ')) return 'es';
if (text.includes('le ') && text.includes(' et ')) return 'fr';
return 'unknown';
}
function analyzeSentiment(text) {
const positiveWords = ['good', 'great', 'excellent', 'amazing', 'wonderful'];
const negativeWords = ['bad', 'terrible', 'awful', 'horrible', 'poor'];
const lowerText = text.toLowerCase();
const positiveCount = positiveWords.filter(word => lowerText.includes(word)).length;
const negativeCount = negativeWords.filter(word => lowerText.includes(word)).length;
if (positiveCount > negativeCount) return 'positive';
if (negativeCount > positiveCount) return 'negative';
return 'neutral';
}
function extractKeywords(text, limit) {
const words = text.toLowerCase().match(/\b\w{4,}\b/g) || [];
const wordCount = {};
words.forEach(word => {
wordCount[word] = (wordCount[word] || 0) + 1;
});
return Object.entries(wordCount)
.sort(([,a], [,b]) => b - a)
.slice(0, limit)
.map(([word, count]) => ({ word, count }));
}
return [{
json: {
analysis: analysis,
processed_at: new Date().toISOString()
}
}];// Tool that combines local processing with AI
const data = $input.item.json.raw_data;
// Step 1: Local preprocessing
const cleaned = data.trim().toLowerCase();
// Step 2: AI analysis (this would call an AI model)
const aiInsights = $node.openAi.default.sendMessage({
model: 'gpt-3.5-turbo',
messages: [{
role: 'user',
content: `Analyze this data and provide insights: ${cleaned}`
}],
max_tokens: 200
});
// Step 3: Combine results
return [{
json: {
original_data: data,
processed_data: cleaned,
ai_insights: aiInsights.choices[0].message.content,
processing_steps: ['cleaning', 'ai_analysis'],
confidence_score: 0.85
}
}];// custom-node.ts
import { IExecuteFunctions } from 'n8n-workflow';
export class CustomAINode {
async execute(this: IExecuteFunctions) {
const items = this.getInputData();
for (let i = 0; i < items.length; i++) {
const input = items[i].json;
// Custom logic here
const result = {
processed: true,
input_length: JSON.stringify(input).length,
timestamp: new Date().toISOString()
};
// Return processed data
this.addOutputData(i, [{ json: result }]);
}
return this.prepareOutputData(items);
}
}{
"nodes": [
{
"parameters": {
"functionCode": "
// Step 1: Validate input
const query = $input.item.json.query;
if (!query || query.length < 3) {
return [{ json: { error: 'Query too short', valid: false } }];
}
// Step 2: Preprocessing
const processedQuery = query.trim().toLowerCase();
// Step 3: Route to appropriate tool
const tool = processedQuery.includes('calculate') ? 'calculator' :
processedQuery.includes('search') ? 'search' :
'general_ai';
return [{
json: {
original_query: query,
processed_query: processedQuery,
selected_tool: tool,
confidence: 0.9
}
}];
"
},
"name": "Query Router",
"type": "n8n-nodes-base.function"
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.selected_tool }}",
"operation": "equal",
"value2": "calculator"
}
]
}
},
"name": "Calculator Route",
"type": "n8n-nodes-base.if"
},
{
"parameters": {
"functionCode": "
// Calculator implementation
const expression = $input.item.json.processed_query;
const result = eval(expression.replace(/[^0-9+\-*/().\s]/g, ''));
return [{
json: {
tool: 'calculator',
result: result,
expression: expression
}
}];
"
},
"name": "Calculator Tool",
"type": "n8n-nodes-base.function"
}
]
}{
"parameters": {
"httpMethod": "POST",
"path": "ai-tool",
"responseMode": "responseNode",
"options": {}
},
"name": "AI Tool Webhook",
"type": "n8n-nodes-base.webhook"
}// Custom API integration
const apiKey = $credentials.customApi.apiKey;
const endpoint = $input.item.json.endpoint;
const response = await $node.httpRequest.default.sendRequest({
method: 'POST',
url: endpoint,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify($input.item.json.payload)
});
return [{
json: {
api_response: response,
status_code: response.statusCode,
processing_time: Date.now() - $input.item.json.start_time
}
}];// Tool with built-in caching
const cache = $workflow.expression.get('tool_cache') || {};
const cacheKey = JSON.stringify($input.item.json);
if (cache[cacheKey]) {
return [cache[cacheKey]];
}
// Process normally
const result = performExpensiveOperation($input.item.json);
// Cache result
cache[cacheKey] = result;
$workflow.expression.set('tool_cache', cache);
return [result];{
"parameters": {
"batchSize": 10,
"options": {
"merge": false
}
},
"name": "Batch Tool Processor",
"type": "n8n-nodes-base.splitInBatches"
}// Track tool usage and performance
const toolMetrics = $workflow.expression.get('tool_metrics') || {};
const toolName = $input.item.json.tool_name;
const startTime = $input.item.json.start_time || Date.now();
const endTime = Date.now();
if (!toolMetrics[toolName]) {
toolMetrics[toolName] = {
calls: 0,
total_time: 0,
avg_time: 0,
errors: 0
};
}
toolMetrics[toolName].calls += 1;
toolMetrics[toolName].total_time += (endTime - startTime);
toolMetrics[toolName].avg_time = toolMetrics[toolName].total_time / toolMetrics[toolName].calls;
if ($input.item.json.error) {
toolMetrics[toolName].errors += 1;
}
$workflow.expression.set('tool_metrics', toolMetrics);
return [{
json: {
metrics: toolMetrics[toolName],
tool_name: toolName
}
}];// Secure input validation
const input = $input.item.json;
const MAX_LENGTH = 10000;
const ALLOWED_CHARS = /^[a-zA-Z0-9\s.,!?-]+$/;
if (typeof input !== 'object' || !input.text) {
throw new Error('Invalid input format');
}
if (input.text.length > MAX_LENGTH) {
throw new Error('Input too long');
}
if (!ALLOWED_CHARS.test(input.text)) {
throw new Error('Invalid characters in input');
}
// Sanitize input
const sanitized = input.text.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
return [{
json: {
sanitized_input: sanitized,
validation_passed: true
}
}];{
"parameters": {
"mode": "queue",
"batchSize": 1,
"concurrency": 1,
"options": {
"reset": false
}
},
"name": "Rate Limiter",
"type": "n8n-nodes-base.splitInBatches"
}- Modular Design: Build reusable tools with clear interfaces
- Error Handling: Implement comprehensive error handling and recovery
- Documentation: Document tool parameters and expected outputs
- Testing: Thoroughly test custom tools before production use
- Security: Validate inputs and implement appropriate security measures
- Performance: Monitor and optimize tool performance
- Versioning: Maintain version control for custom tools
- Monitoring: Track tool usage and success rates
Custom tools extend n8n's capabilities infinitely. The final chapter covers production deployment and scaling.
Most teams struggle here because the hard part is not writing more code, but deciding clear boundaries for text, json, input so behavior stays predictable as complexity grows.
In practical terms, this chapter helps you avoid three common failures:
- coupling core logic too tightly to one implementation path
- missing the handoff boundaries between setup, execution, and validation
- shipping changes without clear rollback or observability strategy
After working through this chapter, you should be able to reason about Chapter 7: Building Custom AI Tools and Integrations as an operating subsystem inside n8n AI Tutorial: Workflow Automation with AI, with explicit contracts for inputs, state transitions, and outputs.
Use the implementation notes around parameters, item, name as your checklist when adapting these patterns to your own repository.
Under the hood, Chapter 7: Building Custom AI Tools and Integrations usually follows a repeatable control path:
- Context bootstrap: initialize runtime config and prerequisites for
text. - Input normalization: shape incoming data so
jsonreceives stable contracts. - Core execution: run the main logic branch and propagate intermediate state through
input. - Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
- Output composition: return canonical result payloads for downstream consumers.
- Operational telemetry: emit logs/metrics needed for debugging and performance tuning.
When debugging, walk this sequence in order and confirm each stage has explicit success/failure conditions.
Key source files in n8n-io/n8n:
packages/@n8n/nodes-langchain/nodes/tools/ToolHttpRequest/-- HTTP Request Tool node: wraps any REST API call as an AI agent toolpackages/@n8n/nodes-langchain/nodes/tools/ToolCode/-- Code Tool node: runs JavaScript in a sandboxed environment as a callable toolpackages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/-- Workflow Tool node: exposes any n8n workflow as a tool callable by an agent
Suggested trace: the ToolWorkflow node's supplyData() creates a LangChain DynamicTool that invokes another workflow via WorkflowRunner.run() when called.