技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
formbricks【数据与研究】Formbricks is an open-source survey and experience management platform that shows in-app and website surveys to targeted users and collects their feedback. Use when someone asks to "add surveys to my app", "Formbricks", "in-app feedback", "NPS survey", "user research", "product feedback tool", or "open-source Typeform alternative". Covers the JavaScr…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Formbricks is an open-source survey and feedback platform — embed surveys directly in your app, trigger them based on user actions, and collect targeted feedback. Unlike Typeform (generic forms) or Hotjar (page-level), Formbricks targets specific users at the right moment: after checkout, on feature use, at churn risk. NPS, CSAT, feature requests, bug reports — all in-context.
Surveys are built in the Formbricks app (Cloud at app.formbricks.com, or your own instance). Your code does three things: loads the SDK with a Workspace ID, tells Formbricks who the user is, and fires actions that surveys listen for. This skill follows Formbricks 6.0 and @formbricks/js 5.1 (September 2026), where the SDK entry point is setup({ workspaceId, appUrl }); the older init({ environmentId, apiHost }) call and the @formbricks/js/app import no longer exist.
npm install @formbricks/js
Find the two values the SDK needs under Settings → Workspace → Web & Mobile SDK (the docs still call this page "Connect Your App"): the Workspace ID and the SDK Connection URL (https://app.formbricks.com on Cloud). Mobile apps use @formbricks/react-native, the FormbricksSDK Swift package, com.formbricks:android, or the formbricks Flutter package with the same two values.
Self-hosting is no longer a single container: the v6 Compose stack runs the web app, PostgreSQL, Valkey, Formbricks Hub, Cube and SpiceDB. Follow https://formbricks.com/docs/self-hosting/setup/docker to fetch docker-compose.yml with its helper files from the repository's stable branch and to generate the secrets in .env (WEBAPP_URL, NEXTAUTH_SECRET, ENCRYPTION_KEY, POSTGRES_PASSWORD and others), then:
docker compose config >/dev/null # validates .env and the compose file
docker compose up -d
docker compose ps -a # the *-migrate and bootstrap services must have exited 0
curl -fsS http://localhost:3000/health # {"status":"ok"}
// app/formbricks.tsx — client component, rendered once in app/layout.tsx inside <Suspense>
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import formbricks from "@formbricks/js";
export default function FormbricksProvider() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
formbricks.setup({
workspaceId: process.env.NEXT_PUBLIC_FORMBRICKS_WORKSPACE_ID!,
appUrl: process.env.NEXT_PUBLIC_FORMBRICKS_APP_URL!, // https://app.formbricks.com or your own host
});
}, []);
useEffect(() => {
formbricks.registerRouteChange(); // lets page-view and URL-filtered actions fire on client navigation
}, [pathname, searchParams]);
return null;
}
In a plain React or Vue app, call formbricks.setup(...) once at startup behind if (typeof window !== "undefined") and call registerRouteChange() from the router's navigation hook. Without a bundler, paste the script tag from the Web & Mobile SDK page; it loads /js/formbricks.umd.cjs from your instance and exposes window.formbricks.
// After login — setUserId must come first, attributes are dropped for anonymous visitors
await formbricks.setUserId("usr_8f2k1");
await formbricks.setEmail("dana.whitfield@lumenlabs.io");
await formbricks.setAttributes({ plan: "pro", signup_date: "2026-01-15", company: "Lumen Labs" });
await formbricks.setLanguage("de"); // ISO code or the alias from Settings → Workspace → Survey Languages
// On sign-out, so the next person on this browser is not linked to the previous user
await formbricks.logout();
Anonymous visitors still get surveys from triggers; identification is what enables attribute targeting and shows who answered. Identification and attribute-based targeting are Enterprise Edition features, so a self-hosted instance needs a license for them.
An action does nothing until it exists in Formbricks and a survey lists it as a trigger. Create it under Settings → Workspace → User Actions → Add action, switch the dialog to Code, and give it a key. No-code actions (click, page view, exit intent, 50% scroll, time on page) need no code at all, but work only on the web.
// Fire a code action — the string is the action's key
await formbricks.track("checkout_completed");
// Attach hidden-field values to the response this trigger produces
await formbricks.track("checkout_completed", { hiddenFields: { order_value: 149, plan: "pro" } });
// Or push context once; every survey shown afterwards picks it up (until reload or logout)
await formbricks.setEmbeddedData({ page_type: "checkout", plan: "pro" });
await formbricks.setEmbeddedData({ page_type: null }); // null removes the key
track() takes only { hiddenFields } as its second argument — arbitrary event properties are not stored. A hidden field must be declared on the survey first (editor → Questions tab → Hidden fields); new ids are lowercase with underscores (order_value, not orderValue).
track() returning does not mean a survey appeared — recontact options, the cooldown period and targeting may all say no. Subscribe to lifecycle events to learn what happened:
const stop = formbricks.on("formbricks_survey_shown", ({ surveyId }) => {
console.log("survey shown", surveyId);
});
formbricks.on("formbricks_response_submitted", ({ surveyId, responseId, finished }) => {
if (finished) console.log("completed", surveyId, responseId);
});
stop(); // on() returns the unsubscribe function
Other events: formbricks_survey_closed, formbricks_setup_successful, formbricks_action_tracked. The same events are pushed to window.dataLayer for Google Tag Manager.
Create a key under the organization dropdown → API Keys; add each workspace it may reach with read, write or manage permission. The value is shown once. Send it in the x-api-key header.
# Check the key (single-workspace keys only; a key with several workspaces gets 400 — use /api/v2/me)
curl -s https://app.formbricks.com/api/v1/management/me -H "x-api-key: $FORMBRICKS_API_KEY"
# Create a code action that surveys can use as a trigger
curl -s -X POST https://app.formbricks.com/api/v1/management/action-classes \
-H "x-api-key: $FORMBRICKS_API_KEY" -H "Content-Type: application/json" \
-d "{\"workspaceId\":\"$FORMBRICKS_WORKSPACE_ID\",\"name\":\"Checkout completed\",\"type\":\"code\",\"key\":\"checkout_completed\"}"
# List surveys, then read one survey's responses (limit/skip paginate)
curl -s https://app.formbricks.com/api/v1/management/surveys -H "x-api-key: $FORMBRICKS_API_KEY"
curl -s "https://app.formbricks.com/api/v1/management/responses?surveyId=$SURVEY_ID&limit=50&skip=0" \
-H "x-api-key: $FORMBRICKS_API_KEY"
POST /api/v1/management/surveys creates a survey, but the document is large: the API reference lists workspaceId, name, type (link or app) and status as required, the server also rejects a body without questions or blocks, and every text is a translation object ("headline": {"default": "How likely are you to recommend us?"}). Build surveys in the editor, or read an existing one with GET /api/v1/management/surveys/{surveyId} and modify that document. An API v2 (/api/v2/management/..., beta) covers responses, contacts and webhooks with the same header.
AI agents can skip the REST API: Formbricks exposes an MCP server at https://app.formbricks.com/api/mcp (or /api/mcp on your instance) with tools such as list_surveys, get_survey and create_survey, authorized through OAuth in the browser.
claude mcp add --transport http formbricks https://app.formbricks.com/api/mcp
curl -s -X POST https://app.formbricks.com/api/v1/webhooks \
-H "x-api-key: $FORMBRICKS_API_KEY" -H "Content-Type: application/json" \
-d "{\"workspaceId\":\"$FORMBRICKS_WORKSPACE_ID\",\"name\":\"NPS to Slack\",\"url\":\"https://hooks.lumenlabs.io/formbricks\",\"triggers\":[\"responseFinished\"],\"surveyIds\":[\"$SURVEY_ID\"]}"
Triggers are responseCreated, responseUpdated and responseFinished. Each delivery is a POST with the body { "webhookId", "event", "data": { ...response, "survey": { "title", "type", "status", "createdAt", "updatedAt" } } } and Standard Webhooks headers. The signing secret (whsec_…) is shown when the webhook is created; verify every request:
// verify-formbricks.ts — Standard Webhooks signature check
import crypto from "node:crypto";
export function verifyFormbricksWebhook(rawBody: string, headers: Record<string, string>) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const received = headers["webhook-signature"]; // "v1,<base64>"
if (!id || !timestamp || !received) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // replay window: 5 minutes
const secret = Buffer.from(process.env.FORMBRICKS_WEBHOOK_SECRET!.replace(/^whsec_/, ""), "base64");
const expected = crypto.createHmac("sha256", secret).update(`${id}.${timestamp}.${rawBody}`).digest("base64");
const given = received.replace(/^v1,/, "");
return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
User prompt: "Add an NPS survey that appears after a user completes onboarding."
onboarding_completed as its trigger, declare a hidden field plan, and publish it.FormbricksProvider from Instructions to app/layout.tsx, identify the user after login, and fire the action where onboarding ends:// app/onboarding/done/page.tsx
"use client";
import { useEffect } from "react";
import formbricks from "@formbricks/js";
export default function OnboardingDone() {
useEffect(() => {
formbricks.track("onboarding_completed", { hiddenFields: { plan: "pro" } });
}, []);
return <h1>You're all set</h1>;
}
https://app.lumenlabs.io/onboarding/done?formbricksDebug=true. The console logs the setup and the tracked action, the survey slides in, and the Web & Mobile SDK page shows "Formbricks SDK is connected" (click Re-check; it does not poll). A new survey or trigger can take up to a minute to reach the browser because the SDK's configuration request is cached.User prompt: "Let users submit feature requests from inside the app and send each one to our backend."
feedback_button_clicked, and under Visibility & Recontact set both Keep showing while conditions match and Ignore Cooldown Period — with only the first, the button goes quiet for the cooldown period after any survey was shown.<button onClick={() => formbricks.track("feedback_button_clicked")}>Give feedback</button>.responseFinished (the curl call under Webhooks) and verify each delivery with verifyFormbricksWebhook. A delivery looks like (abridged — the response also carries createdAt, meta, contact, tags and more):{
"webhookId": "cm8x2k4tq0007l508u1d3hz9e",
"event": "responseFinished",
"data": {
"id": "cm8x31w2v000dl508f0a7qk1m",
"surveyId": "cm8wz6n3a0002l508c4r9t2yb",
"finished": true,
"data": { "category": "Integrations", "request": "Export responses to BigQuery" },
"survey": { "title": "Feature requests", "type": "app", "status": "inProgress" }
}
}
The keys inside data.data are the question ids, which you can rename in the editor before the survey is published.
setEmail and setAttributes before setUserId are dropped with a console error; attribute keys are capped at 150 per workspaceappUrl in script-src and connect-src, and call formbricks.setNonce(nonce) before setup() so survey styles pass style-srcenvironmentId still works in setup() and the API but is deprecatedDANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS=1 is set — leave it off on a public server.env together: the encryption key lives in .env