Browser Sandbox Environment
⚡️ Ready to unleash?
Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.
node-add-oauth
Streamline OAuth2 credential management for n8n nodes with this AI agent skill, designed for developers to automate workflow integrations and API connections.
Overview
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).
Step 0 — Parse arguments
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?"
Step 1 — Explore the node
Read the following (adjust path conventions for the specific service):
- Node directory:
packages/nodes-base/nodes/{NODE_NAME}/- Find
*.node.ts(main node) and any*Trigger.node.ts - Find
GenericFunctions.ts(may be named differently) - Check if an
auth/versionsubdirectory exists
- Find
- Existing credentials:
packages/nodes-base/credentials/— look for existing{NODE_NAME}*Api.credentials.tsfiles to understand the naming convention and any auth method already in use. package.jsonatpackages/nodes-base/package.json— find where existing credentials for this node are registered (grep for the node name).
Step 2 — Research OAuth2 endpoints
Look up the service's OAuth2 documentation:
- Authorization URL
- Access Token URL
- Required auth query parameters (e.g.
prompt=consent,access_type=offline) - Default scopes needed for the node's existing operations
- Whether the API requires a cloudId / workspace ID lookup after the token exchange (Atlassian-style gateway APIs do; most services don't)
If you can't determine the endpoints confidently, ask the user to provide them.
Step 3 — Create the credential file
File: packages/nodes-base/credentials/{NODE_NAME}OAuth2Api.credentials.ts
typescript1import type { ICredentialType, INodeProperties } from 'n8n-workflow'; 2 3const defaultScopes = [/* minimum scopes for existing node operations */]; 4 5export class {NODE_NAME}OAuth2Api implements ICredentialType { 6 name = '{camelCase}OAuth2Api'; 7 extends = ['oAuth2Api']; 8 displayName = '{Display Name} OAuth2 API'; 9 documentationUrl = '{doc-slug}'; // matches docs.n8n.io/integrations/... 10 11 properties: INodeProperties[] = [ 12 // Include service-specific fields the node needs to construct API calls 13 // (e.g. domain, workspace URL) — add BEFORE the hidden fields below. 14 15 { displayName: 'Grant Type', name: 'grantType', type: 'hidden', default: 'authorizationCode' }, 16 { displayName: 'Authorization URL', name: 'authUrl', type: 'hidden', default: '{AUTH_URL}', required: true }, 17 { displayName: 'Access Token URL', name: 'accessTokenUrl', type: 'hidden', default: '{TOKEN_URL}', required: true }, 18 // Only include authQueryParameters if the service requires extra query params: 19 { displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', type: 'hidden', default: '{QUERY_PARAMS}' }, 20 { displayName: 'Authentication', name: 'authentication', type: 'hidden', default: 'header' }, 21 22 // ── Custom scopes block (ONLY when CUSTOM_SCOPES = yes) ────────────── 23 { 24 displayName: 'Custom Scopes', 25 name: 'customScopes', 26 type: 'boolean', 27 default: false, 28 description: 'Define custom scopes', 29 }, 30 { 31 displayName: 32 'The default scopes needed for the node to work are already set. If you change these the node may not function correctly.', 33 name: 'customScopesNotice', 34 type: 'notice', 35 default: '', 36 displayOptions: { show: { customScopes: [true] } }, 37 }, 38 { 39 displayName: 'Enabled Scopes', 40 name: 'enabledScopes', 41 type: 'string', 42 displayOptions: { show: { customScopes: [true] } }, 43 default: defaultScopes.join(' '), 44 description: 'Scopes that should be enabled', 45 }, 46 // ── End custom scopes block ─────────────────────────────────────────── 47 48 { 49 displayName: 'Scope', 50 name: 'scope', 51 type: 'hidden', 52 // Custom scopes: expression toggles between user value and defaults. 53 // Fixed scopes: use the literal defaultScopes string instead. 54 default: 55 '={{$self["customScopes"] ? $self["enabledScopes"] : "' + defaultScopes.join(' ') + '"}}', 56 }, 57 ]; 58}
Rules:
- No
authenticateblock —oAuth2Apimachinery handles Bearer token injection automatically. - No
testblock — the OAuth dance validates the credential. defaultScopesat module level is the single source of truth: it populates both theenabledScopesdefault and thescopeexpression fallback. Update it in one place.- If the service needs a domain / workspace URL for API call construction, add it as a
visible
stringfield before the hidden fields.
Step 4 — Register the credential in package.json
File: 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):
json1"dist/credentials/{NODE_NAME}OAuth2Api.credentials.js",
Step 5 — Update 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.
typescript1export const GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE = [ 2 'oAuth2Api', 3 'googleOAuth2Api', 4 'microsoftOAuth2Api', 5 'highLevelOAuth2Api', 6 'mcpOAuth2Api', 7 '{camelCase}OAuth2Api', // ← add this 8];
Step 6 — Update GenericFunctions.ts
6a — Standard services (token works directly against the instance URL)
Add an else if branch before the existing else fallback:
typescript1} else if ({versionParam} === '{camelCase}OAuth2') { 2 domain = (await this.getCredentials('{camelCase}OAuth2Api')).{domainField} as string; 3 credentialType = '{camelCase}OAuth2Api'; 4} else {
6b — Gateway services requiring a workspace/cloud ID lookup
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:
typescript1// Module-level cache: normalised domain → site/cloud ID 2export const _cloudIdCache = new Map<string, string>(); 3 4async function getSiteId( 5 this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, 6 credentialType: string, 7 domain: string, 8): Promise<string> { 9 const normalizedDomain = domain.replace(/\/$/, ''); 10 if (_cloudIdCache.has(normalizedDomain)) return _cloudIdCache.get(normalizedDomain)!; 11 12 const resources = (await this.helpers.requestWithAuthentication.call(this, credentialType, { 13 uri: '{ACCESSIBLE_RESOURCES_ENDPOINT}', 14 json: true, 15 })) as Array<{ id: string; url: string }>; 16 17 const site = resources.find((r) => r.url === normalizedDomain); 18 if (!site) { 19 throw new NodeOperationError( 20 this.getNode(), 21 `No accessible site found for domain: ${domain}. Make sure the domain matches your site URL exactly.`, 22 ); 23 } 24 25 _cloudIdCache.set(normalizedDomain, site.id); 26 return site.id; 27}
Then in the main request function:
typescript1} else if ({versionParam} === '{camelCase}OAuth2') { 2 const rawDomain = (await this.getCredentials('{camelCase}OAuth2Api')).domain as string; 3 credentialType = '{camelCase}OAuth2Api'; 4 const siteId = await getSiteId.call(this, credentialType, rawDomain); 5 domain = `{GATEWAY_BASE_URL}/${siteId}`; 6} else {
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.
Step 7 — Update the node file(s)
Main node (*.node.ts)
Credentials array — add an entry for the new credential type:
typescript1{ 2 name: '{camelCase}OAuth2Api', 3 required: true, 4 displayOptions: { show: { {versionParam}: ['{camelCase}OAuth2'] } }, 5},
Version/auth options — add to the {versionParam} (or equivalent) options list:
typescript1{ name: '{Display Name} (OAuth2)', value: '{camelCase}OAuth2' },
Keep default unchanged — existing workflows must not be affected.
Trigger node (*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.
Step 8 — Write credential tests
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:
- Metadata — name, extends array,
enabledScopesdefault, auth URL, token URL,authQueryParametersdefault (if applicable). - Default scopes in authorization URI — call
oauthClient.code.getUri(), assert each default scope is present. - Token retrieval with default scopes — mock the token endpoint with
nock, calloauthClient.code.getToken(...), asserttoken.data.scopecontains each scope. - Custom scopes in authorization URI (skip when CUSTOM_SCOPES = no).
- Token retrieval with custom scopes (skip when CUSTOM_SCOPES = no).
- Minimal / different scope set (skip when CUSTOM_SCOPES = no) — assert scopes not in the set are absent from both the URI and token response.
Lifecycle hooks required:
typescript1beforeAll(() => { nock.disableNetConnect(); }); 2afterAll(() => { nock.restore(); }); 3afterEach(() => { nock.cleanAll(); });
Step 9 — Update GenericFunctions.test.ts
In the credential-routing describe block:
- If a site-ID cache (
_cloudIdCache) was added, import it and call_cloudIdCache.clear()(or equivalent) inafterEach. - Add/update the OAuth2 routing test case:
- Simple routing: assert
getCredentialswas called with the correct credential name andrequestWithAuthenticationwas called with the correct name and URI. - Gateway lookup: mock
requestWithAuthenticationto 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.
- Simple routing: assert
Step 10 — Verify
bash1# From packages/nodes-base/ 2pnpm test credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts 3pnpm test nodes/{NODE_NAME}/__test__/GenericFunctions.test.ts 4pnpm typecheck 5pnpm lint 6 7# Only when constants.ts was changed: 8pushd ../cli && pnpm typecheck && popd
Fix any type errors before finishing. Never skip pnpm typecheck.
FAQ & Installation Steps
These questions and steps mirror the structured data on this page for better search understanding.
? Frequently Asked Questions
What is node-add-oauth?
node-add-oauth is a skill that automates OAuth2 credential setup for n8n nodes, enabling seamless integration with third-party services.
How do I install node-add-oauth?
Run the command: npx killer-skills add n8n-io/n8n/node-add-oauth. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.
Which IDEs are compatible with node-add-oauth?
This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.
↓ How To Install
-
1. Open your terminal
Open the terminal or command line in your project directory.
-
2. Run the install command
Run: npx killer-skills add n8n-io/n8n/node-add-oauth. The CLI will automatically detect your IDE or AI agent and configure the skill.
-
3. Start using the skill
The skill is now active. Your AI agent can use node-add-oauth immediately in the current project.