From bb56d6857983221341b0e4eec4be2ec2f1431383 Mon Sep 17 00:00:00 2001 From: Matti Nannt <675065+mattinannt@users.noreply.github.com> Date: Fri, 6 Nov 2026 10:31:15 +0000 Subject: [PATCH 1/2] fix: forward renderer prefetch setup option --- README.md | 18 ++++++++++++++ packages/js/README.md | 18 ++++++++++++++ packages/js/src/index.test.ts | 10 ++++++++ packages/js/src/lib/load-formbricks.test.ts | 27 +++++++++++++++++++++ packages/js/src/lib/load-formbricks.ts | 5 +++- packages/js/src/types/formbricks.ts | 6 +++++ 6 files changed, 83 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 58ac424..bbedb99 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,24 @@ Replace your-workspace-id with your actual workspace ID. You can find your works > `environmentId` is still accepted for backward compatibility but is deprecated and will be removed in a future version. Prefer `workspaceId`. +### Renderer prefetching and Content Security Policy + +The SDK prefetches the survey renderer by default. If your host application's Content Security +Policy blocks these prefetch requests, disable them during setup: + +```javascript +formbricks.setup({ + workspaceId: "your-workspace-id", + appUrl: "https://app.formbricks.com", + prefetch: false, +}); +``` + +The renderer still loads when a survey is triggered. Omitting `prefetch` preserves the default; +`prefetch: true` explicitly enables prefetching. This requires a Formbricks instance serving +js-core with `prefetch` support. Older instances ignore the option, so upgrading this npm package +alone does not disable their prefetch requests. + ### Dark mode Surveys render light by default. Pass `appearance` to `setup()`, or change it at any time with diff --git a/packages/js/README.md b/packages/js/README.md index 58ac424..bbedb99 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -35,6 +35,24 @@ Replace your-workspace-id with your actual workspace ID. You can find your works > `environmentId` is still accepted for backward compatibility but is deprecated and will be removed in a future version. Prefer `workspaceId`. +### Renderer prefetching and Content Security Policy + +The SDK prefetches the survey renderer by default. If your host application's Content Security +Policy blocks these prefetch requests, disable them during setup: + +```javascript +formbricks.setup({ + workspaceId: "your-workspace-id", + appUrl: "https://app.formbricks.com", + prefetch: false, +}); +``` + +The renderer still loads when a survey is triggered. Omitting `prefetch` preserves the default; +`prefetch: true` explicitly enables prefetching. This requires a Formbricks instance serving +js-core with `prefetch` support. Older instances ignore the option, so upgrading this npm package +alone does not disable their prefetch requests. + ### Dark mode Surveys render light by default. Pass `appearance` to `setup()`, or change it at any time with diff --git a/packages/js/src/index.test.ts b/packages/js/src/index.test.ts index 02f31f8..140f1bf 100644 --- a/packages/js/src/index.test.ts +++ b/packages/js/src/index.test.ts @@ -260,6 +260,16 @@ describe("type safety", () => { const testTypeSafety = () => { void formbricks.setup({ workspaceId: "ws", appUrl: "url" }); void formbricks.setup({ environmentId: "env", appUrl: "url" }); + void formbricks.setup({ + workspaceId: "ws", + appUrl: "url", + prefetch: false, + }); + void formbricks.setup({ + environmentId: "env", + appUrl: "url", + prefetch: true, + }); void formbricks.track("event"); void formbricks.setEmail("email"); void formbricks.setAttribute("key", "value"); diff --git a/packages/js/src/lib/load-formbricks.test.ts b/packages/js/src/lib/load-formbricks.test.ts index 055ae83..75f8c4d 100644 --- a/packages/js/src/lib/load-formbricks.test.ts +++ b/packages/js/src/lib/load-formbricks.test.ts @@ -140,6 +140,33 @@ describe("load-formbricks", () => { describe("setup", () => { describe("setup functionality", () => { + test.each([ + { workspaceId: "ws123" }, + { environmentId: "env123" }, + { workspaceId: "ws123", environmentId: "env123" }, + ])("should preserve prefetch for setup with %j", async (identifier) => { + typedGlobalThis.formbricks = mockFormbricks; + + // Each call must reach the loaded core with the exact caller preference, + // including false; an omitted preference leaves the core's default intact. + for (const prefetch of [false, true, undefined]) { + const config = { + ...identifier, + appUrl: "https://app.formbricks.com/", + prefetch, + }; + + await setup(config); + + const forwarded = mockFormbricks.setup.mock.lastCall?.[0]; + expect(forwarded).toStrictEqual({ + ...identifier, + appUrl: "https://app.formbricks.com", + ...(prefetch === undefined ? {} : { prefetch }), + }); + } + }); + test("should handle setup call with valid arguments", async () => { const setupArgs = { appUrl: "https://app.formbricks.com", diff --git a/packages/js/src/lib/load-formbricks.ts b/packages/js/src/lib/load-formbricks.ts index 5fa622c..207e759 100644 --- a/packages/js/src/lib/load-formbricks.ts +++ b/packages/js/src/lib/load-formbricks.ts @@ -175,7 +175,7 @@ const loadFormbricksSDK = async (appUrl: string): Promise> => { }; const validateSetupArgs = (config: TSetupConfig): TSetupConfig | null => { - const { appUrl, environmentId, workspaceId, appearance } = config; + const { appUrl, environmentId, workspaceId, appearance, prefetch } = config; if (!appUrl) { console.error("🧱 Formbricks - Error: appUrl is required"); @@ -202,6 +202,7 @@ const validateSetupArgs = (config: TSetupConfig): TSetupConfig | null => { // Only forwarded when set, so setup() calls without it reach the SDK exactly as before. const appearanceArg = appearance === undefined ? {} : { appearance }; + const prefetchArg = prefetch === undefined ? {} : { prefetch }; if (workspaceId) { return { @@ -209,6 +210,7 @@ const validateSetupArgs = (config: TSetupConfig): TSetupConfig | null => { workspaceId, ...(environmentId ? { environmentId } : {}), ...appearanceArg, + ...prefetchArg, }; } @@ -216,6 +218,7 @@ const validateSetupArgs = (config: TSetupConfig): TSetupConfig | null => { appUrl: appUrlWithoutTrailingSlash, environmentId: environmentId as string, ...appearanceArg, + ...prefetchArg, }; }; diff --git a/packages/js/src/types/formbricks.ts b/packages/js/src/types/formbricks.ts index 146d180..cbf6209 100644 --- a/packages/js/src/types/formbricks.ts +++ b/packages/js/src/types/formbricks.ts @@ -184,4 +184,10 @@ export type TSetupConfig = ( * Formbricks 6.1 or later; older instances ignore it. */ appearance?: TAppearance; + /** + * Whether to prefetch the survey renderer. Defaults to true. Set to false when a strict CSP + * blocks prefetch requests; the renderer still loads when a survey is triggered. Requires a + * Formbricks instance with js-core prefetch support; older instances ignore this option. + */ + prefetch?: boolean; }; From 9147fead85aac5d10f85823817f73cf451882865 Mon Sep 17 00:00:00 2001 From: Matti Nannt <675065+mattinannt@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:06:22 +0000 Subject: [PATCH 2/2] test: cover prefetch types and initial script loading --- packages/js/src/index.test.ts | 6 ++++++ packages/js/src/lib/load-formbricks.test.ts | 23 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/js/src/index.test.ts b/packages/js/src/index.test.ts index 140f1bf..6b71ea3 100644 --- a/packages/js/src/index.test.ts +++ b/packages/js/src/index.test.ts @@ -270,6 +270,12 @@ describe("type safety", () => { appUrl: "url", prefetch: true, }); + void formbricks.setup({ + workspaceId: "ws", + appUrl: "url", + // @ts-expect-error -- Prefetch accepts booleans, not string values. + prefetch: "false", + }); void formbricks.track("event"); void formbricks.setEmail("email"); void formbricks.setAttribute("key", "value"); diff --git a/packages/js/src/lib/load-formbricks.test.ts b/packages/js/src/lib/load-formbricks.test.ts index 75f8c4d..8ec2be3 100644 --- a/packages/js/src/lib/load-formbricks.test.ts +++ b/packages/js/src/lib/load-formbricks.test.ts @@ -189,6 +189,29 @@ describe("load-formbricks", () => { expect(mockFormbricks.setup).toHaveBeenCalledWith(setupArgs); }); + test("should preserve prefetch false when loading the core script", async () => { + const setupArgs = { + appUrl: "https://app.formbricks.com", + workspaceId: "ws123", + prefetch: false, + }; + + const mockAppendChild = vi + .spyOn(document.head, "appendChild") + .mockImplementation(createSuccessfulScriptMock()); + + await setup(setupArgs); + + expect(mockAppendChild).toHaveBeenCalledWith( + expect.objectContaining({ + src: `${setupArgs.appUrl}/js/formbricks.umd.cjs`, + type: "text/javascript", + async: true, + }), + ); + expect(mockFormbricks.setup).toHaveBeenCalledExactlyOnceWith(setupArgs); + }); + test("should handle setup call with a trailing slash in the appUrl", async () => { const validAppUrl = "https://app.formbricks.com"; const invalidAppUrl = "https://app.formbricks.com/";