@ixiam/n8n-nodes-civicrm 1.1.19 → 1.1.23

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.
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
1
  # n8n-nodes-civicrm
2
- <<<<<<< HEAD
3
2
 
4
3
  ![n8n Community Node](https://img.shields.io/badge/n8n-Community%20Node-blue.svg)
5
4
  ![CiviCRM API v4](https://img.shields.io/badge/CiviCRM-API%20v4-orange.svg)
@@ -8,101 +7,37 @@
8
7
  Community Node for **CiviCRM API v4** (Civi-Go compatible)
9
8
  Developed and maintained by **Ixiam Global Solutions**.
10
9
 
11
- This package provides integration between **n8n** and **CiviCRM API v4**, supporting create, update, delete operations, advanced filtering, dynamic location types, and structured sub-entities (email, phone, address).
12
- Custom fields are **not** supported.
10
+ This node enables full integration between **n8n** and **CiviCRM API v4**, supporting create/update/delete operations, smart field mapping, dynamic location types, and advanced filtering on GET operations.
13
11
 
14
12
  ---
15
- # About CiviCRM
16
-
17
- CiviCRM is an open-source Constituent Relationship Management platform designed for nonprofits, NGOs, and advocacy organizations. It supports contact management, memberships, contributions, event registration, email marketing, case management, and reporting. CiviCRM integrates with WordPress, Drupal, and Joomla.
18
13
 
19
- Download: https://civicrm.org/download
14
+ ## About CiviCRM
20
15
 
16
+ <p align="center">
17
+ <img src="https://civicrm.org/sites/civicrm.org/files/CiviCRM-logo-2019-F2-200px.png" alt="CiviCRM Logo" width="200"/>
18
+ </p>
21
19
 
22
- ## Installation
20
+ CiviCRM is an open-source Constituent Relationship Management platform designed for nonprofits, NGOs, and advocacy organizations. It supports contact management, memberships, contributions, event registration, email marketing, case management, and reporting. CiviCRM integrates with WordPress, Drupal, and Joomla.
23
21
 
24
- 1. In your n8n instance, open:
25
- =======
26
- Community Node for **CiviCRM API v4** (Civi-Go compatible)
27
- Developed and maintained by **Ixiam Global Solutions**.
22
+ Download: https://civicrm.org/download
28
23
 
29
- This node enables full integration between **n8n** and **CiviCRM API v4**, supporting create/update/delete operations, smart field mapping, dynamic location types, and advanced filtering on GET operations.
24
+ ---
30
25
 
31
26
  ## 🚀 Installation
32
27
 
33
28
  1. In your n8n instance, go to:
34
- >>>>>>> 03f29e40d265531031a602d4bdcf4724e39bd809
35
29
  **Settings → Community Nodes → Install**
36
30
  2. Enter the package name:
37
31
 
38
32
  ```
39
- <<<<<<< HEAD
40
33
  @ixiam/n8n-nodes-civicrm
41
34
  ```
42
35
 
43
- 3. Confirm installation and enable Community Nodes.
36
+ 3. Approve installation and enable Community Nodes.
44
37
  4. If running n8n via Docker, restart/rebuild for the node to load.
45
38
 
46
39
  ---
47
40
 
48
- ## Credentials
49
-
50
- Authentication uses **Bearer Token**.
51
-
52
- | Field | Description |
53
- |-------|-------------|
54
- | **Base URL** | Root URL of your CiviCRM instance (no trailing slash). Example: `https://crm.example.org` |
55
- | **API Token** | Sent as header `X-Civi-Auth: Bearer <token>` |
56
-
57
- ---
58
-
59
- ## Supported Entities
60
-
61
- This node implements API v4 operations for:
62
-
63
- - Contact
64
- - Membership
65
- - Group
66
- - Relationship
67
- - Activity
68
- - Custom API Call (raw API4 request)
69
-
70
- Each entity supports:
71
- - get
72
- - getMany
73
- - create
74
- - update
75
- - delete
76
-
77
- ---
78
-
79
- ## Features
80
-
81
- ### Email, phone and address with location types
82
- Two mapping modes:
83
-
84
- Simple:
85
- ```
86
- email = test@example.org
87
- address.city = Barcelona
88
- ```
89
-
90
- With location prefixes:
91
- ```
92
- work.email = user@company.org
93
- billing.address.postal_code = 08014
94
- home.phone.phone_type_id = 2
95
- ```
96
-
97
- ### GET MANY with JSON filters
98
- Example:
99
- ```json
100
- =======
101
- n8n-nodes-civicrm
102
- ```
103
-
104
- 3. Approve installation and enable Community Nodes.
105
-
106
41
  ## 🔐 Credentials
107
42
 
108
43
  The node uses **Bearer Token Authentication**.
@@ -114,6 +49,8 @@ The node uses **Bearer Token Authentication**.
114
49
 
115
50
  After entering credentials, click **Save** to validate the connection.
116
51
 
52
+ ---
53
+
117
54
  ## 📦 Supported Entities
118
55
 
119
56
  The node includes full API v4 support for the following entities:
@@ -127,6 +64,8 @@ The node includes full API v4 support for the following entities:
127
64
  | **Activity** | get, getMany, create, update, delete |
128
65
  | **Custom API Call** | full custom API4 request |
129
66
 
67
+ ---
68
+
130
69
  ## 🧩 Key Features
131
70
 
132
71
  ### **1. Dynamic Field Mapping**
@@ -170,8 +109,8 @@ Accepted input formats:
170
109
  Auto-normalized to `YYYY-MM-DD`.
171
110
 
172
111
  ### **5. GET MANY with JSON Filters**
173
- ```
174
- >>>>>>> 03f29e40d265531031a602d4bdcf4724e39bd809
112
+ Example:
113
+ ```json
175
114
  [
176
115
  ["first_name", "LIKE", "Ju%"],
177
116
  ["birth_date", ">", "1990-01-01"],
@@ -179,24 +118,9 @@ Auto-normalized to `YYYY-MM-DD`.
179
118
  ]
180
119
  ```
181
120
 
182
- <<<<<<< HEAD
183
- ### Birth date normalization
184
- Accepted input formats:
185
- - YYYY-MM-DD
186
- - DD/MM/YYYY
187
- - DD-MM-YYYY
188
- - YYYY/MM/DD
189
- - YYYY.MM.DD
190
-
191
- Normalized to `YYYY-MM-DD`.
192
-
193
- ### Custom API Call
121
+ ### **6. Custom API Call Mode**
194
122
  Example:
195
123
  ```json
196
- =======
197
- ### **6. Custom API Call Mode**
198
- ```
199
- >>>>>>> 03f29e40d265531031a602d4bdcf4724e39bd809
200
124
  {
201
125
  "entity": "Contact",
202
126
  "action": "get",
@@ -204,7 +128,6 @@ Example:
204
128
  }
205
129
  ```
206
130
 
207
- <<<<<<< HEAD
208
131
  ---
209
132
 
210
133
  ## Compatibility
@@ -219,14 +142,14 @@ Example:
219
142
 
220
143
  Clone the repository and run:
221
144
 
222
- ```
145
+ ```bash
223
146
  npm install
224
147
  npm run dev
225
148
  ```
226
149
 
227
150
  Build:
228
151
 
229
- ```
152
+ ```bash
230
153
  npm run build
231
154
  ```
232
155
 
@@ -238,20 +161,13 @@ Pull requests and issues are welcome in the GitHub repository.
238
161
 
239
162
  ---
240
163
 
241
- ## About Ixiam Global Solutions
242
- Website: https://www.ixiam.com
243
- Email: info@ixiam.com
244
-
245
- ---
246
-
247
- ## License
248
- =======
249
164
  ## 🧑‍💻 About Ixiam Global Solutions
250
165
 
251
166
  Website: **https://www.ixiam.com**
252
167
  Contact: **info@ixiam.com**
253
168
 
169
+ ---
170
+
254
171
  ## 📄 License
255
- >>>>>>> 03f29e40d265531031a602d4bdcf4724e39bd809
256
172
 
257
173
  MIT License
@@ -6,30 +6,6 @@ class CiviCrmApi {
6
6
  this.name = 'civiCrmApi';
7
7
  this.displayName = 'CiviCRM API';
8
8
  this.documentationUrl = 'https://docs.civicrm.org/dev/en/latest/api/';
9
- this.authenticate = {
10
- type: 'generic',
11
- properties: {
12
- headers: {
13
- 'X-Civi-Auth': '={{"Bearer " + $credentials.apiToken}}',
14
- 'Content-Type': 'application/x-www-form-urlencoded',
15
- },
16
- },
17
- };
18
- // Credential test button
19
- this.test = {
20
- request: {
21
- method: 'POST',
22
- url: '={{$credentials.baseUrl.replace(/\\/$/, "")}}/civicrm/ajax/api4/Contact/get',
23
- headers: {
24
- 'X-Civi-Auth': '={{"Bearer " + $credentials.apiToken}}',
25
- 'Content-Type': 'application/x-www-form-urlencoded',
26
- },
27
- body: {
28
- params: '={"limit":1}', // API4 compatible
29
- },
30
- json: true,
31
- },
32
- };
33
9
  this.properties = [
34
10
  {
35
11
  displayName: 'Base URL',
@@ -37,8 +13,6 @@ class CiviCrmApi {
37
13
  type: 'string',
38
14
  default: '',
39
15
  required: true,
40
- placeholder: 'https://crm.example.org',
41
- description: 'Base URL without a trailing slash.',
42
16
  },
43
17
  {
44
18
  displayName: 'API Token',
@@ -47,9 +21,26 @@ class CiviCrmApi {
47
21
  typeOptions: { password: true },
48
22
  default: '',
49
23
  required: true,
50
- description: 'Sent as "X-Civi-Auth: Bearer &lt;token&gt;"',
51
24
  },
52
25
  ];
26
+ this.authenticate = {
27
+ type: 'generic',
28
+ properties: {
29
+ headers: {
30
+ 'X-Civi-Auth': '={{ "Bearer " + $credentials.apiToken }}',
31
+ },
32
+ },
33
+ };
34
+ this.test = {
35
+ request: {
36
+ url: '={{ $credentials.baseUrl }}/civicrm/ajax/api4/Contact/get',
37
+ method: 'POST',
38
+ body: {
39
+ limit: 1,
40
+ select: ['id']
41
+ },
42
+ },
43
+ };
53
44
  }
54
45
  }
55
46
  exports.CiviCrmApi = CiviCrmApi;
@@ -29,7 +29,7 @@ async function getLocationTypeMap() {
29
29
  limit: 0,
30
30
  });
31
31
  const map = {};
32
- const values = (res?.values || []);
32
+ const values = ((res === null || res === void 0 ? void 0 : res.values) || []);
33
33
  for (const v of values) {
34
34
  const name = v.name || '';
35
35
  const label = v.label || '';
@@ -169,7 +169,7 @@ class CiviCrm {
169
169
  body: { params: JSON.stringify({ limit: 50, select: ['id', 'label'] }) },
170
170
  json: true,
171
171
  });
172
- const values = (res?.values || []);
172
+ const values = ((res === null || res === void 0 ? void 0 : res.values) || []);
173
173
  return values.map((v) => ({
174
174
  name: v.label,
175
175
  value: v.id,
@@ -182,6 +182,7 @@ class CiviCrm {
182
182
  EXECUTE
183
183
  ============================================================================ */
184
184
  async execute() {
185
+ var _a, _b, _c, _d, _e, _f, _g, _h;
185
186
  const items = this.getInputData();
186
187
  const out = [];
187
188
  const resource = this.getNodeParameter('resource', 0);
@@ -226,7 +227,7 @@ class CiviCrm {
226
227
  select: ['id', 'name', 'title', 'subject', 'display_name'],
227
228
  };
228
229
  const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, params);
229
- out.push({ json: res?.values?.[0] ?? {} });
230
+ out.push({ json: (_b = (_a = res === null || res === void 0 ? void 0 : res.values) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : {} });
230
231
  continue;
231
232
  }
232
233
  /* ======================================================
@@ -241,7 +242,7 @@ class CiviCrm {
241
242
  try {
242
243
  where = JSON.parse(whereJson);
243
244
  }
244
- catch {
245
+ catch (error) {
245
246
  throw new Error('Invalid JSON in whereJson');
246
247
  }
247
248
  }
@@ -276,19 +277,23 @@ class CiviCrm {
276
277
  if (returnAll) {
277
278
  let offset = 0;
278
279
  const page = 500;
279
- while (true) {
280
+ let hasMore = true;
281
+ while (hasMore) {
280
282
  const r = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, { ...params, limit: page, offset });
281
- const vals = r?.values ?? [];
283
+ const vals = (_c = r === null || r === void 0 ? void 0 : r.values) !== null && _c !== void 0 ? _c : [];
282
284
  for (const v of vals)
283
285
  out.push({ json: v });
284
- if (vals.length < page)
285
- break;
286
- offset += page;
286
+ if (vals.length < page) {
287
+ hasMore = false;
288
+ }
289
+ else {
290
+ offset += page;
291
+ }
287
292
  }
288
293
  }
289
294
  else {
290
295
  const r = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, { ...params, limit });
291
- const vals = r?.values ?? [];
296
+ const vals = (_d = r === null || r === void 0 ? void 0 : r.values) !== null && _d !== void 0 ? _d : [];
292
297
  for (const v of vals)
293
298
  out.push({ json: v });
294
299
  }
@@ -322,31 +327,6 @@ class CiviCrm {
322
327
  const emailData = {};
323
328
  const phoneData = {};
324
329
  const addressData = {};
325
- function normalizeBirthDate(input) {
326
- if (!input)
327
- return input;
328
- let v = input.trim();
329
- if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
330
- }
331
- else if (/^\d{2}\/\d{2}\/\d{4}$/.test(v)) {
332
- const [d, m, y] = v.split('/');
333
- v = `${y}-${m}-${d}`;
334
- }
335
- else if (/^\d{2}-\d{2}-\d{4}$/.test(v)) {
336
- const [d, m, y] = v.split('-');
337
- v = `${y}-${m}-${d}`;
338
- }
339
- else if (/^\d{4}\/\d{2}\/\d{2}$/.test(v)) {
340
- v = v.replace(/\//g, '-');
341
- }
342
- else if (/^\d{4}\.\d{2}\.\d{2}$/.test(v)) {
343
- v = v.replace(/\./g, '-');
344
- }
345
- const dt = new Date(v);
346
- if (isNaN(dt.getTime()))
347
- throw new Error(`Invalid birth_date: ${input}`);
348
- return v;
349
- }
350
330
  let locationTypeMap = {};
351
331
  if (resource === 'contact') {
352
332
  locationTypeMap = await getLocationTypeMap.call(this);
@@ -440,7 +420,7 @@ class CiviCrm {
440
420
  let contactId = id;
441
421
  if (isCreate) {
442
422
  const r = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', '/civicrm/ajax/api4/Contact/create', { values });
443
- contactId = r?.values?.[0]?.id;
423
+ contactId = (_f = (_e = r === null || r === void 0 ? void 0 : r.values) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.id;
444
424
  if (!contactId)
445
425
  throw new Error('Failed to create contact.');
446
426
  }
@@ -526,7 +506,7 @@ class CiviCrm {
526
506
  }
527
507
  : {}),
528
508
  });
529
- out.push({ json: res?.values?.[0] ?? {} });
509
+ out.push({ json: (_h = (_g = res === null || res === void 0 ? void 0 : res.values) === null || _g === void 0 ? void 0 : _g[0]) !== null && _h !== void 0 ? _h : {} });
530
510
  }
531
511
  return [out];
532
512
  }
@@ -536,7 +516,7 @@ exports.CiviCrm = CiviCrm;
536
516
  UTILS
537
517
  ============================================================================ */
538
518
  function convertValue(val) {
539
- const t = String(val ?? '').trim();
519
+ const t = String(val !== null && val !== void 0 ? val : '').trim();
540
520
  if (t === '')
541
521
  return '';
542
522
  if (t === 'true')
@@ -550,7 +530,35 @@ function convertValue(val) {
550
530
  if (typeof j === 'object')
551
531
  return j;
552
532
  }
553
- catch { }
533
+ catch (error) {
534
+ // Not valid JSON, return original value
535
+ }
554
536
  return val;
555
537
  }
538
+ function normalizeBirthDate(input) {
539
+ if (!input)
540
+ return input;
541
+ let v = input.trim();
542
+ if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
543
+ // Already in correct format
544
+ }
545
+ else if (/^\d{2}\/\d{2}\/\d{4}$/.test(v)) {
546
+ const [d, m, y] = v.split('/');
547
+ v = `${y}-${m}-${d}`;
548
+ }
549
+ else if (/^\d{2}-\d{2}-\d{4}$/.test(v)) {
550
+ const [d, m, y] = v.split('-');
551
+ v = `${y}-${m}-${d}`;
552
+ }
553
+ else if (/^\d{4}\/\d{2}\/\d{2}$/.test(v)) {
554
+ v = v.replace(/\//g, '-');
555
+ }
556
+ else if (/^\d{4}\.\d{2}\.\d{2}$/.test(v)) {
557
+ v = v.replace(/\./g, '-');
558
+ }
559
+ const dt = new Date(v);
560
+ if (isNaN(dt.getTime()))
561
+ throw new Error(`Invalid birth_date: ${input}`);
562
+ return v;
563
+ }
556
564
  exports.default = CiviCrm;
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.api4 = exports.civicrmApiRequest = void 0;
3
+ exports.civicrmApiRequest = civicrmApiRequest;
4
+ exports.api4 = api4;
4
5
  const n8n_workflow_1 = require("n8n-workflow");
5
6
  /**
6
7
  * Ejecuta una llamada a la API de CiviCRM v4 (Civi-Go)
7
8
  * Usa form-urlencoded con el campo "params" serializado
8
9
  */
9
10
  async function civicrmApiRequest(method, path, body) {
11
+ var _a;
10
12
  const { baseUrl, apiToken } = (await this.getCredentials('civiCrmApi'));
11
13
  const options = {
12
14
  method,
@@ -17,7 +19,7 @@ async function civicrmApiRequest(method, path, body) {
17
19
  },
18
20
  // cuerpo plano como espera Civi-Go
19
21
  body: {
20
- params: JSON.stringify(body.params ?? body),
22
+ params: JSON.stringify((_a = body.params) !== null && _a !== void 0 ? _a : body),
21
23
  },
22
24
  json: true,
23
25
  };
@@ -29,7 +31,6 @@ async function civicrmApiRequest(method, path, body) {
29
31
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
30
32
  }
31
33
  }
32
- exports.civicrmApiRequest = civicrmApiRequest;
33
34
  /**
34
35
  * Devuelve el cuerpo estándar para las llamadas API4 (plano)
35
36
  */
@@ -37,4 +38,3 @@ function api4(entity, action, params = {}) {
37
38
  // devolvemos los parámetros planos, no anidados
38
39
  return params;
39
40
  }
40
- exports.api4 = api4;
package/package.json CHANGED
@@ -1,126 +1,63 @@
1
1
  {
2
-
3
2
  "name": "@ixiam/n8n-nodes-civicrm",
4
-
5
- "version": "1.1.19",
6
-
3
+ "version": "1.1.23",
7
4
  "description": "Full-featured CiviCRM API v4 integration for n8n with support for Contacts, Memberships, Groups, Relationships, Activities and structured sub-entities like Email, Phone, and Address.",
8
-
9
5
  "license": "MIT",
10
-
11
6
  "keywords": [
12
-
13
7
  "n8n-community-node-package",
14
-
15
8
  "civicrm",
16
-
17
9
  "api4",
18
-
19
10
  "crm",
20
-
21
11
  "automation"
22
-
23
12
  ],
24
-
25
13
  "main": "dist/index.js",
26
-
27
14
  "types": "dist/index.d.ts",
28
-
29
15
  "scripts": {
30
-
31
16
  "dev": "n8n-node dev",
32
-
33
17
  "build": "n8n-node build && pnpm run copy:assets",
34
-
35
18
  "copy:assets": "cp src/nodes/CiviCrm/civicrm.svg dist/nodes/CiviCrm/",
36
-
37
19
  "lint": "n8n-node lint",
38
-
39
20
  "release": "n8n-node release",
40
-
41
21
  "test": "npm run build"
42
-
43
22
  },
44
-
45
23
  "repository": {
46
-
47
24
  "type": "git",
48
-
49
25
  "url": "git+https://github.com/ixiam/n8n-nodes-civicrm.git"
50
-
51
26
  },
52
-
53
27
  "homepage": "https://www.ixiam.com",
54
-
55
28
  "bugs": {
56
-
57
29
  "url": "https://github.com/ixiam/n8n-nodes-civicrm/issues"
58
-
59
30
  },
60
-
61
31
  "author": "Julian Reartes <j.reartes@ixiam.com>",
62
-
63
32
  "engines": {
64
-
65
33
  "node": ">=18"
66
-
67
34
  },
68
-
69
35
  "n8n": {
70
-
71
36
  "nodes": [
72
-
73
37
  "dist/nodes/CiviCrm/CiviCrm.node.js"
74
-
75
38
  ],
76
-
77
39
  "credentials": [
78
-
79
40
  "dist/credentials/CiviCrmApi.credentials.js"
80
-
81
41
  ]
82
-
83
42
  },
84
-
85
43
  "devDependencies": {
86
-
87
44
  "@n8n/node-cli": "0.16.0",
88
-
89
45
  "@types/node": "^20.11.30",
90
-
91
46
  "@typescript-eslint/eslint-plugin": "^7.0.0",
92
-
93
47
  "@typescript-eslint/parser": "^7.0.0",
94
-
95
48
  "eslint": "8.57.0",
96
-
97
49
  "eslint-plugin-n8n-nodes-base": "1.16.4",
98
-
99
50
  "n8n-core": "^1.0.0",
100
-
101
51
  "n8n-workflow": "^1.0.0",
102
-
103
52
  "typescript": "^5.0.0"
104
-
105
53
  },
106
-
107
54
  "pnpm": {
108
-
109
55
  "overrides": {
110
-
111
56
  "eslint": "8.57.0",
112
-
113
57
  "@typescript-eslint/parser": "6.21.0",
114
-
115
58
  "@typescript-eslint/eslint-plugin": "6.21.0",
116
-
117
59
  "change-case": "4.1.2"
118
-
119
60
  }
120
-
121
61
  },
122
-
123
62
  "packageManager": "pnpm@10.18.2+sha512.9fb969fa749b3ade6035e0f109f0b8a60b5d08a1a87fdf72e337da90dcc93336e2280ca4e44f2358a649b83c17959e9993e777c2080879f3801e6f0d999ad3dd"
124
-
125
- }
126
-
63
+ }
@@ -0,0 +1,68 @@
1
+ # Reload Node in Docker n8n
2
+
3
+ If you're loading this node from a local folder in Docker and seeing cached/old versions, try these steps:
4
+
5
+ ## Option 1: Restart Docker Container (Quick)
6
+ ```bash
7
+ # Restart your n8n container
8
+ docker restart <container-name>
9
+ # or if using docker-compose
10
+ docker-compose restart
11
+ ```
12
+
13
+ ## Option 2: Clear n8n Cache (Better)
14
+ ```bash
15
+ # Stop the container
16
+ docker stop <container-name>
17
+
18
+ # Remove the container (keeps volumes)
19
+ docker rm <container-name>
20
+
21
+ # Start fresh
22
+ docker-compose up -d
23
+ # or your docker run command
24
+ ```
25
+
26
+ ## Option 3: Verify Volume Mount
27
+ If you're using a volume mount like `-v /path/to/n8n-nodes-civicrm:/data/custom-nodes/n8n-nodes-civicrm`:
28
+
29
+ 1. Make sure the path points to THIS directory
30
+ 2. Verify the dist folder is being mounted:
31
+ ```bash
32
+ docker exec <container-name> ls -la /data/custom-nodes/n8n-nodes-civicrm/dist/credentials/
33
+ docker exec <container-name> cat /data/custom-nodes/n8n-nodes-civicrm/dist/credentials/CiviCrmApi.credentials.js | grep baseURL
34
+ ```
35
+
36
+ ## Option 4: Check n8n Custom Nodes Path
37
+ If using `N8N_CUSTOM_EXTENSION_ENV` environment variable:
38
+
39
+ ```bash
40
+ # Check what path n8n is using
41
+ docker exec <container-name> env | grep N8N_CUSTOM
42
+
43
+ # Verify the file exists in that path
44
+ docker exec <container-name> ls -la <custom-path>/dist/credentials/
45
+ ```
46
+
47
+ ## Option 5: Force Rebuild and Reload
48
+ ```bash
49
+ # In this directory, clean and rebuild
50
+ npm run build
51
+
52
+ # If using npm link
53
+ npm link
54
+
55
+ # Then restart n8n container
56
+ docker restart <container-name>
57
+ ```
58
+
59
+ ## Verify the Fix
60
+ After restarting, check if the error is gone. The built file should have:
61
+ ```javascript
62
+ baseURL: '={{ $credentials.baseUrl }}',
63
+ ```
64
+
65
+ NOT:
66
+ ```javascript
67
+ baseURL: '={{ $credentials.baseUrl.replace(/\\/$/, "") }}',
68
+ ```