@n8n/node-cli 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,8 @@ import { Command } from '@oclif/core';
2
2
  export default class Release extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
- static flags: {};
5
+ static flags: {
6
+ publish: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ };
6
8
  run(): Promise<void>;
7
9
  }
@@ -7,10 +7,26 @@ const package_manager_1 = require("../utils/package-manager");
7
7
  const prompts_2 = require("../utils/prompts");
8
8
  class Release extends core_1.Command {
9
9
  async run() {
10
- await this.parse(Release);
10
+ const { flags } = await this.parse(Release);
11
11
  (0, prompts_1.intro)(await (0, prompts_2.getCommandHeader)('n8n-node release'));
12
12
  const pm = (await (0, package_manager_1.detectPackageManager)()) ?? 'npm';
13
+ const isCI = Boolean(process.env.GITHUB_ACTIONS);
13
14
  try {
15
+ if (isCI) {
16
+ await (0, child_process_1.runCommand)(pm, ['run', 'lint'], { stdio: 'inherit' });
17
+ await (0, child_process_1.runCommand)(pm, ['run', 'build'], { stdio: 'inherit' });
18
+ await (0, child_process_1.runCommand)('npm', ['publish'], {
19
+ stdio: 'inherit',
20
+ env: {
21
+ RELEASE_MODE: 'true',
22
+ NPM_CONFIG_PROVENANCE: 'true',
23
+ },
24
+ });
25
+ return;
26
+ }
27
+ if (flags.publish) {
28
+ prompts_1.log.warning('Publishing directly from your machine will not include npm provenance, which is required for n8n Cloud starting May 1 2026.\nConsider switching to GitHub Actions publishing. See: https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/');
29
+ }
14
30
  await (0, child_process_1.runCommand)('release-it', [
15
31
  '-n',
16
32
  '--git.requireBranch main',
@@ -24,6 +40,7 @@ class Release extends core_1.Command {
24
40
  '--github.release',
25
41
  `--hooks.before:init="${pm} run lint && ${pm} run build"`,
26
42
  '--hooks.after:bump="npx auto-changelog -p"',
43
+ ...(flags.publish ? [] : ['--npm.publish=false']),
27
44
  ], {
28
45
  stdio: 'inherit',
29
46
  context: 'local',
@@ -31,6 +48,9 @@ class Release extends core_1.Command {
31
48
  RELEASE_MODE: 'true',
32
49
  },
33
50
  });
51
+ if (!flags.publish) {
52
+ prompts_1.log.info('The node was not published to NPM. Starting May 1 2026, n8n requires verified community nodes to be published via GitHub Actions with npm provenance. Learn more in our documentation: https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/');
53
+ }
34
54
  }
35
55
  catch (error) {
36
56
  if (error instanceof child_process_1.ChildProcessError) {
@@ -45,8 +65,34 @@ class Release extends core_1.Command {
45
65
  }
46
66
  }
47
67
  }
48
- Release.description = 'Publish your community node package to npm';
68
+ Release.description = `Release your community node package.
69
+
70
+ When running locally (default): Runs release-it to bump the version interactively, generate a changelog, commit, tag, push, and create a GitHub release. Does NOT publish to npm — use GitHub Actions for that.
71
+
72
+ When running inside a GitHub Action: Detected automatically via the GITHUB_ACTIONS environment variable. Runs lint and build, then publishes with provenance enabled (NPM_CONFIG_PROVENANCE=true).
73
+
74
+ Starting May 1 2026, n8n requires all community nodes to be published via GitHub Actions with npm provenance. Provenance lets anyone cryptographically verify that a package was built from a specific repository and commit.
75
+
76
+ To set up GitHub Actions publishing:
77
+ 1. Add a publish.yml workflow that triggers on version tags (e.g. v*.*.*).
78
+ 2. Grant the publish job: permissions: { id-token: write, contents: read }
79
+ 3. Use actions/setup-node with registry-url: 'https://registry.npmjs.org/'
80
+ 4. Run \`npm run release\` as the publish step.
81
+
82
+ For npm Trusted Publishing (no long-lived secrets):
83
+ On npmjs.com → package settings → Trusted Publishers → add your repo and workflow name.
84
+ Leave NPM_TOKEN unset; GitHub's OIDC token is used automatically.
85
+
86
+ For token-based auth (fallback):
87
+ Add NPM_TOKEN to your repository secrets and pass it as NODE_AUTH_TOKEN.
88
+
89
+ Full documentation: https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/`;
49
90
  Release.examples = ['<%= config.bin %> <%= command.id %>'];
50
- Release.flags = {};
91
+ Release.flags = {
92
+ publish: core_1.Flags.boolean({
93
+ description: 'Publish to npm from your local machine (not recommended). Packages published this way will not include npm provenance and cannot become verified community nodes.',
94
+ default: false,
95
+ }),
96
+ };
51
97
  exports.default = Release;
52
98
  //# sourceMappingURL=release.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"release.js","sourceRoot":"","sources":["../../src/commands/release.ts"],"names":[],"mappings":";;AAAA,4CAAuC;AACvC,sCAAsC;AAEtC,0DAAuE;AACvE,8DAAgE;AAChE,8CAAoD;AAEpD,MAAqB,OAAQ,SAAQ,cAAO;IAK3C,KAAK,CAAC,GAAG;QACR,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE1B,IAAA,eAAK,EAAC,MAAM,IAAA,0BAAgB,EAAC,kBAAkB,CAAC,CAAC,CAAC;QAElD,MAAM,EAAE,GAAG,CAAC,MAAM,IAAA,sCAAoB,GAAE,CAAC,IAAI,KAAK,CAAC;QAEnD,IAAI,CAAC;YACJ,MAAM,IAAA,0BAAU,EACf,YAAY,EACZ;gBACC,IAAI;gBACJ,0BAA0B;gBAC1B,8BAA8B;gBAC9B,uBAAuB;gBACvB,sBAAsB;gBACtB,cAAc;gBACd,WAAW;gBACX,YAAY;gBACZ,kGAAkG;gBAClG,kBAAkB;gBAClB,wBAAwB,EAAE,gBAAgB,EAAE,aAAa;gBACzD,4CAA4C;aAC5C,EACD;gBACC,KAAK,EAAE,SAAS;gBAChB,OAAO,EAAE,OAAO;gBAChB,GAAG,EAAE;oBACJ,YAAY,EAAE,MAAM;iBACpB;aACD,CACD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,YAAY,iCAAiB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;gBAC/B,CAAC;YACF,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;;AA9Ce,mBAAW,GAAG,4CAA4C,CAAC;AAC3D,gBAAQ,GAAG,CAAC,qCAAqC,CAAC,CAAC;AACnD,aAAK,GAAG,EAAE,CAAC;kBAHP,OAAO"}
1
+ {"version":3,"file":"release.js","sourceRoot":"","sources":["../../src/commands/release.ts"],"names":[],"mappings":";;AAAA,4CAA4C;AAC5C,sCAA6C;AAE7C,0DAAuE;AACvE,8DAAgE;AAChE,8CAAoD;AAEpD,MAAqB,OAAQ,SAAQ,cAAO;IAkC3C,KAAK,CAAC,GAAG;QACR,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE5C,IAAA,eAAK,EAAC,MAAM,IAAA,0BAAgB,EAAC,kBAAkB,CAAC,CAAC,CAAC;QAElD,MAAM,EAAE,GAAG,CAAC,MAAM,IAAA,sCAAoB,GAAE,CAAC,IAAI,KAAK,CAAC;QACnD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAEjD,IAAI,CAAC;YACJ,IAAI,IAAI,EAAE,CAAC;gBACV,MAAM,IAAA,0BAAU,EAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC5D,MAAM,IAAA,0BAAU,EAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC7D,MAAM,IAAA,0BAAU,EAAC,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE;oBACpC,KAAK,EAAE,SAAS;oBAChB,GAAG,EAAE;wBACJ,YAAY,EAAE,MAAM;wBACpB,qBAAqB,EAAE,MAAM;qBAC7B;iBACD,CAAC,CAAC;gBACH,OAAO;YACR,CAAC;YAED,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnB,aAAG,CAAC,OAAO,CACV,mQAAmQ,CACnQ,CAAC;YACH,CAAC;YAED,MAAM,IAAA,0BAAU,EACf,YAAY,EACZ;gBACC,IAAI;gBACJ,0BAA0B;gBAC1B,8BAA8B;gBAC9B,uBAAuB;gBACvB,sBAAsB;gBACtB,cAAc;gBACd,WAAW;gBACX,YAAY;gBACZ,kGAAkG;gBAClG,kBAAkB;gBAClB,wBAAwB,EAAE,gBAAgB,EAAE,aAAa;gBACzD,4CAA4C;gBAC5C,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;aACjD,EACD;gBACC,KAAK,EAAE,SAAS;gBAChB,OAAO,EAAE,OAAO;gBAChB,GAAG,EAAE;oBACJ,YAAY,EAAE,MAAM;iBACpB;aACD,CACD,CAAC;YAEF,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACpB,aAAG,CAAC,IAAI,CACP,uQAAuQ,CACvQ,CAAC;YACH,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,YAAY,iCAAiB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;gBAC/B,CAAC;YACF,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;;AAtGe,mBAAW,GAAG;;;;;;;;;;;;;;;;;;;;;mGAqBoE,CAAC;AAEnF,gBAAQ,GAAG,CAAC,qCAAqC,CAAC,CAAC;AAEnD,aAAK,GAAG;IACvB,OAAO,EAAE,YAAK,CAAC,OAAO,CAAC;QACtB,WAAW,EACV,mKAAmK;QACpK,OAAO,EAAE,KAAK;KACd,CAAC;CACF,CAAC;kBAhCkB,OAAO"}
@@ -28,7 +28,7 @@ async function copyDefaultTemplateFilesToDestination(data) {
28
28
  }
29
29
  async function templateStaticFiles(data) {
30
30
  const files = await (0, fast_glob_1.default)('**/*.{md,json,yml}', {
31
- ignore: ['tsconfig.json', 'tsconfig.build.json'],
31
+ ignore: ['tsconfig.json', 'tsconfig.build.json', 'AGENTS.md', 'CLAUDE.md', '.agents/*.md'],
32
32
  cwd: data.destinationPath,
33
33
  absolute: true,
34
34
  dot: true,
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/template/core.ts"],"names":[],"mappings":";;;;;AAgCA,wEASC;AAED,sFAMC;AAED,kDAkBC;AAED,wCAYC;AAnFD,0DAA6B;AAC7B,4DAAoC;AACpC,gEAAkC;AAClC,0DAA6B;AAE7B,oDAAiD;AA2B1C,KAAK,UAAU,8BAA8B,CACnD,QAA0B,EAC1B,IAAkB;IAElB,MAAM,IAAA,uBAAU,EAAC;QAChB,MAAM,EAAE,QAAQ,CAAC,IAAI;QACrB,WAAW,EAAE,IAAI,CAAC,eAAe;QACjC,MAAM,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC;KAChC,CAAC,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,qCAAqC,CAAC,IAAkB;IAC7E,MAAM,IAAA,uBAAU,EAAC;QAChB,MAAM,EAAE,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,0BAA0B,CAAC;QAC3D,WAAW,EAAE,IAAI,CAAC,eAAe;QACjC,MAAM,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC;KAChC,CAAC,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,mBAAmB,CAAC,IAAkB;IAC3D,MAAM,KAAK,GAAG,MAAM,IAAA,mBAAI,EAAC,oBAAoB,EAAE;QAC9C,MAAM,EAAE,CAAC,eAAe,EAAE,qBAAqB,CAAC;QAChD,GAAG,EAAE,IAAI,CAAC,eAAe;QACzB,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE,IAAI;KACT,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,oBAAU,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAEzE,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,kBAAE,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACtC,CAAC;IACF,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AAED,SAAgB,cAAc,CAC7B,QAA0B;IAE1B,OAAO;QACN,GAAG,QAAQ;QACX,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACnB,MAAM,qCAAqC,CAAC,IAAI,CAAC,CAAC;YAClD,MAAM,8BAA8B,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACrD,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/template/core.ts"],"names":[],"mappings":";;;;;AAgCA,wEASC;AAED,sFAMC;AAED,kDAkBC;AAED,wCAYC;AAnFD,0DAA6B;AAC7B,4DAAoC;AACpC,gEAAkC;AAClC,0DAA6B;AAE7B,oDAAiD;AA2B1C,KAAK,UAAU,8BAA8B,CACnD,QAA0B,EAC1B,IAAkB;IAElB,MAAM,IAAA,uBAAU,EAAC;QAChB,MAAM,EAAE,QAAQ,CAAC,IAAI;QACrB,WAAW,EAAE,IAAI,CAAC,eAAe;QACjC,MAAM,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC;KAChC,CAAC,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,qCAAqC,CAAC,IAAkB;IAC7E,MAAM,IAAA,uBAAU,EAAC;QAChB,MAAM,EAAE,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,0BAA0B,CAAC;QAC3D,WAAW,EAAE,IAAI,CAAC,eAAe;QACjC,MAAM,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC;KAChC,CAAC,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,mBAAmB,CAAC,IAAkB;IAC3D,MAAM,KAAK,GAAG,MAAM,IAAA,mBAAI,EAAC,oBAAoB,EAAE;QAC9C,MAAM,EAAE,CAAC,eAAe,EAAE,qBAAqB,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,CAAC;QAC1F,GAAG,EAAE,IAAI,CAAC,eAAe;QACzB,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE,IAAI;KACT,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,oBAAU,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAEzE,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,kBAAE,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACtC,CAAC;IACF,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AAED,SAAgB,cAAc,CAC7B,QAA0B;IAE1B,OAAO;QACN,GAAG,QAAQ;QACX,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACnB,MAAM,qCAAqC,CAAC,IAAI,CAAC,CAAC;YAClD,MAAM,8BAA8B,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACrD,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,115 @@
1
+ # Credentials
2
+
3
+ Credentials are used to authenticate with external services and store
4
+ sensitive values. They are encrypted at rest.
5
+
6
+ ## Credential class anatomy
7
+ Credentials classes have:
8
+ - `name` – machine name (used in nodes' `credentials` array)
9
+ - `displayName` – human-readable label in the UI
10
+ - `properties` – parameters (similar types to node properties)
11
+ Sensitive properties should set `typeOptions.password = true`
12
+
13
+ ## Example
14
+ Simplified WordPress example:
15
+ ```typescript
16
+ export class WordpressApi implements ICredentialType {
17
+ name = 'wordpressApi';
18
+ displayName = 'Wordpress API';
19
+ documentationUrl = 'wordpress';
20
+
21
+ properties: INodeProperties[] = [
22
+ {
23
+ displayName: 'Username',
24
+ name: 'username',
25
+ type: 'string',
26
+ default: '',
27
+ },
28
+ {
29
+ displayName: 'Password',
30
+ name: 'password',
31
+ type: 'string',
32
+ typeOptions: {
33
+ password: true,
34
+ },
35
+ default: '',
36
+ },
37
+ {
38
+ displayName: 'Wordpress URL',
39
+ name: 'url',
40
+ type: 'string',
41
+ default: '',
42
+ placeholder: 'https://example.com',
43
+ },
44
+ ];
45
+
46
+ authenticate: IAuthenticateGeneric = {
47
+ type: 'generic',
48
+ properties: {
49
+ auth: {
50
+ username: '={{$credentials.username}}',
51
+ password: '={{$credentials.password}}',
52
+ },
53
+ },
54
+ };
55
+
56
+ test: ICredentialTestRequest = {
57
+ request: {
58
+ baseURL: '={{$credentials?.url}}/wp-json/wp/v2',
59
+ url: '/users',
60
+ method: 'GET',
61
+ },
62
+ };
63
+ }
64
+ ```
65
+
66
+ ## Notes
67
+ - `test` describes how to check if credentials are valid to show a
68
+ message to the user in the UI
69
+ - Not strictly required, but strongly recommended.
70
+ - `authenticate` describes how to modify requests for declarative-style
71
+ nodes and the HTTP Request node.
72
+
73
+ ## Custom authenticate function
74
+ You can also use a custom authenticate function:
75
+ ```typescript
76
+ authenticate: IAuthenticate = async (credentials, requestOptions) => {
77
+ const values = (credentials.headers as { values: Array<{ name: string; value: string }> }).values;
78
+
79
+ const headers = values.reduce((acc, cur) => {
80
+ acc[cur.name] = cur.value;
81
+ return acc;
82
+ }, {} as Record<string, string>);
83
+
84
+ return {
85
+ ...requestOptions,
86
+ headers: {
87
+ ...requestOptions.headers,
88
+ ...headers,
89
+ },
90
+ };
91
+ };
92
+ ```
93
+
94
+ ## OAuth2 credentials
95
+ For services using OAuth2:
96
+ - You usually do not need to define `test` or `authenticate` explicitly.
97
+ - Instead, create credentials that extend `oAuth2Api`:
98
+ ```typescript
99
+ export class MyServiceOAuth2Api implements ICredentialType {
100
+ name = 'myServiceOAuth2Api';
101
+ displayName = 'My Service OAuth2 API';
102
+ extends = ['oAuth2Api'];
103
+
104
+ properties: INodeProperties[] = [
105
+ // Add only the extra properties your service needs,
106
+ // e.g. scopes, custom URLs, etc.
107
+ ];
108
+ }
109
+ ```
110
+ - The base `oAuth2Api` handles the generic OAuth2 flow.
111
+ - When allowing users to specify scopes in a custom OAuth2 credential,
112
+ make sure to follow n8n's internal rules (see n8n docs).
113
+ - If you want to define scopes that the credentials will request, add a
114
+ property with `name: 'scope'`, `type: 'hidden'` and `default` field
115
+ that has your desired scopes
@@ -0,0 +1,103 @@
1
+ # Declarative nodes
2
+
3
+ Preferred for most integrations that simply call external APIs.
4
+
5
+ Also read `.agents/nodes.md` for shared node anatomy and conventions.
6
+
7
+ ## When to use
8
+ - The integration is mostly simple HTTP/REST requests and responses
9
+ - You can express the behavior by mapping parameters to
10
+ URL/query/body/headers
11
+
12
+ If you need multiple dependent API calls, complex control flow, or heavy
13
+ transformations, use programmatic-style instead (see
14
+ `.agents/nodes-programmatic.md`).
15
+
16
+ ## What you define
17
+ - Node `properties` (parameters)
18
+ - `requestDefaults` (base URL, default headers)
19
+ - `routing` on parameters and operations:
20
+ - Where to send the request (URL, method)
21
+ - How to map parameters to query/body/headers
22
+ - Optional pre-send and post-receive transformations
23
+
24
+ ## requestDefaults
25
+ ```typescript
26
+ requestDefaults: {
27
+ baseURL: 'https://api.nasa.gov',
28
+ headers: {
29
+ Accept: 'application/json',
30
+ 'Content-Type': 'application/json',
31
+ },
32
+ },
33
+ ```
34
+
35
+ ## Parameter routing
36
+ Example parameter with `routing`:
37
+ ```typescript
38
+ {
39
+ displayName: 'Rover Name',
40
+ description: 'Choose which Mars Rover to get a photo from',
41
+ required: true,
42
+ name: 'roverName',
43
+ type: 'options',
44
+ options: [
45
+ { name: 'Curiosity', value: 'curiosity' },
46
+ { name: 'Opportunity', value: 'opportunity' },
47
+ { name: 'Perseverance', value: 'perseverance' },
48
+ { name: 'Spirit', value: 'spirit' },
49
+ ],
50
+ routing: {
51
+ request: {
52
+ method: 'GET',
53
+ url: '=/mars-photos/api/v1/rovers/{{$value}}/photos',
54
+ },
55
+ },
56
+ default: 'curiosity',
57
+ displayOptions: {
58
+ show: {
59
+ resource: ['marsRoverPhotos'],
60
+ },
61
+ },
62
+ }
63
+ ```
64
+
65
+ ## Post-receive transformations
66
+ You can modify response data with `routing.output.postReceive`:
67
+ ```typescript
68
+ postReceive: [
69
+ {
70
+ type: 'setKeyValue',
71
+ properties: {
72
+ name: '={{$responseItem.modelName}}',
73
+ description: '={{$responseItem.modelArn}}',
74
+ value: '={{$responseItem.modelId}}',
75
+ },
76
+ },
77
+ {
78
+ type: 'sort',
79
+ properties: {
80
+ key: 'name',
81
+ },
82
+ },
83
+ async function (
84
+ this: IExecuteSingleFunctions,
85
+ data: INodeExecutionData[],
86
+ response: IN8nHttpFullResponse,
87
+ ): Promise<INodeExecutionData[]> {
88
+ // Custom transformation if needed
89
+ return data;
90
+ },
91
+ ]
92
+ ```
93
+
94
+ ## Pre-send transformations
95
+ You can also use `routing.send.preSend` to change the request before
96
+ sending (e.g. add custom headers or body transformations).
97
+
98
+ ## Guidelines
99
+ - Prefer **declarative transformations** like `setKeyValue`, `sort`, etc
100
+ (there are other transformations that are not in the example).
101
+ - Only add custom functions when necessary.
102
+ - Declarative-style nodes only support **light versioning** (not full
103
+ versioning). See `.agents/versioning.md` for details.
@@ -0,0 +1,76 @@
1
+ # Programmatic nodes
2
+
3
+ Programmatic-style nodes implement an `execute` method and have full
4
+ control over HTTP calls, loops, transformations, etc.
5
+
6
+ Also read `.agents/nodes.md` for shared node anatomy and conventions.
7
+
8
+ ## When to use
9
+ - You need multiple dependent API calls per node execution.
10
+ - You need complex transformations or branching logic.
11
+ - The API doesn't map cleanly into simple "one request per item"
12
+ patterns.
13
+
14
+ If the integration is mostly simple HTTP/REST requests, prefer
15
+ declarative-style instead (see `.agents/nodes-declarative.md`).
16
+
17
+ If you choose programmatic-style, briefly explain **why**
18
+ declarative-style won't work for this particular node.
19
+
20
+ ## Canonical execute pattern
21
+ ```typescript
22
+ async execute(
23
+ this: IExecuteFunctions,
24
+ ): Promise<INodeExecutionData[][]> {
25
+ const items = this.getInputData();
26
+ const returnData: INodeExecutionData[] = [];
27
+
28
+ for (let i = 0; i < items.length; i++) {
29
+ try {
30
+ const resource = this.getNodeParameter('resource', i) as string;
31
+ const operation = this.getNodeParameter('operation', i) as string;
32
+
33
+ // Implement logic based on resource + operation
34
+ // Use this.helpers.httpRequest / httpRequestWithAuthentication, etc.
35
+ const responseData = {};
36
+
37
+ returnData.push({
38
+ json: responseData,
39
+ pairedItem: { item: i },
40
+ });
41
+ } catch (error) {
42
+ if (this.continueOnFail()) {
43
+ returnData.push({
44
+ json: {
45
+ error: (error as Error).message,
46
+ },
47
+ pairedItem: { item: i },
48
+ });
49
+ continue;
50
+ }
51
+
52
+ // Implement a check to see what error we have
53
+ const isApiError = true;
54
+ // Use NodeApiError for API-related errors
55
+ if (isApiError) {
56
+ throw new NodeApiError(this.getNode(), error as Error, { itemIndex: i });
57
+ }
58
+
59
+ // Use NodeOperationError for configuration/validation errors
60
+ throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
61
+ }
62
+ }
63
+
64
+ return [returnData];
65
+ }
66
+ ```
67
+
68
+ ## Guidelines
69
+ - Always get input items via `this.getInputData()`
70
+ - Pass the correct item index as the second argument to
71
+ `getNodeParameter`
72
+ - Handle errors using `NodeApiError` (for API failures) and
73
+ `NodeOperationError` (for operational/validation errors)
74
+ - Support `continueOnFail()` to allow workflows to proceed when possible
75
+ - Programmatic-style nodes support both **light and full versioning**.
76
+ See `.agents/versioning.md` for details.
@@ -0,0 +1,178 @@
1
+ # Building nodes
2
+
3
+ ## Overview
4
+ Community nodes depend on `n8n-workflow` package that has different interfaces,
5
+ classes and helper functions.
6
+
7
+ Nodes can be built using one of two styles. To identify which style an
8
+ existing node uses:
9
+ - **Declarative-style** — no `execute` method. Instead, the node has
10
+ `requestDefaults` and parameters use `routing` (with `routing.request`,
11
+ `routing.send.preSend`, `routing.output.postReceive`, etc.) to
12
+ describe HTTP calls. See `.agents/nodes-declarative.md`
13
+ - **Programmatic-style** — has an `async execute(this: IExecuteFunctions)`
14
+ method that manually calls APIs (via `this.helpers.httpRequest` /
15
+ `httpRequestWithAuthentication`), loops over items, and builds the
16
+ return array. See `.agents/nodes-programmatic.md`
17
+
18
+ ## Node description
19
+ Nodes have `description` which defines:
20
+ - `displayName`, `name`
21
+ - `icon`, `group`, `version`
22
+ - `inputs`, `outputs`
23
+ - `properties`
24
+ - Optional `subtitle`, `usableAsTool`, etc
25
+ and an optional `execute` function for programmatic-style nodes.
26
+
27
+ Example node description (simplified, using WordPress **only as an
28
+ example**):
29
+ ```typescript
30
+ description: INodeTypeDescription = {
31
+ displayName: 'Wordpress',
32
+ name: 'wordpress',
33
+ icon: 'file:wordpress.svg',
34
+ group: ['output'],
35
+ version: 1,
36
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
37
+ description: 'Consume Wordpress API',
38
+ defaults: {
39
+ name: 'Wordpress',
40
+ },
41
+ usableAsTool: true,
42
+ inputs: [NodeConnectionTypes.Main],
43
+ outputs: [NodeConnectionTypes.Main],
44
+ credentials: [
45
+ {
46
+ name: 'wordpressApi',
47
+ required: true,
48
+ },
49
+ ],
50
+ properties: [
51
+ {
52
+ displayName: 'Resource',
53
+ name: 'resource',
54
+ type: 'options',
55
+ noDataExpression: true,
56
+ options: [
57
+ { name: 'Post', value: 'post' },
58
+ { name: 'Page', value: 'page' },
59
+ { name: 'User', value: 'user' },
60
+ ],
61
+ default: 'post',
62
+ },
63
+ // other properties for specific resources and operations
64
+ ],
65
+ };
66
+ ```
67
+
68
+ ## Description fields
69
+ - `inputs` and `outputs` specify which inputs and outputs a node has.
70
+ - **Most nodes will need only 1 main input and 1 main output, unless
71
+ there is specific reason to have something else** (e.g. a node like
72
+ `If` that has a `true` and `false` outputs).
73
+ - `usableAsTool`
74
+ - Set to `true` to allow n8n to use this node as a tool for the AI
75
+ agent.
76
+ - Set to `false` or omit this if node works heavily with **binary
77
+ data** which tools don't support
78
+ - `properties` define the UI parameters
79
+ - Use the convention: first a **"Resource"** parameter and for each resource an **"Operation"** parameter.
80
+ - You can choose to not follow this convention **ONLY if it's not
81
+ applicable to the node you're developing** (i.e. data transformation
82
+ nodes, etc.)
83
+
84
+ ## Resource and operation pattern
85
+ Example "operations":
86
+ ```typescript
87
+ export const postOperations: INodeProperties[] = [
88
+ {
89
+ displayName: 'Operation',
90
+ name: 'operation',
91
+ type: 'options',
92
+ noDataExpression: true,
93
+ displayOptions: {
94
+ show: {
95
+ resource: ['post'],
96
+ },
97
+ },
98
+ options: [
99
+ { name: 'Create', value: 'create', description: 'Create a post', action: 'Create a post', },
100
+ { name: 'Get', value: 'get', description: 'Get a post', action: 'Get a post', },
101
+ { name: 'Get Many', value: 'getAll', description: 'Get many posts', action: 'Get many posts', },
102
+ { name: 'Update', value: 'update', description: 'Update a post', action: 'Update a post', },
103
+ ],
104
+ default: 'create',
105
+ },
106
+ ];
107
+ ```
108
+ In your implementation:
109
+ - Replace `post` and operation names with the **real resource and operations** for the target API.
110
+
111
+ Example properties for "Create post" operation:
112
+ ```typescript
113
+ export const postFields: INodeProperties[] = [
114
+ {
115
+ displayName: 'Title',
116
+ name: 'title',
117
+ type: 'string',
118
+ required: true,
119
+ default: '',
120
+ displayOptions: {
121
+ show: {
122
+ resource: ['post'],
123
+ operation: ['create'],
124
+ },
125
+ },
126
+ description: 'The title for the post',
127
+ },
128
+ {
129
+ displayName: 'Additional Fields',
130
+ name: 'additionalFields',
131
+ type: 'collection',
132
+ placeholder: 'Add Field',
133
+ default: {},
134
+ displayOptions: {
135
+ show: {
136
+ resource: ['post'],
137
+ operation: ['create'],
138
+ },
139
+ },
140
+ options: [
141
+ {
142
+ displayName: 'Content',
143
+ name: 'content',
144
+ type: 'string',
145
+ default: '',
146
+ description: 'The content for the post',
147
+ },
148
+ // Add more fields as needed for the real API
149
+ ],
150
+ },
151
+ ];
152
+ ```
153
+ **Important**:
154
+ - In a real node, replace `post`, `Title`, `Content`, etc. with the
155
+ **real names** from the target API.
156
+ - Do not reuse these exact WordPress-specific field names unless the
157
+ node is actually for WordPress.
158
+ - Remember that these examples are **incomplete** and n8n provides a lot
159
+ of options for defining properties. Refer to their docs, when in doubt
160
+
161
+ ## General guidelines
162
+ - `icon` property can either be a string, which starts with `file:` and
163
+ contains a path to a PNG or an SVG. That path **is relative to the current
164
+ file**, you can reference icons in other folders: `file:../icon.svg`. If the
165
+ node has different icons for light and dark mode, provide an object for the
166
+ `icon` property: `{ light: 'file:icon.light.svg', dark: 'file:icon.dark.svg' }`
167
+ - For operations that are supposed to return multiple items, like "Get Many
168
+ Posts", make sure you return those items, instead of single object that has
169
+ them. I.e. if you have an object like `{ data: [{ ... }, { ... }], count: 2 }`,
170
+ then return the items inside `data` array
171
+ - If the API response is complex, you can add a "Simplify Output" toggle. When
172
+ it's `false` - return the raw response. If it's `true` - return a more
173
+ user-friendly response with only the essential data
174
+ - For "Get Many" operations add "Return All" toggle that would return all of
175
+ the items, and a "Limit" parameter to limit the number of items, if "Return
176
+ All" is `false`
177
+ - Don't forget to mark required properties as `required: true`
178
+ - Use camelCase for property names