Skip to content

Commit 9ba146d

Browse files
committed
Initial code commit
1 parent 1aaa0ed commit 9ba146d

37 files changed

Lines changed: 21217 additions & 0 deletions

.agents/credentials.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

.agents/nodes-declarative.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

.agents/nodes-programmatic.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)