$npx -y skills add czlonkowski/n8n-skills --skill n8n-code-toolWrite JavaScript or Python for the n8n Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode) — the AI-agent-callable tool, NOT the workflow Code node. Use when building a Code Tool attached to an AI Agent, writing code that an LLM will invoke, parsing the query input, returning
| 1 | # n8n Custom Code Tool |
| 2 | |
| 3 | Expert guidance for writing code inside `@n8n/n8n-nodes-langchain.toolCode` — the tool an AI Agent can invoke, **not** the regular workflow Code node. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## ⚠️ This is NOT the Code node |
| 8 | |
| 9 | The Custom Code Tool looks like a Code node in the editor — same JavaScript editor, similar layout — but it is a **completely different node** from a different package with a **different runtime contract**. |
| 10 | |
| 11 | | | Code node | Custom Code Tool | |
| 12 | |---|---|---| |
| 13 | | **Node type** | `n8n-nodes-base.code` | `@n8n/n8n-nodes-langchain.toolCode` | |
| 14 | | **Package** | `n8n-nodes-base` | `@n8n/n8n-nodes-langchain` | |
| 15 | | **Invoked by** | Previous node (workflow flow) | AI Agent (LangChain) | |
| 16 | | **Input** | `$input.all()` — item stream | `query` — string or object from LLM | |
| 17 | | **Return** | `[{json: {...}}]` (items array) | **A string** | |
| 18 | | **`$fromAI()`** | N/A | **Not available** (see Errors) | |
| 19 | | **HTTP helper** | `this.helpers.httpRequest` (auth helpers blocked) | Not exposed to the tool sandbox | |
| 20 | | **State** | Per-run execution data | No `getContext`, no `$getWorkflowStaticData` | |
| 21 | |
| 22 | **If you treat it like a Code node, it fails.** The rest of this skill covers the Code Tool's actual contract. |
| 23 | |
| 24 | --- |
| 25 | |
| 26 | ## Quick Start |
| 27 | |
| 28 | ### Minimal JavaScript Code Tool |
| 29 | |
| 30 | ```javascript |
| 31 | // `query` is whatever the AI sent (a string by default) |
| 32 | return `You asked: ${query}`; |
| 33 | ``` |
| 34 | |
| 35 | ### Minimal Python Code Tool |
| 36 | |
| 37 | ```python |
| 38 | # `_query` is whatever the AI sent (a string by default) |
| 39 | return f"You asked: {_query}" |
| 40 | ``` |
| 41 | |
| 42 | ### Essential Rules |
| 43 | |
| 44 | 1. **Return a string.** Numbers are auto-converted. Anything else throws `"The response property should be a string, but it is an object"`. |
| 45 | 2. **Input variable is fixed**: `query` (JS), `_query` (Python). You cannot rename it. |
| 46 | 3. **Do NOT use `$fromAI()`** inside the Code Tool sandbox — it throws `"No execution data available"`. |
| 47 | 4. **Do NOT use `[{json: {...}}]`** return format — that's for Code nodes. Throws `"Wrong output type returned"`. |
| 48 | 5. **Use a descriptive tool name** (letters/numbers/underscores, v1.1+). The agent calls the tool by its name. |
| 49 | 6. **Write a precise description** — the LLM decides whether to invoke the tool based on it. |
| 50 | |
| 51 | --- |
| 52 | |
| 53 | ## The Two Input Modes |
| 54 | |
| 55 | The Code Tool has two input shapes, controlled by `specifyInputSchema`: |
| 56 | |
| 57 | ### Mode 1: Unstructured (default, `specifyInputSchema: false`) |
| 58 | |
| 59 | The AI passes **a single string** as `query`. If you need multiple fields, the AI has to stuff them into that one string and you parse them out. In practice, LLMs will happily pass a JSON string if your description tells them to. |
| 60 | |
| 61 | ```javascript |
| 62 | // Parse a JSON string the AI sent |
| 63 | let params; |
| 64 | try { |
| 65 | params = typeof query === 'string' ? JSON.parse(query) : query; |
| 66 | } catch (e) { |
| 67 | throw new Error('Expected a JSON object. Parser said: ' + e.message); |
| 68 | } |
| 69 | const price = Number(params.price); |
| 70 | const months = Number(params.months); |
| 71 | // ... |
| 72 | return JSON.stringify({ monthly_payment: /* ... */ }); |
| 73 | ``` |
| 74 | |
| 75 | **Pros**: simplest to set up, one field to describe. |
| 76 | **Cons**: no schema validation — if the LLM forgets a field, the tool throws at runtime. |
| 77 | |
| 78 | **Best for**: quick prototypes, tools with one natural input (a question, a URL, a text blob). |
| 79 | |
| 80 | ### Mode 2: Structured (`specifyInputSchema: true`) |
| 81 | |
| 82 | The tool becomes a LangChain `DynamicStructuredTool`. The LLM sees a typed argument schema and passes a **validated object** as `query`. You access fields directly. |
| 83 | |
| 84 | ```javascript |
| 85 | // query is now an object matching your schema |
| 86 | const price = query.price; |
| 87 | const months = query.months; |
| 88 | const residual_percent = query.residual_percent; |
| 89 | |
| 90 | const monthly = computeAnnuity(price, months, residual_percent); |
| 91 | return JSON.stringify({ monthly_payment: monthly }); |
| 92 | ``` |
| 93 | |
| 94 | Schema is defined via either: |
| 95 | - `schemaType: "fromJson"` + `jsonSchemaExample` (n8n v≥1.3) — paste an example JSON, n8n infers the schema |
| 96 | - `schemaType: "manual"` + `inputSchema` — write a full JSON Schema yourself |
| 97 | |
| 98 | **Pros**: LLM gets type hints, invalid calls rejected before your code runs, cleaner code. |
| 99 | **Cons**: a little more setup; requi |