Skills
Skill 10 of 43
Add OAuth2 credential support to an existing n8n node — creates the credential file, updates the node, adds tests, and keeps the CLI constant in sync.
3 minutes · 720 words · 16 sections
Install
npx skills add n8n-io/n8n --skill n8n:node-add-oauthnpx skills add n8n-io/n8n/plugin marketplace add n8n-io/n8nThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
Add OAuth2 (Authorization Code / 3LO) support to an existing n8n node. Works for any third-party service that supports standard OAuth2.
Before starting, read comparable existing OAuth2 credential files and tests under
packages/nodes-base/credentials/ to understand the conventions used in this codebase
(e.g. DiscordOAuth2Api.credentials.ts, MicrosoftTeamsOAuth2Api.credentials.ts).
Extract:
NODE_NAME: the service name (e.g. GitHub, Notion). Try to infer from the argument;
if ambiguous, ask the user.CUSTOM_SCOPES: whether the credential should support user-defined scopes. If the
argument does not make this clear, ask the user before proceeding:
“Should users be able to customise the OAuth2 scopes for this credential, or should scopes be fixed?”
Read the following (adjust path conventions for the specific service):
packages/nodes-base/nodes/{NODE_NAME}/
*.node.ts (main node) and any *Trigger.node.tsGenericFunctions.ts (may be named differently)auth / version subdirectory existspackages/nodes-base/credentials/ — look for existing
{NODE_NAME}*Api.credentials.ts files to understand the naming convention and any
auth method already in use.package.json at packages/nodes-base/package.json — find where existing credentials
for this node are registered (grep for the node name).Look up the service’s OAuth2 documentation:
prompt=consent, access_type=offline)If you can’t determine the endpoints confidently, ask the user to provide them.
File: packages/nodes-base/credentials/{NODE_NAME}OAuth2Api.credentials.ts
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
const defaultScopes
Rules:
authenticate block — oAuth2Api machinery handles Bearer token injection automatically.test block — the OAuth dance validates the credential.defaultScopes at module level is the single source of truth: it populates both the
enabledScopes default and the scope expression fallback. Update it in one place.string field before the hidden fields.package.jsonFile: packages/nodes-base/package.json
Find the n8n.credentials array and insert the new entry near other credentials for this
service (alphabetical ordering within the service’s block):
"dist/credentials/{NODE_NAME}OAuth2Api.credentials.js",GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE (custom scopes only)Only do this step when CUSTOM_SCOPES = yes.
File: packages/cli/src/constants.ts
Add '{camelCase}OAuth2Api' to the GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE
array. Without this, n8n deletes the user’s custom scope on OAuth2 reconnect.
export const GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE = [
'oAuth2Api',
'googleOAuth2Api',
'microsoftOAuth2Api',
'highLevelOAuth2Api',
'mcpOAuth2Api',
'{camelCase}OAuth2Api', // ← add this
];GenericFunctions.tsAdd an else if branch before the existing else fallback:
} else if ({versionParam} === '{camelCase}OAuth2') {
domain = (await this.getCredentials('{camelCase}OAuth2Api')).{domainField} as string;
credentialType = '{camelCase}OAuth2Api';
} else {When the OAuth token is scoped for a gateway URL rather than the direct instance URL
(Atlassian’s api.atlassian.com is the canonical example), add a module-level cache and
lookup helper before the main request function:
// Module-level cache: normalised domain → site/cloud ID
export const _cloudIdCache = new Map<string, string>();
async function getSiteId(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
credentialType
Then in the main request function:
} else if ({versionParam} === '{camelCase}OAuth2') {
const rawDomain = (await this.getCredentials('{camelCase}OAuth2Api')).domain as string;
credentialType = '{camelCase}OAuth2Api';
const siteId = await getSiteId.call(this, credentialType, rawDomain);
The existing uri: \${domain}/rest${endpoint}`` construction then produces the correct
gateway URL automatically.
Add NodeOperationError to the n8n-workflow import if not already present.
*.node.ts)Credentials array — add an entry for the new credential type:
{
name: '{camelCase}OAuth2Api',
required: true,
displayOptions: { show: { {versionParam}: ['{camelCase}OAuth2'] } },
},Version/auth options — add to the {versionParam} (or equivalent) options list:
{ name: '{Display Name} (OAuth2)', value: '{camelCase}OAuth2' },Keep default unchanged — existing workflows must not be affected.
*Trigger.node.ts, if present)Same two changes. Preserve any displayName label pattern already used by other credential
entries in that trigger node’s credentials array.
File: packages/nodes-base/credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts
Use ClientOAuth2 from @n8n/client-oauth2 and nock for HTTP mocking. Follow the
structure in MicrosoftTeamsOAuth2Api.credentials.test.ts.
Required test cases:
enabledScopes default, auth URL, token URL,
authQueryParameters default (if applicable).oauthClient.code.getUri(), assert each
default scope is present.nock, call
oauthClient.code.getToken(...), assert token.data.scope contains each scope.Lifecycle hooks required:
beforeAll(() => { nock.disableNetConnect(); });
afterAll(() => { nock.restore(); });
afterEach(() => { nock.cleanAll(); });GenericFunctions.test.tsIn the credential-routing describe block:
_cloudIdCache) was added, import it and call
_cloudIdCache.clear() (or equivalent) in afterEach.getCredentials was called with the correct credential
name and requestWithAuthentication was called with the correct name and URI.requestWithAuthentication to return the accessible-resources
payload on the first call and {} on the second. Assert the first call targets the
resources endpoint and the second call uses the gateway base URL with the site ID.# From packages/nodes-base/
pnpm test credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts
pnpm test nodes/{NODE_NAME}/__test__/GenericFunctions.test.ts
pnpm typecheck
pnpm lint
# Only when constants.ts was changed:
pushd ../cli && pnpm typecheck && popdFix any type errors before finishing. Never skip pnpm typecheck.
Add OAuth2 credential support to an existing n8n node — creates the credential file, updates the node, adds tests, and keeps the CLI constant in sync. Use when the user says /node-add-oauth.
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
master, last pushed 24 September 2026.SKILL.md, not by matching a directory convention. 5 distinct layouts observed: .agents/skills/*/SKILL.md, .claude/plugins/n8n/skills/*/SKILL.md, .opencode/skills/*/SKILL.md, packages/@n8n/cli/skills/*/SKILL.md, packages/@n8n/instance-ai/skills/*/SKILL.md..claude/plugins/n8n/.claude-plugin/marketplace.json by n8n, declaring 1 plugin. It is read for editorial metadata only — never as the skill index, which is always the repository tree./n8n-io/n8n.md, and each skill at its own .md URL.