agentsclimarketplace

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

Install
npx -y skills add Impertio-Studio/n8n-Claude-Skill-Package --skill n8n-impl-custom-nodes

Assembled 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

TaskCommand / Action
Scaffold projectClone n8n-io/n8n-nodes-starter from GitHub
Install dependenciesnpm install
Development modenpm run dev (hot-reload at http://localhost:5678)
Buildnpm run build (compiles to dist/)
Lintnpm run lint
Publishnpm 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 keywords to ["n8n-community-node-package"] — n8n uses this to discover community nodes
  • ALWAYS point n8n.nodes and n8n.credentials to compiled .js files in dist/
  • NEVER point to .ts source files in the n8n field
  • ALWAYS use n8n-nodes- prefix for the package name
  • ALWAYS declare n8n-workflow as a peerDependency, 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 group to ['trigger'] and inputs to [] for trigger nodes
  • ALWAYS use NodeConnectionTypes.Main from n8n-workflow (NEVER hardcode 'main')
  • ALWAYS provide a defaults.name that matches displayName

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 i to this.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.baseURL for the API base URL
  • ALWAYS use routing.request on operation options to define HTTP method and URL
  • ALWAYS use routing.send on 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 name property with the name referenced in node credentials array
  • ALWAYS use authenticate for declarative nodes (auto-injects auth into requests)
  • For programmatic nodes, retrieve credentials via await this.getCredentials('myApi') in execute()

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 nock definitions in the test data
  • ALWAYS define expected nodeData output for assertion
  • ALWAYS specify nodeExecutionOrder to verify execution flow

Publishing Checklist

  • Package name starts with n8n-nodes-
  • keywords includes "n8n-community-node-package"
  • n8n.nodes and n8n.credentials point to dist/*.js files
  • n8n-workflow is in peerDependencies
  • files field includes ["dist"]
  • npm run build succeeds without errors
  • npm run lint passes
  • Icon is included (SVG preferred)
  • README.md describes installation and available nodes

Reference Links

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.