From 20ffaf36bc3a1cb10d7c4b49cea19e5602893c43 Mon Sep 17 00:00:00 2001 From: Alexander Gekov Date: Tue, 16 Jun 2026 19:38:37 +0300 Subject: [PATCH 1/3] feat: make base URLs configurable via credentials Move the hardcoded REST (api.browserbase.com) and Stagehand (api.stagehand.browserbase.com) base URLs into the Browserbase API credential as `baseUrl` and `stagehandBaseUrl` fields. The node now reads these at runtime, falling back to the previous hardcoded values when unset, so existing credentials and default behavior are unchanged. This lets operators point the node at a proxy or regional endpoint, and allows the base URL to be overridden by gateways/proxies that swap the credential URL field. Co-authored-by: Cursor --- README.md | 2 ++ credentials/BrowserbaseApi.credentials.ts | 17 +++++++++++ nodes/Browserbase/Browserbase.node.ts | 35 ++++++++++++++++------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index eb660bf..32f709c 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ The node uses one Browserbase credential: | Credential | Description | | --- | --- | | `Browserbase API Key` | Required for all resources | +| `Base URL` | Optional. Base URL for the Browserbase REST API (Search and Fetch). Defaults to `https://api.browserbase.com`. Override to point at a proxy or regional endpoint. | +| `Stagehand Base URL` | Optional. Base URL for the Stagehand API used by the Agent resource. Defaults to `https://api.stagehand.browserbase.com`. | | `Browserbase Project ID (Deprecated)` | Optional legacy header | | `Model API Key` | Optional. Only needed for Agent when using your own model provider key | diff --git a/credentials/BrowserbaseApi.credentials.ts b/credentials/BrowserbaseApi.credentials.ts index 75a3c44..c97c347 100644 --- a/credentials/BrowserbaseApi.credentials.ts +++ b/credentials/BrowserbaseApi.credentials.ts @@ -23,6 +23,23 @@ export class BrowserbaseApi implements ICredentialType { required: true, description: 'Your Browserbase API key', }, + { + displayName: 'Base URL', + name: 'baseUrl', + type: 'string', + default: 'https://api.browserbase.com', + required: false, + description: + 'Base URL for the Browserbase REST API (Search and Fetch). Overridden automatically by n8n Connect.', + }, + { + displayName: 'Stagehand Base URL', + name: 'stagehandBaseUrl', + type: 'string', + default: 'https://api.stagehand.browserbase.com', + required: false, + description: 'Base URL for the Stagehand API used by the Agent resource', + }, { displayName: 'Browserbase Project ID (Deprecated)', name: 'browserbaseProjectId', diff --git a/nodes/Browserbase/Browserbase.node.ts b/nodes/Browserbase/Browserbase.node.ts index 38dc4f6..1af0e6b 100644 --- a/nodes/Browserbase/Browserbase.node.ts +++ b/nodes/Browserbase/Browserbase.node.ts @@ -51,6 +51,12 @@ function normalizeUrl(url: string): string { return `https://${url}`; } +function resolveBaseUrl(value: unknown, fallback: string): string { + const trimmed = typeof value === 'string' ? value.trim() : ''; + const base = trimmed || fallback; + return base.replace(/\/+$/, ''); +} + function getSessionId(response: Record): string | undefined { const data = response.data as Record | undefined; return (data?.sessionId ?? response.sessionId ?? response.id) as string | undefined; @@ -818,13 +824,14 @@ export class Browserbase implements INodeType { ): Promise { try { const headers = getHeaders(credential.data!); + const baseUrl = resolveBaseUrl(credential.data?.baseUrl, API_BASE_URL); const httpRequest = this.helpers['request' as keyof typeof this.helpers] as ( opts: object, ) => Promise>; await httpRequest({ method: 'POST', - uri: `${API_BASE_URL}/v1/fetch`, + uri: `${baseUrl}/v1/fetch`, headers, body: { url: 'https://browserbase.com/' }, json: true, @@ -874,6 +881,7 @@ export class Browserbase implements INodeType { executeFunctions: IExecuteFunctions, itemIndex: number, headers: BrowserbaseHeaders, + baseUrl: string, ): Promise { const query = executeFunctions.getNodeParameter('query', itemIndex) as string; const numResults = executeFunctions.getNodeParameter('numResults', itemIndex) as number; @@ -881,7 +889,7 @@ export class Browserbase implements INodeType { const response = await this.apiCall( executeFunctions, 'POST', - API_BASE_URL, + baseUrl, '/v1/search', headers, { @@ -907,6 +915,7 @@ export class Browserbase implements INodeType { executeFunctions: IExecuteFunctions, itemIndex: number, headers: BrowserbaseHeaders, + baseUrl: string, ): Promise { const url = normalizeUrl(executeFunctions.getNodeParameter('fetchUrl', itemIndex) as string); const fetchOptions = executeFunctions.getNodeParameter('fetchOptions', itemIndex, {}) as { @@ -918,7 +927,7 @@ export class Browserbase implements INodeType { const response = await this.apiCall( executeFunctions, 'POST', - API_BASE_URL, + baseUrl, '/v1/fetch', headers, { @@ -946,6 +955,7 @@ export class Browserbase implements INodeType { executeFunctions: IExecuteFunctions, itemIndex: number, headers: BrowserbaseHeaders, + baseUrl: string, ): Promise { let url = executeFunctions.getNodeParameter('url', itemIndex) as string; url = normalizeUrl(url); @@ -1048,7 +1058,7 @@ export class Browserbase implements INodeType { const startResponse = await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, '/v1/sessions/start', headers, { @@ -1068,7 +1078,7 @@ export class Browserbase implements INodeType { await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/navigate`, headers, { @@ -1123,7 +1133,7 @@ export class Browserbase implements INodeType { const executeResponse = await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/agentExecute`, headers, { @@ -1135,7 +1145,7 @@ export class Browserbase implements INodeType { await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/end`, headers, {}, @@ -1163,7 +1173,7 @@ export class Browserbase implements INodeType { await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/end`, headers, {}, @@ -1199,12 +1209,15 @@ export class Browserbase implements INodeType { includeModelApiKey: resource === 'agent' && modelSource === 'userProvidedKey', }); + const apiBaseUrl = resolveBaseUrl(credentials.baseUrl, API_BASE_URL); + const stagehandBaseUrl = resolveBaseUrl(credentials.stagehandBaseUrl, STAGEHAND_BASE_URL); + if (resource === 'search') { - returnData.push(await node.executeSearch(this, i, headers)); + returnData.push(await node.executeSearch(this, i, headers, apiBaseUrl)); } else if (resource === 'fetch') { - returnData.push(await node.executeFetch(this, i, headers)); + returnData.push(await node.executeFetch(this, i, headers, apiBaseUrl)); } else { - returnData.push(await node.executeAgent(this, i, headers)); + returnData.push(await node.executeAgent(this, i, headers, stagehandBaseUrl)); } } catch (error) { if (this.continueOnFail()) { From 88bb0af651bde7cef6f1ca760890e803515113fd Mon Sep 17 00:00:00 2001 From: Alexander Gekov Date: Wed, 17 Jun 2026 10:35:49 +0300 Subject: [PATCH 2/3] refactor: gate credential base URLs by typeVersion, hide fields Make the baseUrl/stagehandBaseUrl credential fields hidden and gate credential-based URL resolution on node typeVersion: nodes at version 2 keep using the hardcoded constants, while version 2.1 (the new default) reads the URLs from the credential. n8n applies credential defaults at decrypt time, so existing credentials resolve to the same values. This mirrors the pattern used by the n8n team for n8n Connect support (hidden URL field + typeVersion bump) and guarantees existing workflows are unaffected. Co-authored-by: Cursor --- README.md | 2 -- credentials/BrowserbaseApi.credentials.ts | 9 ++------- nodes/Browserbase/Browserbase.node.ts | 19 +++++++++++-------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 32f709c..eb660bf 100644 --- a/README.md +++ b/README.md @@ -102,8 +102,6 @@ The node uses one Browserbase credential: | Credential | Description | | --- | --- | | `Browserbase API Key` | Required for all resources | -| `Base URL` | Optional. Base URL for the Browserbase REST API (Search and Fetch). Defaults to `https://api.browserbase.com`. Override to point at a proxy or regional endpoint. | -| `Stagehand Base URL` | Optional. Base URL for the Stagehand API used by the Agent resource. Defaults to `https://api.stagehand.browserbase.com`. | | `Browserbase Project ID (Deprecated)` | Optional legacy header | | `Model API Key` | Optional. Only needed for Agent when using your own model provider key | diff --git a/credentials/BrowserbaseApi.credentials.ts b/credentials/BrowserbaseApi.credentials.ts index c97c347..e11025e 100644 --- a/credentials/BrowserbaseApi.credentials.ts +++ b/credentials/BrowserbaseApi.credentials.ts @@ -26,19 +26,14 @@ export class BrowserbaseApi implements ICredentialType { { displayName: 'Base URL', name: 'baseUrl', - type: 'string', + type: 'hidden', default: 'https://api.browserbase.com', - required: false, - description: - 'Base URL for the Browserbase REST API (Search and Fetch). Overridden automatically by n8n Connect.', }, { displayName: 'Stagehand Base URL', name: 'stagehandBaseUrl', - type: 'string', + type: 'hidden', default: 'https://api.stagehand.browserbase.com', - required: false, - description: 'Base URL for the Stagehand API used by the Agent resource', }, { displayName: 'Browserbase Project ID (Deprecated)', diff --git a/nodes/Browserbase/Browserbase.node.ts b/nodes/Browserbase/Browserbase.node.ts index 1af0e6b..ca0c09c 100644 --- a/nodes/Browserbase/Browserbase.node.ts +++ b/nodes/Browserbase/Browserbase.node.ts @@ -51,10 +51,8 @@ function normalizeUrl(url: string): string { return `https://${url}`; } -function resolveBaseUrl(value: unknown, fallback: string): string { - const trimmed = typeof value === 'string' ? value.trim() : ''; - const base = trimmed || fallback; - return base.replace(/\/+$/, ''); +function normalizeBaseUrl(url: string): string { + return url.trim().replace(/\/+$/, ''); } function getSessionId(response: Record): string | undefined { @@ -796,7 +794,7 @@ export class Browserbase implements INodeType { name: 'browserbase', icon: 'file:../../icons/browserbase.svg', group: ['transform'], - version: 2, + version: [2, 2.1], subtitle: '={{$parameter["resource"] === "agent" ? $parameter["operation"] + ": " + $parameter["mode"] : $parameter["operation"]}}', description: 'Browser automation, web search, and page fetches with Browserbase.', @@ -824,7 +822,7 @@ export class Browserbase implements INodeType { ): Promise { try { const headers = getHeaders(credential.data!); - const baseUrl = resolveBaseUrl(credential.data?.baseUrl, API_BASE_URL); + const baseUrl = normalizeBaseUrl((credential.data?.baseUrl as string) ?? API_BASE_URL); const httpRequest = this.helpers['request' as keyof typeof this.helpers] as ( opts: object, ) => Promise>; @@ -1209,8 +1207,13 @@ export class Browserbase implements INodeType { includeModelApiKey: resource === 'agent' && modelSource === 'userProvidedKey', }); - const apiBaseUrl = resolveBaseUrl(credentials.baseUrl, API_BASE_URL); - const stagehandBaseUrl = resolveBaseUrl(credentials.stagehandBaseUrl, STAGEHAND_BASE_URL); + const useCredentialBaseUrls = this.getNode().typeVersion >= 2.1; + const apiBaseUrl = useCredentialBaseUrls + ? normalizeBaseUrl(credentials.baseUrl as string) + : API_BASE_URL; + const stagehandBaseUrl = useCredentialBaseUrls + ? normalizeBaseUrl(credentials.stagehandBaseUrl as string) + : STAGEHAND_BASE_URL; if (resource === 'search') { returnData.push(await node.executeSearch(this, i, headers, apiBaseUrl)); From 31ab7d840485d76eebf4e33aaf86c04e41890ae9 Mon Sep 17 00:00:00 2001 From: Alexander Gekov Date: Wed, 17 Jun 2026 12:36:57 +0300 Subject: [PATCH 3/3] fix: fall back to default base URLs when credential value is missing On v2.1 nodes, credentials saved before the hidden URL fields existed (or otherwise lacking them) yield an undefined value, which crashed normalizeBaseUrl. Fall back to the hardcoded constants when the credential URL is empty so resolution never throws. Co-authored-by: Cursor --- nodes/Browserbase/Browserbase.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/Browserbase/Browserbase.node.ts b/nodes/Browserbase/Browserbase.node.ts index ca0c09c..a21926f 100644 --- a/nodes/Browserbase/Browserbase.node.ts +++ b/nodes/Browserbase/Browserbase.node.ts @@ -1209,10 +1209,10 @@ export class Browserbase implements INodeType { const useCredentialBaseUrls = this.getNode().typeVersion >= 2.1; const apiBaseUrl = useCredentialBaseUrls - ? normalizeBaseUrl(credentials.baseUrl as string) + ? normalizeBaseUrl((credentials.baseUrl as string) || API_BASE_URL) : API_BASE_URL; const stagehandBaseUrl = useCredentialBaseUrls - ? normalizeBaseUrl(credentials.stagehandBaseUrl as string) + ? normalizeBaseUrl((credentials.stagehandBaseUrl as string) || STAGEHAND_BASE_URL) : STAGEHAND_BASE_URL; if (resource === 'search') {