[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F478DD4DCTRT8YCD32HE1H":3,"skill-same:01M6F478DD4DCTRT8YCD32HE1H":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":59,"ownerHtmlUrl":60,"ownerBlogUrl":6,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M6F478DD4DCTRT8YCD32HE1H","building-n8n-nodes",null,"Builds custom community nodes for n8n, the workflow automation platform. Activates when the user wants to create, scaffold, develop, test, lint, or publish an n8n node — including both declarative (REST API) and programmatic styles. Also triggers when the user mentions n8n nodes, n8n-cli, n8n-node, community nodes, node credentials, or anything related to extending n8n with custom integrations. Encodes all official best practices from n8n's documentation.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fgeckse\u002Fn8n-skills\u002Fbuilding-n8n-nodes\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"building n8n nodes","n8n","Nodes","credentials","triggers","platform","community","node","integrations","api","cli","publish","Workflow Automation","best practices","lint","create","documentation","official","automation","workflow","2026-11-10 10:12:30",false,"---\nname: building-n8n-nodes\ndescription: \"Builds custom community nodes for n8n, the workflow automation platform. Activates when the user wants to create, scaffold, develop, test, lint, or publish an n8n node — including both declarative (REST API) and programmatic styles. Also triggers when the user mentions n8n nodes, n8n-cli, n8n-node, community nodes, node credentials, or anything related to extending n8n with custom integrations. Encodes all official best practices from n8n's documentation.\"\n---\n\n# n8n Node Builder\n\nBuild production-ready custom nodes for n8n using the official `n8n-node` CLI tool and n8n's best practices.\n\n## When You Need More Detail\n\nThis skill uses progressive disclosure. The SKILL.md covers the full workflow and decision-making. For complete code templates, read these reference files:\n\n- `references\u002Fdeclarative-node.md` — Full declarative node template with routing, credentials, and codex file\n- `references\u002Fprogrammatic-node.md` — Full programmatic node template with execute method, error handling, item linking, and trigger patterns\n- `references\u002Fcredentials.md` — All credential\u002Fauth patterns (API key, Bearer, OAuth2, Basic, Custom, testedBy)\n- `references\u002Fpublishing.md` — Linting, testing, releasing, and verification checklist\n- `references\u002Fcommon-mistakes.md` — Error catalog with 36 numbered mistake patterns and fixes\n\nRead the appropriate reference file before writing any code.\n\n## Workflow Overview\n\nBuilding an n8n node follows this sequence:\n\n1. **Decide** on the node style (declarative vs programmatic)\n2. **Scaffold** the project with the `n8n-node` CLI\n3. **Implement** the node base file, credentials file, and codex file\n4. **Test** locally with `npm run dev`\n5. **Lint** with `npm run lint`\n6. **Publish** to npm and optionally submit for verification\n\n## Step 1: Choose Your Node Style\n\nn8n has two node-building styles. Picking the right one up front saves significant rework.\n\n### Declarative Style (preferred for REST APIs)\n\nUse declarative when the integration is a REST API wrapper. It's JSON-based, simpler, more future-proof, and faster to get approved for n8n Cloud.\n\nThe declarative style handles data flow through a `routing` key inside the operations object. There's no `execute()` method — n8n constructs HTTP requests from the JSON description automatically.\n\nDeclarative nodes support advanced patterns beyond simple routing: declarative dynamic dropdowns via `typeOptions.loadOptions.routing` with `setKeyValue`\u002F`sort` postReceive transforms, dynamic property paths using `$parent`\u002F`$index` expressions for nested body structures, routing on any parameter field (not just operations), `preSend` functions (including factory patterns) to transform request bodies before sending, custom `postReceive` functions to transform responses (including binary file handling with `binaryData` type), custom pagination functions with duplicate detection, three pagination modes (offset, generic token-based, and cursor-based via custom functions), `resourceLocator` parameters for multi-mode entity selection (list\u002FURL\u002FID), `resourceMapper` for dynamic field mapping UIs, `fixedCollection` for structured filters\u002Fsort rules, advanced `displayOptions` with `_cnd` operators (eq, not, gte, lte, startsWith, includes, regex, exists, etc.) and `@version`\u002F`@tool` special keys, `ignoreHttpStatusErrors` for custom error handling in postReceive, conditional transforms with `enabled`\u002F`errorMessage` on all postReceive types, `propertyInDotNotation` control for literal dot keys, dynamic base URLs from credentials, and a `methods` object for `listSearch`, `loadOptions`, and `resourceMapping`. See `references\u002Fdeclarative-node.md` → \"Advanced Declarative Patterns\" for complete templates and a TypeScript type reference.\n\n**Choose declarative when:**\n- The API is REST-based\n- You want a simpler, lower-risk codebase\n- Even if you need custom request\u002Fresponse transformation (use `preSend`\u002F`postReceive` functions)\n- Even if you need file upload\u002Fdownload (use `preSend` with form-data, `postReceive` with binary data)\n- Even if you need dynamic field mapping (use `resourceMapper`)\n- Even if you need custom pagination logic (use custom pagination functions)\n\n### Programmatic Style (required for advanced use cases)\n\nUse programmatic when you need full control over execution. It requires an `execute()` method that reads inputs, builds requests, and returns results manually.\n\n**You must use programmatic for:**\n- Trigger nodes (webhook, polling, or other event-driven)\n- GraphQL APIs\n- Non-REST protocols\n- Complex multi-step logic that chains multiple sequential API calls where later calls depend on earlier results\n\n### Quick Decision\n\nAsk: \"Is this a REST API with no triggers and no multi-call chaining?\" If yes → declarative (even for complex request\u002Fresponse transformation, pagination, file handling, and field mapping — use `preSend`\u002F`postReceive` functions). Otherwise → programmatic.\n\n## Step 2: Scaffold with the n8n-node CLI\n\nThe CLI sets up the correct project structure, dependencies, linter config, and build scripts automatically.\n\n### Option A: Without installing (recommended)\n\n```bash\nnpm create @n8n\u002Fnode@latest n8n-nodes-\u003CYOUR_NODE_NAME> -- --template \u003Ctemplate>\n```\n\nTemplates:\n- `declarative\u002Fgithub-issues` — Demo with multiple operations and credentials (good for learning)\n- `declarative\u002Fcustom` — Blank declarative starting point (prompts for base URL, auth type)\n- `programmatic\u002Fexample` — Programmatic with full flexibility\n\n### Option B: Install globally\n\n```bash\nnpm install --global @n8n\u002Fnode-cli\nn8n-node new n8n-nodes-\u003CYOUR_NODE_NAME> --template \u003Ctemplate>\n```\n\n### Option C: Clone the n8n-nodes-starter repo\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fn8n-io\u002Fn8n-nodes-starter.git n8n-nodes-\u003CYOUR_NODE_NAME>\ncd n8n-nodes-\u003CYOUR_NODE_NAME>\nrm -rf .git && git init\nnpm install\n```\n\nThe starter provides pre-configured TypeScript, ESLint, build scripts, and example files. After cloning, rename\u002Freplace the example node and credential files with your own and update `package.json`.\n\n### Naming Rules\n\nPackage names must follow one of these formats:\n- `n8n-nodes-\u003CNAME>` (e.g., `n8n-nodes-acme`)\n- `@\u003CORG>\u002Fn8n-nodes-\u003CNAME>` (e.g., `@myorg\u002Fn8n-nodes-acme`)\n\nAfter scaffolding, the project looks like:\n\n```\nn8n-nodes-\u003Cname>\u002F\n├── package.json          # Must contain \"n8n\" attribute listing nodes and credentials\n├── tsconfig.json\n├── .eslintrc.js          # Don't edit — contains n8n linter config\n├── nodes\u002F\n│   └── \u003CNodeName>\u002F\n│       ├── \u003CNodeName>.node.ts      # Base file — the node's core logic\n│       ├── \u003CNodeName>.node.json    # Codex file — metadata for n8n's node panel\n│       └── \u003CNodeName>.svg          # Icon — square SVG recommended\n├── credentials\u002F\n│   └── \u003CNodeName>Api.credentials.ts  # Credential file\n└── dist\u002F                 # Built output (generated by build command)\n```\n\n## Step 3: Implement the Node\n\nEvery node needs three files at minimum: the base file, the codex file, and the credentials file (unless no auth is needed).\n\n### 3A: The Node Base File (`\u003CName>.node.ts`)\n\nThis is the heart of the node. It exports a class implementing `INodeType` with a `description` object.\n\n**Critical rules:**\n- The class name must match the filename (e.g., class `Acme` → file `Acme.node.ts`)\n- Use `NodeConnectionType.Main` for inputs\u002Foutputs (imported from `n8n-workflow`). If your `n8n-workflow` version exports it as type-only, use the string `'main'` as fallback\n- The `name` field in the description must be a camelCase unique identifier\n- Use Title Case for `displayName` and all UI-facing strings\n- Always set `noDataExpression: true` on Resource and Operation selectors\n- Always include `action` on every operation option (e.g., `action: 'Create a contact'`)\n- Use `import type` for symbols only used in type annotations (rule of thumb: if a symbol only appears in `: Type` annotations, function signatures, or `as Type` casts, use `import type`; if it's used as a value like `throw new NodeApiError(...)`, use regular import)\n- Dynamic expressions in routing must start with `=` prefix: `'=\u002Fcontacts\u002F{{$parameter[\"id\"]}}'`\n- **Declarative nodes cannot have an `execute()` method** — if `requestDefaults` is present, n8n uses the routing engine and ignores `execute()`. Use one or the other\n- The `execute()` method must return `[returnData]` — an array of arrays (one per output connector). Forgetting the outer array is a common error\n\n**Standard description parameters** (same for both styles):\n\n| Parameter | Type | Purpose |\n|-----------|------|---------|\n| `displayName` | string | Name shown in the UI |\n| `name` | string | Internal camelCase identifier |\n| `icon` | string | `'file:\u003Cname>.svg'` — reference the icon file |\n| `group` | string[] | `['transform']` for action nodes, `['trigger']` for triggers |\n| `version` | number or number[] | Start at `1`; use array for light versioning |\n| `subtitle` | string | Template shown below node name, e.g. `'={{$parameter[\"operation\"]}}'` |\n| `description` | string | Short description for the node panel |\n| `defaults` | object | `{ name: 'Display Name' }` |\n| `inputs` | array | `[NodeConnectionType.Main]` |\n| `outputs` | array | `[NodeConnectionType.Main]` |\n| `usableAsTool` | boolean | `true` — enables use as an AI agent tool (recommended) |\n| `credentials` | array | `[{ name: 'credName', required: true }]` |\n| `properties` | array | Resource, operation, and field definitions |\n\n**For declarative nodes**, also add:\n- `requestDefaults: { baseURL: '...', headers: { Accept: 'application\u002Fjson' } }` — supports dynamic expressions from credentials (e.g., `'={{ !$credentials.customBaseUrl ? \"https:\u002F\u002Fapi.example.com\u002Fv1\" : $credentials.baseUrl }}'`)\n- Operations use a `routing` key to define HTTP method, URL, query strings, and body — use `encodeURIComponent()` for user values in URLs\n- `routing` can be placed on any parameter (not just operations) — fields, fixedCollections, etc.\n- Use `typeOptions.loadOptions.routing` for declarative dynamic dropdowns with `setKeyValue`\u002F`sort` postReceive transforms\n- Use dynamic property paths with `$parent`, `$index` expressions (e.g., `'=attributes.{{$parent.fieldName}}'`, `'=items[{{$index}}].value'`)\n- Operations can use `routing.send.preSend` array for custom request transformation functions\n- Operations can use `routing.output.postReceive` array for custom response transformation (including binary file handling)\n- Operations can use `routing.operations.pagination` for custom pagination functions\n- Use `type: 'resourceLocator'` for multi-mode entity selection (list\u002FURL\u002FID) with `methods.listSearch`\n- Use `type: 'resourceMapper'` for dynamic field mapping UIs with `methods.resourceMapping`\n- Use `type: 'fixedCollection'` for structured parameter groups (filters, sort rules) with `$index` for array mapping\n- Combine `displayOptions.show` and `displayOptions.hide` for fine-grained field visibility\n- Split operations\u002Ffields into separate `*Description.ts` files per resource, spread into the main node\n- Define a `methods` object on the class for `listSearch`, `loadOptions`, and `resourceMapping`\n\n**For programmatic nodes**, also add:\n- An `async execute()` method\n- Proper item looping with `this.getInputData()` and `pairedItem` linking\n\nFor complete templates, read the appropriate reference file before coding:\n- Declarative → Read `references\u002Fdeclarative-node.md`\n- Programmatic → Read `references\u002Fprogrammatic-node.md`\n\n### 3B: The Resource → Operation Pattern\n\nn8n nodes follow a consistent UI pattern: **Resource** (what entity) → **Operation** (what action).\n\nEach resource gets a dropdown, each operation gets a dropdown filtered by the selected resource using `displayOptions.show`. Operations should map to CRUD verbs where applicable: Create, Create or Update (Upsert), Delete, Get, Get Many, Update. Use the `action` field on each operation option to provide a human-readable description (e.g., `action: 'Create a contact'`). For Upsert, use displayName \"Create or Update\" with description \"Create a new record or update an existing one (upsert)\".\n\n**Important naming rule:** The linter enforces naming list operations **\"Get Many\"** (not \"Get All\"). The operation value should be `getAll` but the display name must be `Get Many`.\n\n### Return All \u002F Limit Pattern\n\nFor list (\"Get Many\") operations, always include a `returnAll` boolean toggle (default `false`, description `'Whether to return all results or only up to a given limit'`) paired with a conditional `limit` number field that only shows when `returnAll` is `false` (`displayOptions: { show: { returnAll: [false] } }`). This is the standard pattern used across all n8n built-in nodes. See both reference templates for complete examples.\n\n### 3C: displayOptions and Conditional Fields\n\nUse `displayOptions.show` to conditionally display fields based on the selected resource, operation, or other parameter values (e.g., `show: { resource: ['contact'], operation: ['create'] }`). For version-specific fields, use `'@version'`: `displayOptions: { show: { '@version': [2] } }`.\n\n### 3D: Additional Fields (Optional Parameters)\n\nGroup optional parameters under a collection named \"Additional Fields\":\n\n```typescript\n{\n  displayName: 'Additional Fields',\n  name: 'additionalFields',\n  type: 'collection',\n  placeholder: 'Add Field',\n  default: {},\n  displayOptions: {\n    show: { resource: ['contact'], operation: ['create'] },\n  },\n  options: [\n    \u002F\u002F Individual optional fields here\n  ],\n}\n```\n\n### 3E: The Codex File (`\u003CName>.node.json`)\n\nMetadata controlling how the node appears in n8n's node discovery panel:\n\n```json\n{\n  \"node\": \"n8n-nodes-\u003Cpackage>.\u003CnodeName>\",\n  \"nodeVersion\": \"1.0\",\n  \"codexVersion\": \"1.0\",\n  \"categories\": [\"Miscellaneous\"],\n  \"resources\": {\n    \"credentialDocumentation\": [{ \"url\": \"\" }],\n    \"primaryDocumentation\": [{ \"url\": \"\" }]\n  }\n}\n```\n\nThe `node` field format is `\u003Cnpm-package-name>.\u003Cnode-internal-name>` (e.g., `n8n-nodes-acme.acmeService`).\n\nCategories: Analytics, Communication, Data & Storage, Development, Finance & Accounting, Marketing & Content, Miscellaneous, Productivity, Sales, Utility.\n\n### 3F: Credentials\n\nRead `references\u002Fcredentials.md` for complete patterns. Key points:\n- File: `credentials\u002F\u003CName>Api.credentials.ts`\n- Class implements `ICredentialType`\n- `name` must match the node's `credentials[].name`\n- Use `authenticate: IAuthenticateGeneric` for header\u002Fbody\u002Fquery auth\n- Use `test: ICredentialTestRequest` to validate credentials (or `testedBy` in the node for complex validation)\n- Always use `$credentials` (plural) in expressions — `$credential` (singular) is wrong\n- The linter requires an `icon` property using `Icon` type from n8n-workflow\n\n### 3G: The Icon\n\nSVG is recommended (square aspect ratio). PNG alternative: 60×60px. Place alongside the `.node.ts` file. Reference with `icon: 'file:\u003Cname>.svg'`. For light\u002Fdark variants: `icon: { light: 'file:icon.svg', dark: 'file:icon.dark.svg' }`. Don't reference Font Awesome — download and embed.\n\n## Step 4: Error Handling (Programmatic Nodes)\n\nUse `NodeApiError` for API errors and `NodeOperationError` for validation errors (both from `n8n-workflow`). Wrap each item's processing in `try\u002Fcatch` and support `continueOnFail()` so users can choose to keep going on errors — push `{ json: { error: message }, pairedItem: { item: i } }` on failure. See `references\u002Fprogrammatic-node.md` → \"Error Handling Patterns\" for full examples including HTTP status-specific handling.\n\n## Step 5: Item Linking (pairedItem \u002F constructExecutionMetaData)\n\nEvery output item in a programmatic node must link back to its source input. There are two approaches:\n\n**Modern approach (recommended):** Use `constructExecutionMetaData`:\n```typescript\nconst executionData = this.helpers.constructExecutionMetaData(\n  this.helpers.returnJsonArray(responseData),\n  { itemData: { item: i } },\n);\nreturnData.push(...executionData);\n```\n\n**Manual approach:** Set `pairedItem` directly:\n```typescript\nreturnData.push({\n  json: responseData,\n  pairedItem: { item: i },\n});\n```\n\nWithout item linking, n8n can't trace data flow between nodes.\n\n## Step 6: HTTP Helpers\n\nUse n8n's built-in helpers — no external HTTP libraries:\n\n```typescript\n\u002F\u002F Without auth:\nconst response = await this.helpers.httpRequest(options);\n\n\u002F\u002F With auth (handles credential injection automatically):\nconst response = await this.helpers.httpRequestWithAuthentication.call(\n  this, 'credentialTypeName', options\n);\n```\n\n**Deprecation warning:** `this.helpers.requestWithAuthentication` and `IRequestOptions` are **deprecated**. Always use `httpRequestWithAuthentication` with `IHttpRequestOptions`. The new interface uses `url` (not `uri`) and defaults to JSON parsing.\n\n### GenericFunctions.ts Pattern\n\nFor programmatic nodes, create a `GenericFunctions.ts` helper to centralize HTTP logic. Include `IHookFunctions`, `IWebhookFunctions`, and `IPollFunctions` in the `this` type union for trigger node compatibility. See `references\u002Fprogrammatic-node.md` → \"GenericFunctions.ts Pattern\" for the full template with pagination variants.\n\n### Dynamic Options (loadOptionsMethod)\n\nUse `loadOptionsMethod` for dropdowns that fetch values from an API at runtime. Define a `methods.loadOptions` object in the node class, with each method returning `Array\u003C{ name: string, value: string }>`. See `references\u002Fprogrammatic-node.md` for the complete pattern.\n\n## Step 7: Node Versioning\n\n**Light versioning** (all node types): Change `version` to an array `[1, 2]` and use `displayOptions: { show: { '@version': [2] } }`.\n\n**Full versioning** (programmatic only): Extend `NodeVersionedType` with separate `v1\u002F`, `v2\u002F` directories. See the Mattermost node on GitHub for a real example.\n\n## Step 8: Test, Lint, Publish\n\n```bash\nnpm run dev              # Live-reload local n8n with your node\nnpm run lint             # Check against n8n standards\nnpm run lint -- --fix    # Auto-fix what's possible\nn8n-node release         # Publish to npm (uses release-it)\n```\n\nRead `references\u002Fpublishing.md` for the full publishing and verification checklist.\n\n## Code Standards Summary\n\n- Write in TypeScript; use `import type` for type-only imports (if a symbol only appears in `: Type` annotations or `as Type` casts, use `import type`)\n- Use `httpRequestWithAuthentication` (not the deprecated `requestWithAuthentication`); use `url` not `uri` in `IHttpRequestOptions`\n- Never mutate incoming data — clone with spread or `structuredClone()`\n- No external runtime dependencies for verified nodes — use built-in helpers only\n- `n8n-workflow` should be a peer dependency, not bundled\n- Follow Resource → Operation pattern with `noDataExpression: true` on selectors\n- Always include `action` on every operation option\n- Use `constructExecutionMetaData` with `itemData` for proper item linking\n- Implement `continueOnFail()` in every execute loop\n- The `execute()` method returns `[returnData]` — don't forget the outer array wrapper\n- Create `GenericFunctions.ts` for shared API request helpers (include `IHookFunctions` and `IWebhookFunctions` in the `this` type for trigger node compatibility)\n- Add `usableAsTool: true` to node descriptions for AI agent compatibility\n- Name list operations **\"Get Many\"** (not \"Get All\") — the linter enforces this\n- Use `returnAll` \u002F `limit` pair for list operations\n- Use `displayOptions` for progressive field disclosure\n- Optional params go in \"Additional Fields\" collections\n- Title Case for UI text; Sentence case for descriptions\u002Fhints\n- Trigger nodes: `inputs: []`, `group: ['trigger']`, \"Trigger\" suffix in `displayName` and class name\n- Reuse internal parameter `value` names across operations\n- Set `\"strict\": true` in the `n8n` config of `package.json`\n- Use `$credentials` (plural) in credential expressions — `$credential` (singular) won't resolve\n- Dynamic expressions in routing need the `=` prefix: `'=\u002Fpath\u002F{{$parameter.id}}'`\n- Declarative nodes cannot have `execute()` — use routing OR execute, not both\n- Use `typeOptions.loadOptions.routing` for declarative dynamic dropdowns — chain `rootProperty` → `setKeyValue` → `sort` postReceive transforms\n- Use dynamic property paths: `$parent.fieldName` for nested objects, `$index` for array indexing in fixedCollections\n- Use `encodeURIComponent()` \u002F `encodeURI()` for user-provided values in routing URLs\n- Place `routing` on any parameter that needs it (fields, fixedCollections), not just on operations\n- Split multi-resource nodes into `*Description.ts` files per resource, spread into properties array\n- Use `preSend` functions for custom request body transformation in declarative nodes — they receive and return `IHttpRequestOptions`\n- Use custom `postReceive` functions for response transformation beyond `rootProperty`\u002F`filter`\u002F`limit`\u002F`set`\u002F`setKeyValue`\u002F`sort`\u002F`binaryData` — they receive `(items, response)` and return `INodeExecutionData[]`\n- All postReceive transforms support optional `enabled` (boolean\u002Fexpression) and `errorMessage` properties\n- For file downloads in declarative nodes, set `returnFullResponse: true` and `encoding: 'arraybuffer'` on the request, then handle binary conversion in `postReceive`\n- For file uploads in declarative nodes, use `preSend` to build `FormData` from `this.helpers.getBinaryDataBuffer()`\n- Use `ignoreHttpStatusErrors: true` on request when you need custom error handling in postReceive\n- Use `propertyInDotNotation: false` on `routing.send` when property names contain literal dots (default is `true`, which creates nested objects)\n- For generic\u002Ftoken-based pagination, use `type: 'generic'` with `$response.body`\u002F`$request` expressions\n- For cursor-based pagination, create a reusable factory function using `IExecutePaginationFunctions` and `makeRoutingRequest()`\n- Use `_cnd` operators in displayOptions for advanced conditions: `{ _cnd: { gte: 2 } }`, `{ _cnd: { startsWith: 'https' } }`, etc.\n- Use `@version`, `@tool`, `@feature` special keys in displayOptions for version\u002Fcontext-specific fields\n- Use `type: 'resourceLocator'` for entity selection (provides list, URL, and ID modes) — requires `methods.listSearch` on the node class\n- Use `type: 'resourceMapper'` for dynamic field mapping (Create\u002FUpdate) — requires `methods.resourceMapping` on the node class\n- Use `type: 'fixedCollection'` with `multipleValues: true` for repeatable structured parameter groups (filters, sort rules)\n- Combine `displayOptions.show` and `displayOptions.hide` for excluding specific parameter values\n- Pass the linter before publishing — see `references\u002Fcommon-mistakes.md` for the full error catalog\n\n## UX Patterns (Verification Requirements)\n\nThese patterns are required for verified community nodes and recommended for all nodes:\n\n**Delete operation output:** Always return `{ deleted: true }` (not `{ success: true }`) from Delete operations. This confirms the deletion and triggers the following node.\n\n**Simplify toggle:** When an endpoint returns data with more than 10 fields, add a \"Simplify\" boolean parameter that returns a curated subset of max 10 fields. Use displayName `Simplify` and description `Whether to return a simplified version of the response instead of the raw data`. Flatten nested fields in simplified mode.\n\n**AI Tool Output parameter:** For nodes used as AI agent tools, add an \"Output\" options parameter with three modes: Simplified (same as Simplify above), Raw (all fields), and Selected Fields (user picks which fields to send to the AI agent). This prevents context window overflow.\n\n**Resource Locator:** Use `type: 'resourceLocator'` instead of a plain string input whenever a user needs to select a single item (e.g., a specific document, board, or channel). It offers ID, URL, and \"From list\" modes. Default to \"From list\" when available. See the Trello and Google Drive nodes for examples.\n\n**Sorting options for Get Many:** Enhance list operations by providing sorting options in a dedicated collection below the main \"Options\" collection.\n\n**Binary data naming:** Don't use \"binary data\" or \"binary property\" in field names. Instead use \"Input Data Field Name\" \u002F \"Output Data Field Name\".\n\n**Upsert:** When the API supports it, include \"Create or Update\" as a separate operation alongside Create and Update.\n\n## Trigger Nodes\n\nTriggers are always programmatic. Four patterns:\n\n| Type | Method | Use When | Example |\n|------|--------|----------|---------|\n| Webhook (auto) | `webhook()` + `webhookMethods` | Service supports API-based webhook registration | Stripe Trigger |\n| Webhook (manual) | `webhook()` only | User pastes webhook URL into external service | Generic Webhook |\n| Polling | `poll()` | No webhook support; check for new data on a schedule | Gmail Trigger |\n| Event\u002FStream | `trigger()` | Long-running connection (WebSocket, SSE, message queue) | AMQP Trigger |\n\n**Key differences from action nodes:**\n- Set `group: ['trigger']` and suffix the `displayName` with \"Trigger\"\n- Trigger nodes have `inputs: []` — they have NO inputs\n- Class names and filenames get the `Trigger` suffix (e.g., `MyServiceTrigger`)\n- Use `getWorkflowStaticData('node')` to persist state (webhook IDs, last-checked timestamps) between calls\n\nFor complete trigger templates with full code examples, read `references\u002Fprogrammatic-node.md` → \"Trigger Node Patterns\".\n\n## Modular Structure (Complex Nodes)\n\nFor many resources\u002Foperations, split into modules:\n\n```\nnodes\u002FMyNode\u002F\n├── MyNode.node.ts           # Main entry\n├── GenericFunctions.ts      # Shared API request helpers\n├── actions\u002F                 # One dir per resource\n│   ├── contact\u002F\n│   │   ├── create.ts\n│   │   ├── get.ts\n│   │   └── index.ts\n│   └── deal\u002F\n│       └── index.ts\n├── methods\u002F                 # loadOptions, etc.\n└── transport\u002F               # Shared HTTP helpers\n```\n\n## n8n Data Structure\n\nData flows between nodes as arrays of items. Each item has `json` (required) and optionally `binary`. The `execute()` method returns `Promise\u003CINodeExecutionData[][]>` — an array of arrays (one per output). Use `this.helpers.returnJsonArray(responseData)` to wrap raw data, and remember to return `[returnData]` (nested array).\n\n","# n8n Node Builder\n\nBuild production-ready custom nodes for n8n using the official `n8n-node` CLI tool and n8n's best practices.\n\n## When You Need More Detail\n\nThis skill uses progressive disclosure. The SKILL.md covers the full workflow and decision-making. For complete code templates, read these reference files:\n\n- `references\u002Fdeclarative-node.md` — Full declarative node template with routing, credentials, and codex file\n- `references\u002Fprogrammatic-node.md` — Full programmatic node template with execute method, error handling, item linking, and trigger patterns\n- `references\u002Fcredentials.md` — All credential\u002Fauth patterns (API key, Bearer, OAuth2, Basic, Custom, testedBy)\n- `references\u002Fpublishing.md` — Linting, testing, releasing, and verification checklist\n- `references\u002Fcommon-mistakes.md` — Error catalog with 36 numbered mistake patterns and fixes\n\nRead the appropriate reference file before writing any code.\n\n## Workflow Overview\n\nBuilding an n8n node follows this sequence:\n\n1. **Decide** on the node style (declarative vs programmatic)\n2. **Scaffold** the project with the `n8n-node` CLI\n3. **Implement** the node base file, credentials file, and codex file\n4. **Test** locally with `npm run dev`\n5. **Lint** with `npm run lint`\n6. **Publish** to npm and optionally submit for verification\n\n## Step 1: Choose Your Node Style\n\nn8n has two node-building styles. Picking the right one up front saves significant rework.\n\n### Declarative Style (preferred for REST APIs)\n\nUse declarative when the integration is a REST API wrapper. It's JSON-based, simpler, more future-proof, and faster to get approved for n8n Cloud.\n\nThe declarative style handles data flow through a `routing` key inside the operations object. There's no `execute()` method — n8n constructs HTTP requests from the JSON description automatically.\n\nDeclarative nodes support advanced patterns beyond simple routing: declarative dynamic dropdowns via `typeOptions.loadOptions.routing` with `setKeyValue`\u002F`sort` postReceive transforms, dynamic property paths using `$parent`\u002F`$index` expressions for nested body structures, routing on any parameter field (not just operations), `preSend` functions (including factory patterns) to transform request bodies before sending, custom `postReceive` functions to transform responses (including binary file handling with `binaryData` type), custom pagination functions with duplicate detection, three pagination modes (offset, generic token-based, and cursor-based via custom functions), `resourceLocator` parameters for multi-mode entity selection (list\u002FURL\u002FID), `resourceMapper` for dynamic field mapping UIs, `fixedCollection` for structured filters\u002Fsort rules, advanced `displayOptions` with `_cnd` operators (eq, not, gte, lte, startsWith, includes, regex, exists, etc.) and `@version`\u002F`@tool` special keys, `ignoreHttpStatusErrors` for custom error handling in postReceive, conditional transforms with `enabled`\u002F`errorMessage` on all postReceive types, `propertyInDotNotation` control for literal dot keys, dynamic base URLs from credentials, and a `methods` object for `listSearch`, `loadOptions`, and `resourceMapping`. See `references\u002Fdeclarative-node.md` → \"Advanced Declarative Patterns\" for complete templates and a TypeScript type reference.\n\n**Choose declarative when:**\n- The API is REST-based\n- You want a simpler, lower-risk codebase\n- Even if you need custom request\u002Fresponse transformation (use `preSend`\u002F`postReceive` functions)\n- Even if you need file upload\u002Fdownload (use `preSend` with form-data, `postReceive` with binary data)\n- Even if you need dynamic field mapping (use `resourceMapper`)\n- Even if you need custom pagination logic (use custom pagination functions)\n\n### Programmatic Style (required for advanced use cases)\n\nUse programmatic when you need full control over execution. It requires an `execute()` method that reads inputs, builds requests, and returns results manually.\n\n**You must use programmatic for:**\n- Trigger nodes (webhook, polling, or other event-driven)\n- GraphQL APIs\n- Non-REST protocols\n- Complex multi-step logic that chains multiple sequential API calls where later calls depend on earlier results\n\n### Quick Decision\n\nAsk: \"Is this a REST API with no triggers and no multi-call chaining?\" If yes → declarative (even for complex request\u002Fresponse transformation, pagination, file handling, and field mapping — use `preSend`\u002F`postReceive` functions). Otherwise → programmatic.\n\n## Step 2: Scaffold with the n8n-node CLI\n\nThe CLI sets up the correct project structure, dependencies, linter config, and build scripts automatically.\n\n### Option A: Without installing (recommended)\n\n```bash\nnpm create @n8n\u002Fnode@latest n8n-nodes-\u003CYOUR_NODE_NAME> -- --template \u003Ctemplate>\n```\n\nTemplates:\n- `declarative\u002Fgithub-issues` — Demo with multiple operations and credentials (good for learning)\n- `declarative\u002Fcustom` — Blank declarative starting point (prompts for base URL, auth type)\n- `programmatic\u002Fexample` — Programmatic with full flexibility\n\n### Option B: Install globally\n\n```bash\nnpm install --global @n8n\u002Fnode-cli\nn8n-node new n8n-nodes-\u003CYOUR_NODE_NAME> --template \u003Ctemplate>\n```\n\n### Option C: Clone the n8n-nodes-starter repo\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fn8n-io\u002Fn8n-nodes-starter.git n8n-nodes-\u003CYOUR_NODE_NAME>\ncd n8n-nodes-\u003CYOUR_NODE_NAME>\nrm -rf .git && git init\nnpm install\n```\n\nThe starter provides pre-configured TypeScript, ESLint, build scripts, and example files. After cloning, rename\u002Freplace the example node and credential files with your own and update `package.json`.\n\n### Naming Rules\n\nPackage names must follow one of these formats:\n- `n8n-nodes-\u003CNAME>` (e.g., `n8n-nodes-acme`)\n- `@\u003CORG>\u002Fn8n-nodes-\u003CNAME>` (e.g., `@myorg\u002Fn8n-nodes-acme`)\n\nAfter scaffolding, the project looks like:\n\n```\nn8n-nodes-\u003Cname>\u002F\n├── package.json          # Must contain \"n8n\" attribute listing nodes and credentials\n├── tsconfig.json\n├── .eslintrc.js          # Don't edit — contains n8n linter config\n├── nodes\u002F\n│   └── \u003CNodeName>\u002F\n│       ├── \u003CNodeName>.node.ts      # Base file — the node's core logic\n│       ├── \u003CNodeName>.node.json    # Codex file — metadata for n8n's node panel\n│       └── \u003CNodeName>.svg          # Icon — square SVG recommended\n├── credentials\u002F\n│   └── \u003CNodeName>Api.credentials.ts  # Credential file\n└── dist\u002F                 # Built output (generated by build command)\n```\n\n## Step 3: Implement the Node\n\nEvery node needs three files at minimum: the base file, the codex file, and the credentials file (unless no auth is needed).\n\n### 3A: The Node Base File (`\u003CName>.node.ts`)\n\nThis is the heart of the node. It exports a class implementing `INodeType` with a `description` object.\n\n**Critical rules:**\n- The class name must match the filename (e.g., class `Acme` → file `Acme.node.ts`)\n- Use `NodeConnectionType.Main` for inputs\u002Foutputs (imported from `n8n-workflow`). If your `n8n-workflow` version exports it as type-only, use the string `'main'` as fallback\n- The `name` field in the description must be a camelCase unique identifier\n- Use Title Case for `displayName` and all UI-facing strings\n- Always set `noDataExpression: true` on Resource and Operation selectors\n- Always include `action` on every operation option (e.g., `action: 'Create a contact'`)\n- Use `import type` for symbols only used in type annotations (rule of thumb: if a symbol only appears in `: Type` annotations, function signatures, or `as Type` casts, use `import type`; if it's used as a value like `throw new NodeApiError(...)`, use regular import)\n- Dynamic expressions in routing must start with `=` prefix: `'=\u002Fcontacts\u002F{{$parameter[\"id\"]}}'`\n- **Declarative nodes cannot have an `execute()` method** — if `requestDefaults` is present, n8n uses the routing engine and ignores `execute()`. Use one or the other\n- The `execute()` method must return `[returnData]` — an array of arrays (one per output connector). Forgetting the outer array is a common error\n\n**Standard description parameters** (same for both styles):\n\n| Parameter | Type | Purpose |\n|-----------|------|---------|\n| `displayName` | string | Name shown in the UI |\n| `name` | string | Internal camelCase identifier |\n| `icon` | string | `'file:\u003Cname>.svg'` — reference the icon file |\n| `group` | string[] | `['transform']` for action nodes, `['trigger']` for triggers |\n| `version` | number or number[] | Start at `1`; use array for light versioning |\n| `subtitle` | string | Template shown below node name, e.g. `'={{$parameter[\"operation\"]}}'` |\n| `description` | string | Short description for the node panel |\n| `defaults` | object | `{ name: 'Display Name' }` |\n| `inputs` | array | `[NodeConnectionType.Main]` |\n| `outputs` | array | `[NodeConnectionType.Main]` |\n| `usableAsTool` | boolean | `true` — enables use as an AI agent tool (recommended) |\n| `credentials` | array | `[{ name: 'credName', required: true }]` |\n| `properties` | array | Resource, operation, and field definitions |\n\n**For declarative nodes**, also add:\n- `requestDefaults: { baseURL: '...', headers: { Accept: 'application\u002Fjson' } }` — supports dynamic expressions from credentials (e.g., `'={{ !$credentials.customBaseUrl ? \"https:\u002F\u002Fapi.example.com\u002Fv1\" : $credentials.baseUrl }}'`)\n- Operations use a `routing` key to define HTTP method, URL, query strings, and body — use `encodeURIComponent()` for user values in URLs\n- `routing` can be placed on any parameter (not just operations) — fields, fixedCollections, etc.\n- Use `typeOptions.loadOptions.routing` for declarative dynamic dropdowns with `setKeyValue`\u002F`sort` postReceive transforms\n- Use dynamic property paths with `$parent`, `$index` expressions (e.g., `'=attributes.{{$parent.fieldName}}'`, `'=items[{{$index}}].value'`)\n- Operations can use `routing.send.preSend` array for custom request transformation functions\n- Operations can use `routing.output.postReceive` array for custom response transformation (including binary file handling)\n- Operations can use `routing.operations.pagination` for custom pagination functions\n- Use `type: 'resourceLocator'` for multi-mode entity selection (list\u002FURL\u002FID) with `methods.listSearch`\n- Use `type: 'resourceMapper'` for dynamic field mapping UIs with `methods.resourceMapping`\n- Use `type: 'fixedCollection'` for structured parameter groups (filters, sort rules) with `$index` for array mapping\n- Combine `displayOptions.show` and `displayOptions.hide` for fine-grained field visibility\n- Split operations\u002Ffields into separate `*Description.ts` files per resource, spread into the main node\n- Define a `methods` object on the class for `listSearch`, `loadOptions`, and `resourceMapping`\n\n**For programmatic nodes**, also add:\n- An `async execute()` method\n- Proper item looping with `this.getInputData()` and `pairedItem` linking\n\nFor complete templates, read the appropriate reference file before coding:\n- Declarative → Read `references\u002Fdeclarative-node.md`\n- Programmatic → Read `references\u002Fprogrammatic-node.md`\n\n### 3B: The Resource → Operation Pattern\n\nn8n nodes follow a consistent UI pattern: **Resource** (what entity) → **Operation** (what action).\n\nEach resource gets a dropdown, each operation gets a dropdown filtered by the selected resource using `displayOptions.show`. Operations should map to CRUD verbs where applicable: Create, Create or Update (Upsert), Delete, Get, Get Many, Update. Use the `action` field on each operation option to provide a human-readable description (e.g., `action: 'Create a contact'`). For Upsert, use displayName \"Create or Update\" with description \"Create a new record or update an existing one (upsert)\".\n\n**Important naming rule:** The linter enforces naming list operations **\"Get Many\"** (not \"Get All\"). The operation value should be `getAll` but the display name must be `Get Many`.\n\n### Return All \u002F Limit Pattern\n\nFor list (\"Get Many\") operations, always include a `returnAll` boolean toggle (default `false`, description `'Whether to return all results or only up to a given limit'`) paired with a conditional `limit` number field that only shows when `returnAll` is `false` (`displayOptions: { show: { returnAll: [false] } }`). This is the standard pattern used across all n8n built-in nodes. See both reference templates for complete examples.\n\n### 3C: displayOptions and Conditional Fields\n\nUse `displayOptions.show` to conditionally display fields based on the selected resource, operation, or other parameter values (e.g., `show: { resource: ['contact'], operation: ['create'] }`). For version-specific fields, use `'@version'`: `displayOptions: { show: { '@version': [2] } }`.\n\n### 3D: Additional Fields (Optional Parameters)\n\nGroup optional parameters under a collection named \"Additional Fields\":\n\n```typescript\n{\n  displayName: 'Additional Fields',\n  name: 'additionalFields',\n  type: 'collection',\n  placeholder: 'Add Field',\n  default: {},\n  displayOptions: {\n    show: { resource: ['contact'], operation: ['create'] },\n  },\n  options: [\n    \u002F\u002F Individual optional fields here\n  ],\n}\n```\n\n### 3E: The Codex File (`\u003CName>.node.json`)\n\nMetadata controlling how the node appears in n8n's node discovery panel:\n\n```json\n{\n  \"node\": \"n8n-nodes-\u003Cpackage>.\u003CnodeName>\",\n  \"nodeVersion\": \"1.0\",\n  \"codexVersion\": \"1.0\",\n  \"categories\": [\"Miscellaneous\"],\n  \"resources\": {\n    \"credentialDocumentation\": [{ \"url\": \"\" }],\n    \"primaryDocumentation\": [{ \"url\": \"\" }]\n  }\n}\n```\n\nThe `node` field format is `\u003Cnpm-package-name>.\u003Cnode-internal-name>` (e.g., `n8n-nodes-acme.acmeService`).\n\nCategories: Analytics, Communication, Data & Storage, Development, Finance & Accounting, Marketing & Content, Miscellaneous, Productivity, Sales, Utility.\n\n### 3F: Credentials\n\nRead `references\u002Fcredentials.md` for complete patterns. Key points:\n- File: `credentials\u002F\u003CName>Api.credentials.ts`\n- Class implements `ICredentialType`\n- `name` must match the node's `credentials[].name`\n- Use `authenticate: IAuthenticateGeneric` for header\u002Fbody\u002Fquery auth\n- Use `test: ICredentialTestRequest` to validate credentials (or `testedBy` in the node for complex validation)\n- Always use `$credentials` (plural) in expressions — `$credential` (singular) is wrong\n- The linter requires an `icon` property using `Icon` type from n8n-workflow\n\n### 3G: The Icon\n\nSVG is recommended (square aspect ratio). PNG alternative: 60×60px. Place alongside the `.node.ts` file. Reference with `icon: 'file:\u003Cname>.svg'`. For light\u002Fdark variants: `icon: { light: 'file:icon.svg', dark: 'file:icon.dark.svg' }`. Don't reference Font Awesome — download and embed.\n\n## Step 4: Error Handling (Programmatic Nodes)\n\nUse `NodeApiError` for API errors and `NodeOperationError` for validation errors (both from `n8n-workflow`). Wrap each item's processing in `try\u002Fcatch` and support `continueOnFail()` so users can choose to keep going on errors — push `{ json: { error: message }, pairedItem: { item: i } }` on failure. See `references\u002Fprogrammatic-node.md` → \"Error Handling Patterns\" for full examples including HTTP status-specific handling.\n\n## Step 5: Item Linking (pairedItem \u002F constructExecutionMetaData)\n\nEvery output item in a programmatic node must link back to its source input. There are two approaches:\n\n**Modern approach (recommended):** Use `constructExecutionMetaData`:\n```typescript\nconst executionData = this.helpers.constructExecutionMetaData(\n  this.helpers.returnJsonArray(responseData),\n  { itemData: { item: i } },\n);\nreturnData.push(...executionData);\n```\n\n**Manual approach:** Set `pairedItem` directly:\n```typescript\nreturnData.push({\n  json: responseData,\n  pairedItem: { item: i },\n});\n```\n\nWithout item linking, n8n can't trace data flow between nodes.\n\n## Step 6: HTTP Helpers\n\nUse n8n's built-in helpers — no external HTTP libraries:\n\n```typescript\n\u002F\u002F Without auth:\nconst response = await this.helpers.httpRequest(options);\n\n\u002F\u002F With auth (handles credential injection automatically):\nconst response = await this.helpers.httpRequestWithAuthentication.call(\n  this, 'credentialTypeName', options\n);\n```\n\n**Deprecation warning:** `this.helpers.requestWithAuthentication` and `IRequestOptions` are **deprecated**. Always use `httpRequestWithAuthentication` with `IHttpRequestOptions`. The new interface uses `url` (not `uri`) and defaults to JSON parsing.\n\n### GenericFunctions.ts Pattern\n\nFor programmatic nodes, create a `GenericFunctions.ts` helper to centralize HTTP logic. Include `IHookFunctions`, `IWebhookFunctions`, and `IPollFunctions` in the `this` type union for trigger node compatibility. See `references\u002Fprogrammatic-node.md` → \"GenericFunctions.ts Pattern\" for the full template with pagination variants.\n\n### Dynamic Options (loadOptionsMethod)\n\nUse `loadOptionsMethod` for dropdowns that fetch values from an API at runtime. Define a `methods.loadOptions` object in the node class, with each method returning `Array\u003C{ name: string, value: string }>`. See `references\u002Fprogrammatic-node.md` for the complete pattern.\n\n## Step 7: Node Versioning\n\n**Light versioning** (all node types): Change `version` to an array `[1, 2]` and use `displayOptions: { show: { '@version': [2] } }`.\n\n**Full versioning** (programmatic only): Extend `NodeVersionedType` with separate `v1\u002F`, `v2\u002F` directories. See the Mattermost node on GitHub for a real example.\n\n## Step 8: Test, Lint, Publish\n\n```bash\nnpm run dev              # Live-reload local n8n with your node\nnpm run lint             # Check against n8n standards\nnpm run lint -- --fix    # Auto-fix what's possible\nn8n-node release         # Publish to npm (uses release-it)\n```\n\nRead `references\u002Fpublishing.md` for the full publishing and verification checklist.\n\n## Code Standards Summary\n\n- Write in TypeScript; use `import type` for type-only imports (if a symbol only appears in `: Type` annotations or `as Type` casts, use `import type`)\n- Use `httpRequestWithAuthentication` (not the deprecated `requestWithAuthentication`); use `url` not `uri` in `IHttpRequestOptions`\n- Never mutate incoming data — clone with spread or `structuredClone()`\n- No external runtime dependencies for verified nodes — use built-in helpers only\n- `n8n-workflow` should be a peer dependency, not bundled\n- Follow Resource → Operation pattern with `noDataExpression: true` on selectors\n- Always include `action` on every operation option\n- Use `constructExecutionMetaData` with `itemData` for proper item linking\n- Implement `continueOnFail()` in every execute loop\n- The `execute()` method returns `[returnData]` — don't forget the outer array wrapper\n- Create `GenericFunctions.ts` for shared API request helpers (include `IHookFunctions` and `IWebhookFunctions` in the `this` type for trigger node compatibility)\n- Add `usableAsTool: true` to node descriptions for AI agent compatibility\n- Name list operations **\"Get Many\"** (not \"Get All\") — the linter enforces this\n- Use `returnAll` \u002F `limit` pair for list operations\n- Use `displayOptions` for progressive field disclosure\n- Optional params go in \"Additional Fields\" collections\n- Title Case for UI text; Sentence case for descriptions\u002Fhints\n- Trigger nodes: `inputs: []`, `group: ['trigger']`, \"Trigger\" suffix in `displayName` and class name\n- Reuse internal parameter `value` names across operations\n- Set `\"strict\": true` in the `n8n` config of `package.json`\n- Use `$credentials` (plural) in credential expressions — `$credential` (singular) won't resolve\n- Dynamic expressions in routing need the `=` prefix: `'=\u002Fpath\u002F{{$parameter.id}}'`\n- Declarative nodes cannot have `execute()` — use routing OR execute, not both\n- Use `typeOptions.loadOptions.routing` for declarative dynamic dropdowns — chain `rootProperty` → `setKeyValue` → `sort` postReceive transforms\n- Use dynamic property paths: `$parent.fieldName` for nested objects, `$index` for array indexing in fixedCollections\n- Use `encodeURIComponent()` \u002F `encodeURI()` for user-provided values in routing URLs\n- Place `routing` on any parameter that needs it (fields, fixedCollections), not just on operations\n- Split multi-resource nodes into `*Description.ts` files per resource, spread into properties array\n- Use `preSend` functions for custom request body transformation in declarative nodes — they receive and return `IHttpRequestOptions`\n- Use custom `postReceive` functions for response transformation beyond `rootProperty`\u002F`filter`\u002F`limit`\u002F`set`\u002F`setKeyValue`\u002F`sort`\u002F`binaryData` — they receive `(items, response)` and return `INodeExecutionData[]`\n- All postReceive transforms support optional `enabled` (boolean\u002Fexpression) and `errorMessage` properties\n- For file downloads in declarative nodes, set `returnFullResponse: true` and `encoding: 'arraybuffer'` on the request, then handle binary conversion in `postReceive`\n- For file uploads in declarative nodes, use `preSend` to build `FormData` from `this.helpers.getBinaryDataBuffer()`\n- Use `ignoreHttpStatusErrors: true` on request when you need custom error handling in postReceive\n- Use `propertyInDotNotation: false` on `routing.send` when property names contain literal dots (default is `true`, which creates nested objects)\n- For generic\u002Ftoken-based pagination, use `type: 'generic'` with `$response.body`\u002F`$request` expressions\n- For cursor-based pagination, create a reusable factory function using `IExecutePaginationFunctions` and `makeRoutingRequest()`\n- Use `_cnd` operators in displayOptions for advanced conditions: `{ _cnd: { gte: 2 } }`, `{ _cnd: { startsWith: 'https' } }`, etc.\n- Use `@version`, `@tool`, `@feature` special keys in displayOptions for version\u002Fcontext-specific fields\n- Use `type: 'resourceLocator'` for entity selection (provides list, URL, and ID modes) — requires `methods.listSearch` on the node class\n- Use `type: 'resourceMapper'` for dynamic field mapping (Create\u002FUpdate) — requires `methods.resourceMapping` on the node class\n- Use `type: 'fixedCollection'` with `multipleValues: true` for repeatable structured parameter groups (filters, sort rules)\n- Combine `displayOptions.show` and `displayOptions.hide` for excluding specific parameter values\n- Pass the linter before publishing — see `references\u002Fcommon-mistakes.md` for the full error catalog\n\n## UX Patterns (Verification Requirements)\n\nThese patterns are required for verified community nodes and recommended for all nodes:\n\n**Delete operation output:** Always return `{ deleted: true }` (not `{ success: true }`) from Delete operations. This confirms the deletion and triggers the following node.\n\n**Simplify toggle:** When an endpoint returns data with more than 10 fields, add a \"Simplify\" boolean parameter that returns a curated subset of max 10 fields. Use displayName `Simplify` and description `Whether to return a simplified version of the response instead of the raw data`. Flatten nested fields in simplified mode.\n\n**AI Tool Output parameter:** For nodes used as AI agent tools, add an \"Output\" options parameter with three modes: Simplified (same as Simplify above), Raw (all fields), and Selected Fields (user picks which fields to send to the AI agent). This prevents context window overflow.\n\n**Resource Locator:** Use `type: 'resourceLocator'` instead of a plain string input whenever a user needs to select a single item (e.g., a specific document, board, or channel). It offers ID, URL, and \"From list\" modes. Default to \"From list\" when available. See the Trello and Google Drive nodes for examples.\n\n**Sorting options for Get Many:** Enhance list operations by providing sorting options in a dedicated collection below the main \"Options\" collection.\n\n**Binary data naming:** Don't use \"binary data\" or \"binary property\" in field names. Instead use \"Input Data Field Name\" \u002F \"Output Data Field Name\".\n\n**Upsert:** When the API supports it, include \"Create or Update\" as a separate operation alongside Create and Update.\n\n## Trigger Nodes\n\nTriggers are always programmatic. Four patterns:\n\n| Type | Method | Use When | Example |\n|------|--------|----------|---------|\n| Webhook (auto) | `webhook()` + `webhookMethods` | Service supports API-based webhook registration | Stripe Trigger |\n| Webhook (manual) | `webhook()` only | User pastes webhook URL into external service | Generic Webhook |\n| Polling | `poll()` | No webhook support; check for new data on a schedule | Gmail Trigger |\n| Event\u002FStream | `trigger()` | Long-running connection (WebSocket, SSE, message queue) | AMQP Trigger |\n\n**Key differences from action nodes:**\n- Set `group: ['trigger']` and suffix the `displayName` with \"Trigger\"\n- Trigger nodes have `inputs: []` — they have NO inputs\n- Class names and filenames get the `Trigger` suffix (e.g., `MyServiceTrigger`)\n- Use `getWorkflowStaticData('node')` to persist state (webhook IDs, last-checked timestamps) between calls\n\nFor complete trigger templates with full code examples, read `references\u002Fprogrammatic-node.md` → \"Trigger Node Patterns\".\n\n## Modular Structure (Complex Nodes)\n\nFor many resources\u002Foperations, split into modules:\n\n```\nnodes\u002FMyNode\u002F\n├── MyNode.node.ts           # Main entry\n├── GenericFunctions.ts      # Shared API request helpers\n├── actions\u002F                 # One dir per resource\n│   ├── contact\u002F\n│   │   ├── create.ts\n│   │   ├── get.ts\n│   │   └── index.ts\n│   └── deal\u002F\n│       └── index.ts\n├── methods\u002F                 # loadOptions, etc.\n└── transport\u002F               # Shared HTTP helpers\n```\n\n## n8n Data Structure\n\nData flows between nodes as arrays of items. Each item has `json` (required) and optionally `binary`. The `execute()` method returns `Promise\u003CINodeExecutionData[][]>` — an array of arrays (one per output). Use `this.helpers.returnJsonArray(responseData)` to wrap raw data, and remember to return `[returnData]` (nested array).\n\n",{"name":5,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fgeckse\u002Fn8n-skills\u002Fbuilding-n8n-nodes",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill building-n8n-nodes",{"tool":69,"command":70},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill building-n8n-nodes",{"tool":72,"command":73},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill building-n8n-nodes","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fgeckse\u002Fn8n-skills\u002Fbuilding-n8n-nodes\u002FSKILL.md",true,[],[78,88,98,107,113,120],{"skillNo":79,"name":80,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":84,"publishedAt":49},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[83],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[85,86,87],"model","model hierarchy","model hierarchy skill",{"skillNo":89,"name":90,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":94,"publishedAt":49},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[93],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[95,96,97],"MCP","zoom","zoom mcp",{"skillNo":99,"name":100,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":49},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[104,105,106,96],"sdk","zoom apps sdk","apps",{"skillNo":108,"name":109,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":49},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[109],{"skillNo":114,"name":115,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":119,"publishedAt":49},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[118],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[115],{"skillNo":121,"name":122,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":125,"publishedAt":49},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[122]]