N8n impl custom nodes
Skill Impertio-Studio/n8n-Claude-Skill-Package/skills/source/n8n-impl/n8n-impl-custom-nodes
21 deterministic Claude AI skills for n8n v1.x workflow automation
npx -y skills add Impertio-Studio/n8n-Claude-Skill-Package --skill n8n-impl-custom-nodesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when creating custom n8n nodes or building community node packages for npm. Prevents packaging errors from incorrect package.json n8n field configuration or missing credential registration. Covers n8n-nodes-starter template, community node packaging, project structure, programmatic vs declarative node styles, testing with WorkflowTestData, and publishing to npm. Keywords: n8n, custom node, community node, npm, INodeType, starter template, build custom node, create node, npm package, community node, extend n8n..
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.3 KB, as published. Nobody here has run it
n8n Custom Node Development
Build, test, and publish custom n8n community nodes using TypeScript.
Quick Reference
| Task | Command / Action |
|---|---|
| Scaffold project | Clone n8n-io/n8n-nodes-starter from GitHub |
| Install dependencies | npm install |
| Development mode | npm run dev (hot-reload at http://localhost:5678) |
| Build | npm run build (compiles to dist/) |
| Lint | npm run lint |
| Publish | npm publish (package name MUST start with n8n-nodes-) |
Decision Tree: Programmatic vs Declarative
Need a custom node?
├── Is it a REST API wrapper with standard CRUD operations?
│ └── YES → Use DECLARATIVE style (routing in properties, NO execute() method)
├── Does it require complex data transformation or custom logic?
│ └── YES → Use PROGRAMMATIC style (execute() method with full control)
└── Does it need to call multiple APIs or combine data sources?
└── YES → Use PROGRAMMATIC style
Project Structure
n8n-nodes-<service-name>/
├── package.json # Node registration via n8n field (CRITICAL)
├── tsconfig.json # TypeScript config
├── credentials/
│ └── MyApi.credentials.ts # Credential definitions
├── nodes/
│ └── MyNode/
│ ├── MyNode.node.ts # Main node implementation
│ ├── MyNode.node.json # Codex metadata (optional, for search/AI)
│ └── resources/ # Resource descriptions (declarative style)
├── icons/
│ └── my-icon.svg # Node icon (SVG or PNG, max 60x60px)
└── dist/ # Compiled output (generated by build)
package.json n8n Field (CRITICAL)
The n8n field in package.json registers your nodes and credentials with n8n.
{
"name": "n8n-nodes-myservice",
"version": "0.1.0",
"keywords": ["n8n-community-node-package"],
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/MyApi.credentials.js"
],
"nodes": [
"dist/nodes/MyNode/MyNode.node.js"
]
},
"files": ["dist"],
"peerDependencies": {
"n8n-workflow": "*"
}
}
Rules:
- ALWAYS set
keywordsto["n8n-community-node-package"]— n8n uses this to discover community nodes - ALWAYS point
n8n.nodesandn8n.credentialsto compiled.jsfiles indist/ - NEVER point to
.tssource files in the n8n field - ALWAYS use
n8n-nodes-prefix for the package name - ALWAYS declare
n8n-workflowas apeerDependency, NEVER as a regular dependency
Node Description (INodeTypeDescription)
Every node MUST define a description property with these required fields:
description: INodeTypeDescription = {
displayName: 'My Node', // Human-readable name shown in UI
name: 'myNode', // Internal name (camelCase, unique)
icon: 'file:myIcon.svg', // Icon reference
group: ['transform'], // 'input' | 'output' | 'transform' | 'trigger'
version: 1, // Node version (number or number[])
description: 'Short description', // Shown in node creator panel
defaults: { name: 'My Node' }, // Default instance name
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [ // Credential requirements
{ name: 'myApi', required: true },
],
properties: [ /* INodeProperties[] */ ],
};
Rules:
- ALWAYS set
groupto['trigger']andinputsto[]for trigger nodes - ALWAYS use
NodeConnectionTypes.Mainfromn8n-workflow(NEVER hardcode'main') - ALWAYS provide a
defaults.namethat matchesdisplayName
Programmatic Node Pattern
Use when you need full control over execution logic.
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
export class MyNode implements INodeType {
description: INodeTypeDescription = { /* ... */ };
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const param = this.getNodeParameter('myParam', i) as string;
const credentials = await this.getCredentials('myApi');
const response = await this.helpers.httpRequest({
method: 'GET',
url: `${credentials.baseUrl}/endpoint`,
headers: { Authorization: `Bearer ${credentials.apiKey}` },
});
returnData.push({ json: response as IDataObject });
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: (error as Error).message }, pairedItem: { item: i } });
continue;
}
throw error;
}
}
return [returnData];
}
}
Rules:
- ALWAYS iterate over all input items (
this.getInputData()) - ALWAYS pass the item index
itothis.getNodeParameter()— it resolves expressions per item - ALWAYS handle
this.continueOnFail()in the catch block - ALWAYS include
pairedItem: { item: i }when pushing error items - ALWAYS return a 2D array:
[returnData]for single output,[trueItems, falseItems]for two outputs
Declarative Node Pattern
Use for REST API wrappers. n8n handles HTTP requests automatically.
export class MyApiNode implements INodeType {
description: INodeTypeDescription = {
/* ... standard fields ... */
requestDefaults: {
baseURL: 'https://api.example.com',
headers: { Accept: 'application/json' },
},
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Get',
value: 'get',
routing: {
request: { method: 'GET', url: '=/items/{{$parameter.itemId}}' },
},
},
],
default: 'get',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
routing: {
send: { type: 'body', property: 'title' },
},
},
],
};
// NO execute() method — n8n handles requests via routing config
}
Rules:
- NEVER define an
execute()method on declarative nodes - ALWAYS define
requestDefaults.baseURLfor the API base URL - ALWAYS use
routing.requeston operation options to define HTTP method and URL - ALWAYS use
routing.sendon parameter properties to map values to request body/query
Credential Definition
import type { ICredentialType, INodeProperties, IAuthenticate } from 'n8n-workflow';
export class MyApi implements ICredentialType {
name = 'myApi'; // Must match credential name in node
displayName = 'My API';
documentationUrl = 'https://docs.example.com';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true },
default: '',
},
];
authenticate: IAuthenticate = {
type: 'generic',
properties: {
headers: { Authorization: '=Bearer {{$credentials.apiKey}}' },
},
};
}
Rules:
- ALWAYS set
typeOptions: { password: true }for sensitive fields (API keys, tokens, passwords) - ALWAYS match the credential
nameproperty with the name referenced in nodecredentialsarray - ALWAYS use
authenticatefor declarative nodes (auto-injects auth into requests) - For programmatic nodes, retrieve credentials via
await this.getCredentials('myApi')inexecute()
Icon Handling
- ALWAYS use SVG format (preferred) or PNG (fallback)
- ALWAYS place icons in the project root
icons/directory or next to the node file - Reference format:
icon: 'file:myIcon.svg'(relative to node file or icons dir) - For themed icons:
icon: { light: 'file:icon.svg', dark: 'file:icon.dark.svg' } - For FontAwesome:
icon: 'fa:clock'(limited set available) - Maximum recommended size: 60x60 pixels
Development Workflow
Step 1: Scaffold
git clone https://github.com/n8n-io/n8n-nodes-starter.git n8n-nodes-myservice
cd n8n-nodes-myservice
rm -rf .git
npm install
Step 2: Develop
npm run dev # Starts n8n with your node loaded, hot-reload enabled
Open http://localhost:5678 — your custom node appears in the node creator panel.
Step 3: Build and Lint
npm run build # Compiles TypeScript to dist/
npm run lint # Checks code quality (ALWAYS run before publishing)
Step 4: Test Locally with npm link
# In your node package directory:
npm link
# In your n8n installation directory:
npm link n8n-nodes-myservice
# Restart n8n to load the linked package
Step 5: Publish
npm publish # Publishes to npm registry
Testing with WorkflowTestData
See references/examples.md for the complete test setup pattern.
Key points:
- ALWAYS mock external API calls using
nockdefinitions in the test data - ALWAYS define expected
nodeDataoutput for assertion - ALWAYS specify
nodeExecutionOrderto verify execution flow
Publishing Checklist
- Package name starts with
n8n-nodes- -
keywordsincludes"n8n-community-node-package" -
n8n.nodesandn8n.credentialspoint todist/*.jsfiles -
n8n-workflowis inpeerDependencies -
filesfield includes["dist"] -
npm run buildsucceeds without errors -
npm run lintpasses - Icon is included (SVG preferred)
- README.md describes installation and available nodes