data/skills/n8n-code-tool/INPUT_SCHEMA.md
How to turn @n8n/n8n-nodes-langchain.toolCode into a DynamicStructuredTool so the LLM passes typed arguments instead of a free-form string.
Without a schema, the Code Tool is a LangChain DynamicTool:
With a schema, the Code Tool becomes a DynamicStructuredTool:
Number(params.price) for every field)Cost: a little config to define the schema, and the node must be on a version that supports it.
Set specifyInputSchema: true on the toolCode parameters. Two schema-definition styles:
fromJson — paste a representative example (v≥1.3, recommended)The easiest. Give n8n an example JSON, and it infers the schema for you.
{
"parameters": {
"name": "calculate_car_loan",
"description": "Computes monthly car-loan payment using an annuity formula with optional balloon.",
"language": "javaScript",
"specifyInputSchema": true,
"schemaType": "fromJson",
"jsonSchemaExample": "{\n \"price\": 439900,\n \"down_payment\": 87980,\n \"interest_rate\": 6.95,\n \"months\": 36,\n \"residual_percent\": 50,\n \"setup_fee\": 695,\n \"monthly_admin_fee\": 59\n}",
"jsCode": "// query is now a validated OBJECT, not a string\nconst { price, down_payment, interest_rate, months, residual_percent, setup_fee = 0, monthly_admin_fee = 0 } = query;\n\nconst principal = price - down_payment;\nconst residual = price * (residual_percent / 100);\nconst r = interest_rate / 100 / 12;\nconst growth = Math.pow(1 + r, months);\nconst base = r === 0\n ? (principal - residual) / months\n : (principal - residual / growth) * r / (1 - 1 / growth);\nconst monthly_payment = base + monthly_admin_fee;\n\nreturn JSON.stringify({\n monthly_payment_sek: Math.round(monthly_payment),\n loan_amount: Math.round(principal)\n});"
},
"type": "@n8n/n8n-nodes-langchain.toolCode",
"typeVersion": 1.3,
"name": "calculate_car_loan"
}
How it works: n8n looks at the example, infers {price: number, down_payment: number, ...}, and generates a JSON Schema. The LLM sees that schema and passes a validated object.
manual — write the JSON Schema yourselfUse when you need descriptions per field, enums, min/max constraints, or optional fields.
{
"parameters": {
"name": "calculate_car_loan",
"description": "Computes monthly car-loan payment.",
"language": "javaScript",
"specifyInputSchema": true,
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"required\": [\"price\", \"down_payment\", \"interest_rate\", \"months\", \"residual_percent\"],\n \"properties\": {\n \"price\": { \"type\": \"number\", \"description\": \"Car price in SEK\" },\n \"down_payment\": { \"type\": \"number\", \"description\": \"Down payment in SEK\" },\n \"interest_rate\": { \"type\": \"number\", \"description\": \"Annual nominal rate in percent, e.g. 6.95\" },\n \"months\": { \"type\": \"integer\", \"minimum\": 1, \"description\": \"Loan term in months\" },\n \"residual_percent\": { \"type\": \"number\", \"minimum\": 0, \"maximum\": 99, \"description\": \"Balloon as % of price\" },\n \"setup_fee\": { \"type\": \"number\", \"default\": 0 },\n \"monthly_admin_fee\": { \"type\": \"number\", \"default\": 0 }\n }\n}",
"jsCode": "const { price, down_payment, interest_rate, months, residual_percent, setup_fee = 0, monthly_admin_fee = 0 } = query;\n// ... same computation as above ...\nreturn JSON.stringify({ monthly_payment_sek: /*...*/ });"
},
"type": "@n8n/n8n-nodes-langchain.toolCode",
"typeVersion": 1.3,
"name": "calculate_car_loan"
}
When manual is worth it:
description strings (the LLM reads these)enum values (e.g. currency: ["SEK", "EUR", "USD"])minimum, maximum)query behaves with a schemaSource of truth from the ToolCode sandbox:
const sandbox = new JsTaskRunnerSandbox(workflowMode, ctx, undefined, { query });
The sandbox always receives { query }. The difference is what query holds:
| Mode | Type of query | How to use |
|---|---|---|
| No schema | string | JSON.parse(query) if you want structure |
| With schema | object (validated) | Destructure: const { price, months } = query; |
In Python, the same applies — _query is a string without schema, a dict with schema.
specifyInputSchema and schemaType: "manual" with inputSchema: available in v1.2schemaType: "fromJson" with jsonSchemaExample: requires v≥1.3Set typeVersion: 1.3 on the node if you want fromJson. Older installs should use manual.
Does your tool need more than one input field?
├─ No (just a URL, question, text blob)
│ └─ Unstructured — skip the schema
├─ Yes, and fields are all typed (numbers, bools, enums)
│ └─ Structured with fromJson (easiest)
├─ Yes, and you need constraints or rich descriptions
│ └─ Structured with manual
└─ Yes, and fields are complex / reusable across agents
└─ Use toolWorkflow (sub-workflow tool) instead of toolCode
jsonSchemaExample and inputSchema are strings containing JSON, not objects. Watch the escaping when you paste them into workflow JSON. If the node won't save or the LLM doesn't see the fields, validate the JSON separately first.
If an agent was already started with an unstructured tool and you flip it to structured, the agent's system prompt may still reflect the old contract until it's reloaded. Force a re-run / re-open the agent node after changing schema settings.