If a migration fails
From Formbricks 5.3 on, one runner applies every database migration in order: theformbricks-migrate service on
Docker Compose, the formbricks-migration Job on Kubernetes (the application pods themselves when the chart sets
migration.enabled: false), or formbricks-authzed-prepare during a v5 to v6 upgrade. When a migration fails,
the runner stops and no later migration runs. A failed schema migration then blocks every later run until you
resolve it; a failed data migration is retried on the next run.
Open a SQL session
The steps below run SQL as the user that owns the Formbricks tables:- One-Click / Docker Compose
- Kubernetes
psql as the user in MIGRATE_DATABASE_URL, or in
DATABASE_URL if you do not set it. The migrations themselves need a direct or session-pooled connection: a
transaction-mode pooler such as PgBouncer can split their session settings across server connections.
1. Find the failed migration
The runner’s output names it.docker compose run … formbricks-migrate and the update script print it to your
terminal; docker compose logs --tail=200 formbricks-migrate has it only when the service ran under
docker compose up. On Kubernetes, read kubectl logs -n formbricks job/formbricks-migration (by default the
chart deletes the Job 300 seconds after it fails). A first failure prints Schema migration batch failed: with
every migration in that batch; the error names the failed one as Migration name:, or as migration_name="…"
when the real error was hidden. Every run after that stops with P3009 and names it again. If you have no log,
including with formbricks-authzed-prepare, which prints only upgrade_database_migration_failed (plus
"reason":"timeout" when the step ran out its time limit rather than failing), the database always knows:
Data migration … failed, see Data migrations
below. If the log just stops without an error, the runner was stopped (Compose stops it after 300 seconds) and
its last statement may still be running in PostgreSQL: check that first, as step 3 describes.
2. Find the real error
The runner’s error usually shows it afterDatabase error:. If it shows only current transaction is aborted, commands ignored until end of transaction block, the migration runs in a transaction of its own and the
real error was hidden. Find it in the PostgreSQL log, on the
ERROR line just before that one:
canceling statement due to lock timeout is the most
common cause: another session held a lock on the table the migration changes, for example a long-running query,
a backup, or an old Formbricks pod still running. Stop it, or retry at a quieter time. If the error itself lists
recovery steps, as some migrations’ guards do, follow those.
3. Check what the migration left behind
First make sure nothing is still running it. On Compose, the runner has exited. On Kubernetes, the Job has finished retrying (kubectl wait -n formbricks --for=condition=Failed job/formbricks-migration --timeout=10m
returns, or the Job is gone). With migration.enabled: false the application pods are the runner and retry on
every restart, so scale them to 0 (kubectl scale deploy -n formbricks formbricks --replicas=0) until step 4. A
statement keeps running in PostgreSQL after its runner is stopped, so also list what the database is still
running:
migration_lock marks a schema migration: Prisma holds that lock while it applies one. Data migrations take no
such lock, so also look for statements on the tables the migration changes. Wait for every migration statement
to end.
Then read the failed migration’s file. A released migration never changes, so the copy in your image is the one
that ran:
https://github.com/formbricks/formbricks/blob/6.0.2/packages/database/migration/<migration_name>/migration.sql.
- The runner printed an error, and the whole file is one
BEGIN; … COMMIT;block, with no statement beforeBEGIN;or afterCOMMIT;: PostgreSQL rolled the whole file back, so there is nothing to undo. A file that commits a block and then runs more statements, such as20250911192630_remove_deprecated_fields_and_tables, belongs under the next case. - Otherwise, including any migration whose runner was stopped instead of printing an error: the statements
before the failing one may have taken effect, even inside a
BEGIN; … COMMIT;block. For a stopped runner, possibly every statement in the file did: a file Prisma sends as one string runs to its end without a client. Check which did (\d "Table"for schema changes, aSELECTfor data) and undo those before you continue. If the new version is already serving, stop it first (docker compose stop formbricks, or scale the Deployment to 0) and keep it stopped until you run the migrations again. Undoing aDROPmeans recreating the object so the retry can drop it again; if a change cannot be undone, such as anUPDATEwhose old values are gone, stop and restore the pre-upgrade backup instead. You need not undo:IF NOT EXISTS,IF EXISTSandCREATE OR REPLACEstatements are safe to repeat, apart from a failedCREATE INDEX CONCURRENTLY. That leaves an invalid index whichIF NOT EXISTSthen skips: list invalid indexes withSELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;and drop only those the migration’s file creates, withSET lock_timeout = 0;and thenDROP INDEX CONCURRENTLY.- An
UPDATE,INSERTorDELETEis safe to repeat only if running it twice changes nothing.
4. Mark it rolled back and run the migrations again
Once the cause is fixed, mark the failed migration rolled back. This is whatprisma migrate resolve --rolled-back does. It should report UPDATE 1; UPDATE 0 means the name is wrong or
the migration is not recorded as failed:
- One-Click / Docker Compose
- Kubernetes
formbricks-authzed-prepare as the
v6 maintenance upgrade guide describes instead.Data migrations
Most data migrations run in a transaction of their own and leave nothing behind when they fail; the large ones commit in batches and are written to resume where they stopped. Either way the runner records the failure and retries the migration on the next run: fix the cause and run the migrations again. The exception isMigration is pending. Please resolve the issue manually., left by an interrupted run of a version before 5.3.
Make sure nothing is still running it (step 3), then mark it failed so the next run retries it:
v6
Formbricks v6 replaces the legacy authorization evaluator with SpiceDB, maintained by AuthZed. PostgreSQL remains the source of roles and grants, but SpiceDB is the sole decision engine. There is no runtime fallback, so an existing installation must prepare its complete authorization graph before serving v6 traffic. Use the supported source/target pair in the release notes and the maintenance upgrade procedure. A self-hosted v5 bridge is not required:- Enter maintenance, stop all writers, suspend their controllers, and drain in-flight work. Take recoverable backups of the databases, secrets, configuration, and source image versions.
- Merge the release-matched AuthZed deployment services or Helm values without overwriting proxy, storage, SMTP, or external-datastore customization.
- Run the target-image database migrations while writers remain stopped, never against the still-running v5 app.
- Run
formbricks-authzed-prepare --backup-confirmed --writers-stopped; it runs migrations, reconciliation, one clean full audit, and a second independentupgrade checkaudit. - For one-click or Compose, set
FORMBRICKS_AUTHZED_V6_MIGRATION_ACKNOWLEDGED=true. For Helm, setauthzed.migrationAcknowledged=truein the v6 upgrade values. - Deploy v6 and verify critical allow, deny, revocation, and cross-tenant cases.
helm rollback. Pin explicit releases and digests, not floating tags.
The Helm chart and Formbricks image are versioned independently. A chart-only patch can retain the same
appVersion; review both pins and the rendered image rather than assuming their version numbers match.
Survey visibility (opt-in)
Surveys can now be restricted to their owner and the organization’s owners and managers. New columns on theSurvey table record each survey’s owner and visibility. The migrations set every existing survey to
workspace-visible and make its creator the owner, so access is unchanged after the upgrade.
Once the marker is set, every survey a person creates starts Restricted to its author and the
organization’s owners and managers. They can make it visible to the workspace from Collaborate or
when activating it. See Survey visibility.
- SURVEY_VISIBILITY_FORCE_DISABLED:
1disables visibility mutations. Stored restrictions remain enforced. Takes effect on restart. - SURVEY_WORKSPACE_LIMIT: most surveys one workspace may hold, archived ones included (default
10000). This applies whether or not visibility is enforced: creating a survey past it answers422 workspace_survey_limit_reached. Raise it before upgrading if a workspace is near the default.
Bundled TEI log level
The Helm chart now defaultshub.embeddings.env.RUST_LOG to warn because TEI 1.9 includes its API key in
INFO-level startup arguments. If you need a different log level, set hub.embeddings.env.RUST_LOG explicitly
and use it only with a runtime that redacts credentials from its logs.
Database transaction limits
Formbricks now sets its database transaction limits explicitly instead of relying on library defaults. Under load, the old 5-second limit was cancelling writes that were about to succeed: user identification, response updates and survey resets. The new limits are at least as permissive as the old ones, so they need no upgrade step of their own.- Transactions may run for up to 20 seconds.
- Waiting for a free database connection is bounded by
connect_timeoutinDATABASE_URL(5 seconds by default). Withconnect_timeout=0, transactions give up waiting after 6 seconds; other queries wait until a connection is free.
Idle transactions (opt-in)
PostgreSQL can end any session left idle inside an open transaction, which releases the locks that session holds and frees its connection. Formbricks does not ask for this by default: it travels as a startup parameter, and a server or pooler that does not accept the parameter refuses the whole connection instead of ignoring it. Turn it on by adding it toDATABASE_URL in milliseconds:
0, sends nothing and keeps your server’s own setting. An
invalid value (anything but digits, or past PostgreSQL’s 32-bit limit) sends nothing and logs a warning.
It applies to every process that connects with that DATABASE_URL — the migration runner uses
MIGRATE_DATABASE_URL instead when that is set, so it follows that one. Keep the value well above the
longest a migration leaves a transaction idle, or leave the parameter out.
One-click: port 3000 is published on the host only
The one-click installer now publishes the Formbricks container’s port3000 on 127.0.0.1 instead of every
interface, and ./formbricks.sh update changes an existing one-click installation the same way, in
docker-compose.yml and in your Compose override file if you have one, keeping each original as
<file>.before-loopback-port. Traefik keeps serving Formbricks on ports 80 and 443, so the app is only reachable
from outside through Traefik and its TLS and request limits. For that to hold against other machines on the same
local network, Docker Engine must be 28.0 or later and not 28.2.0 to 28.3.2; the installer and the update warn on
those versions.
If you reach port 3000 from other machines on purpose, publish it on an explicit address after updating, such as
- 0.0.0.0:3000:3000 (add - "[::]:3000:3000" for IPv6). The update only rewrites the bare - 3000:3000
mapping, so it leaves an address you chose alone; putting the bare mapping back would be narrowed again next time.
Compose setups that don’t use the one-click script are not changed. If yours publishes 3000:3000 and a reverse
proxy on the same host is the intended way in, bind it to 127.0.0.1:3000:3000 as well.
Changes in Environment Variables
DEFAULT_ORGANIZATION_ID and DEFAULT_ORGANIZATION_ROLE drive SSO just-in-time provisioning, which is
part of the Formbricks Enterprise Edition. On a community instance
they have no effect, so there is nothing to rename.-
DEFAULT_ORGANIZATION_ID has been renamed to AUTH_SSO_DEFAULT_ORGANIZATION_ID, matching the
AUTH_SSO_DEFAULT_TEAM_IDnaming pattern. Rename the variable in your configuration; the value is unchanged. Support for this variable was missing between v3.0 and v5.x, so it takes effect again on v6. - DEFAULT_ORGANIZATION_ROLE has been renamed to AUTH_SSO_DEFAULT_ORGANIZATION_ROLE.
-
AUTHZED_CONSISTENCY must be
fully_consistent. v6 refuses to start with any other value, or with the variable unset. An existing.envthat still setsAUTHZED_CONSISTENCY=minimize_latencymust be changed toAUTHZED_CONSISTENCY=fully_consistentbefore upgrading. -
BETTER_AUTH_URL and BETTER_AUTH_SECRET are now the documented names for the auth base URL and
the session-signing secret. This is a documentation change, not a rename:
NEXTAUTH_URLandNEXTAUTH_SECRETkeep working exactly as before, so an existing instance needs no action. New installs configure theBETTER_AUTH_*names, which is what the setup guides now show. Formbricks now refuses to start when neither variable has a value, in every environment. Before this release that refusal applied only whenNODE_ENV=production; a development or test instance with neither variable set still started, using a fallback value for the signing secret, and only invites and verification links failed. So a production instance is already required to have one of these set and is unaffected. If a non-production instance of yours set neither, generate a secret before upgrading — doing so signs every current user out, because their cookies were signed with that fallback value rather than with a secret of yours.
Encrypted single-use survey links must be regenerated (action required if you use them)
Encrypted single-use survey links issued before v6 stop working and have to be regenerated.What to do when upgrading
Regenerate after upgrading, not before. Links minted by the old version are unsigned whichever day you mint them, so regenerating first produces a second batch that the new version rejects exactly like the first.- Plan a maintenance window covering the deploy and the redistribution. Outstanding links stop working the moment the new version serves traffic, and a respondent who opened one before the deploy and submits after it sees the invitation error and loses their answers.
- Deploy v6.
- Regenerate the links for any survey with single-use enabled and URL encryption of single-use
ID on, from the survey’s share modal or via
GET /api/v1/management/surveys/{surveyId}/singleUseIds. The new version signs them. - Re-send. Formbricks never stores generated links, so there is nothing to migrate and nothing to back up — the new links simply replace the old ones.
suToken signature since 5.0 and validate exactly as before, so integrations that mint their own
IDs need no changes.
What you will see if you skip it
Opening an old encrypted link shows “This survey can only be taken by invitation”, and submissions against it are rejected. Server logs record onewarn per rejection carrying the survey id and a
reason of missing_signature.
Why
An encrypted single-use link carried only an encrypted id and no signature, so nothing tied it to the survey it was issued for and any survey on the same deployment accepted it. Both modes now carry the same signature, which is what the old links are missing.If you cannot re-issue in time
SINGLE_USE_LEGACY_UNSIGNED_UNTIL keeps the old links working for a fixed period, so an in-flight
campaign does not die on upgrade day. It takes an ISO 8601 date and is unset by default:
suToken that is present but
wrong is still rejected, and the id must still decrypt under your ENCRYPTION_KEY.
Each accepted legacy link is logged at warn with the survey id and the configured expiry, and
counted on formbricks_survey_single_use_link_validations_total with reason="legacy_unsigned".
Watch that series fall to zero, then remove the variable — it tells you when the window has stopped
carrying traffic and re-issuing is complete.
Rollback
Downgrading restores the old links, since nothing about them changed on disk — but it also restores the behaviour above, so treat it as a stopgap while you regenerate rather than a fix.Hub worker for Docker
Fresh Docker and one-click installations starthub-worker with the Hub API. It has been part of the stack since
v5 and is what the bundled Compose file ships. The worker consumes every PostgreSQL-backed asynchronous Hub job,
including webhook deliveries and any configured enrichment, so an installation that runs the Hub API without it
leaves those jobs queued.
An existing installation keeps its local docker-compose.yml during formbricks.sh update, so the updater
cannot add the service for you. Adding it is recommended rather than required in this release: the updater prints
a warning and continues when the resolved Compose project does not contain hub-worker. The next major release
will require it, so merging it now avoids the interruption later.
-
Start in the directory containing your customized
docker-compose.yml, then back it up and download the file from the release you are upgrading to. For a one-click installation, runcd formbricksfirst. Use that release’s tag instead ofstablewhen your deployment is pinned. -
Compare the two files. Merge
x-hub-runtime-environmentand the release’shubandhub-workersections as one unit so the API and worker retain the same database and taxonomy configuration. Preserve local proxy, storage, database, and image-pin customizations.hub,hub-worker, andhub-migratemust resolve to the same Hub image reference. -
Validate the result before changing the running stack:
-
For a one-click installation, rerun
./formbricks.sh update— the warning disappears once the resolved project containshub-worker. For a manually managed Compose installation, apply Hub migrations before starting the API and worker from the new image: -
Verify that the worker stays running and that the existing River backlog decreases:
restart: always policy cannot start it again:
Cube model files for Docker
6.1 adds thenpsCount measure and the valueBand dimension to the Cube model. The NPS breakdown and CSAT
breakdown chart presets use them. On Docker the model is two files next to your Compose file,
cube/cube.js and cube/schema/FeedbackRecords.js. Until they are refreshed, those presets fail with
'npsCount' not found.
- One-click:
./formbricks.sh update, run with the current updater as described under Update, refreshes both files while the stack is stopped. It takes them from the release your Formbricks image runs, and keeps the previous copies incube/previous/. If either file cannot be refreshed, both stay as they were, and the update finishes and prints the command to retry. If the image is pinned to a digest or a custom tag, the updater leaves both files alone; use the Docker Compose step below with your release’s tag. - Docker Compose: run the Cube refresh step of the Docker update from
the directory containing your
docker-compose.yml. If you refresh outside an update, recreate Cube afterwards withdocker compose up -d --force-recreate cube. - Helm: the chart ships the model, so
helm upgradereplaces it. No action is needed.
Bundled Cube image (action required for some deployments)
From 6.0.2, the bundled Cube service runsghcr.io/formbricks/cube:1.7.47-2 instead of cubejs/cube:v1.6.6;
6.0.0 and 6.0.1 still run the upstream image. 5.4.5 makes the same change (see
Bundled Cube image update). The new image
contains only the Cube 1.7.47 API server and its Postgres driver on a distroless Node 24 runtime, without the
shell, Python and Cube Store that made image scanners report critical CVEs against the upstream image.
Cube 1.7 returns every numeric measure and dimension as a JSON string. Formbricks converts number-typed members
back into numbers, so dashboards need no change; anything else that queries your Cube API directly now receives
strings for them.
-
Docker Compose and one-click:
formbricks.sh updatekeeps your localdocker-compose.yml, so the stack stays oncubejs/cube:v1.6.6until you replace its wholecubeservice with the one from the release you are upgrading to. Changing onlyimage:is not enough. The new image has no shell, so the oldcommand: sh -c '… exec cubejs server'wrapper exits withError: Cannot find module '/cube/conf/sh'. The image’s own command starts Cube, which still refuses to start withoutCUBEJS_API_SECRET. Keep your ownCUBEJS_*overrides when you merge the block. -
Helm: the chart defaults to the new image. If your values set
cube.image.tagwithoutcube.image.repository, the chart now pulls a non-existentghcr.io/formbricks/cube:<your old tag>and the pod fails withImagePullBackOff. Drop the tag override, or set both values. Pointing them back at an upstreamcubejs/cuberelease is supported. -
Helm security context: the Cube container now runs with a read-only root filesystem, no privilege
escalation and all capabilities dropped. The bundled image and upstream
cubejs/cubereleases need no change. A custom image or configuration that writes files, or dev mode on an upstream image, needscube.containerSecurityContext.readOnlyRootFilesystem: false. -
CUBEJS_DEV_MODE=true: not supported on the new image. Dev mode relies on the embedded Cube Store the image leaves out, so/readyzanswers 500 and the Helm probes and Compose healthcheck never pass. Leave it unset in production. -
Air-gapped installs and registry mirrors: mirror
ghcr.io/formbricks/cube:1.7.47-2along with the other Formbricks images, then point the Composeimage:orcube.image.repositoryat your mirror.
cube.image values and recreating the cube service.
v5.4
v5.4 carries one application-level breaking change, and it is narrow: it affects API keys used against the Feedback Records API or the MCP server. If you do not use Unify Feedback, or you only reach it through the UI, there is nothing to do for it. The release adds no new environment variables. It does ship seven database migrations, applied automatically during the upgrade. Six are quick — new columns and tables, two of them data migrations that run on their own. The seventh builds an index on theResponse
table, and it is the one that needs planning: it blocks response writes while it builds, unless you build
the index yourself first. Other migrations add indexes too, but only on new or small tables. See
Database migrations before scheduling the upgrade.
v5.4 also upgrades the Better Auth authentication core to 1.7. That upgrade needs no manual database
step — every migration is additive and applies automatically, including the backfills over existing rows —
and your SSO callback URLs do not change. It carries one narrow exception, and only for custom code: an
MCP access token is now bound to the session that authorized it, so an integration that caches one across
sessions has to fetch a fresh token — see
MCP access tokens now end with their session.
The bundled Valkey image moves from an amd64-only 8.1.1 image to the native amd64/arm64 Valkey 8.1.9 Alpine
image. Fresh Docker and Helm installations receive it automatically. Existing installations that retain an
older Compose file or explicitly reuse the previous Helm digest need the one-time update below.
Bundled Cube image update (action required for some deployments)
5.4.5 makes the same Cube change as 6.0.2: the bundled service moves fromcubejs/cube:v1.6.6 to
ghcr.io/formbricks/cube:1.7.47-2, and Helm runs it with a read-only root filesystem. Follow
Bundled Cube image with version=5.4.5, so the
cube service you copy comes from the 5.4.5 Compose file rather than a 6.x one.
Envoy Gateway 1.8.4 CRD Upgrade
The updated Helm chart replaces Envoy Gateway 1.7.1 and Gateway API 1.4.1 with Envoy Gateway 1.8.4 and Gateway API 1.5.1. Fresh installations receive the new CRDs automatically. Helm does not update CRDs during an ordinary upgrade, so existing installations using the bundled controller must apply the matching CRDs before upgrading:--force-conflicts transfers the existing CRD fields from Helm’s field manager to this explicit CRD upgrade.
Pulling the OCI chart first keeps Helm 4’s pull messages out of the manifest pipe. Remove the temporary directory
after the apply succeeds.
The chart does not install Envoy Gateway’s cluster-wide safe-upgrade admission policy by default. Enabling
envoy.crds.gatewayAPI.safeUpgradePolicy.enabled denies Gateway API CRD versions older than v1.5.0 across the
whole cluster, including CRDs reconciled by unrelated releases. Enable it only after platform review; a chart
rollback does not remove that policy automatically.
If the platform already manages compatible Gateway API CRDs, do not install another Gateway API bundle. Install
only the Envoy Gateway CRDs as described in the Kubernetes guide, set
envoy.crds.enabled=false, and configure Flux to skip CRD installation and upgrades. Upgrade the CRDs before the
controller and never downgrade them during rollback.
The controller scheduling values also move from the ineffective envoy.deployment.envoyGateway.pod path to
envoy.deployment.pod. Move any node selectors, tolerations, topology spread constraints, extra volumes, or
pod annotations that you placed under the old path before upgrading.
The parent chart no longer repeats the bundled controller’s default port list. The
envoy.deployment.ports override remains supported, so existing port overrides need no migration.
Database migrations
All seven migrations apply automatically during the upgrade. Two want something from you first: one needsWEBAPP_URL set if you use MCP OAuth, and one deserves a step of its own on large instances. So here
is the full list rather than a summary:
Build the response index before you upgrade
The migration creates its index onlyIF NOT EXISTS, so an index you build beforehand makes it a no-op. Built
with CONCURRENTLY, the index takes longer but never locks out writes, and the instance keeps running v5.3
while it builds. It does add disk and WAL load, so some writes can slow down while it runs. On a large table the
build can take a while, so run it from a session that survives a dropped connection, such as tmux or screen:
- One-Click / Docker Compose
- Kubernetes
From the directory that contains your
docker-compose.yml (formbricks for One-Click installs):psql as the user that owns the Formbricks
tables, connected directly to the primary rather than through a transaction-pooling connection pooler such as
PgBouncer. A pooler can run the SET and the CREATE on different server connections. Any other SQL client must
run the CREATE INDEX statement on its own, outside a transaction, because CONCURRENTLY cannot run inside one.
The two SET lines keep a timeout configured on the server, the database or your user from cancelling the
build: lock_timeout also limits how long the build waits for other transactions to finish. On PostgreSQL 17 or
later, if SHOW transaction_timeout; returns anything other than 0, add SET transaction_timeout = 0; before
the CREATE line too.
Then read the indisvalid result:
t: the index is ready. Upgrade as usual; the migration finds the index and skips the build.f: the index exists but is not usable yet. Do not upgrade over it, because the migration would see the name, skip the build, and leaveResponsewith an index PostgreSQL never uses. See below.- No row: the
CREATEfailed before it started, for example under the wrong user or on a read-only replica. Fix the error it printed and run the commands again. - No result at all:
psqllost its connection, or never got one, before the check ran. Read on underf.
f, or no result at all, means the build is still running or stopped part-way. If the CREATE printed an
error, it stopped: fix that cause (often a timeout), drop the index as below, and run the commands again. If your
session dropped instead, the build can still be running on the server, so first check, the same way and as the
same user as above:
indisvalid again. A waiting for writers phase is
waiting for transactions that wrote to Response. waiting for old snapshots waits for every older transaction
in this database that still holds a snapshot, on any table, so a long report or a pg_dump backup counts too.
Either way, current_locker_pid is the session it waits for, which you can look up in pg_stat_activity. If the
query returns nothing, the build has ended: check indisvalid again. t means it finished after all, so upgrade
as usual. Still f means it was interrupted: drop the index the same way, with the same SET lines first, since
a concurrent drop waits under lock_timeout too:
SET transaction_timeout = 0; here too if you needed it for the build. Then run the commands above again.
An extra index is harmless to v5.3, so building it early needs no rollback plan of its own.
None of the seven constrains a rollback. v5.3 reads none of the new columns and tables, and the two
NOT NULL columns added to existing tables both carry a database default, so a v5.3 instance keeps
writing to those tables exactly as before. Rolling the application images / Helm values back to v5.3 is
safe without touching the database.
Coming back forward has one catch, and only if accounts were created while you were rolled back.
Account.issuer does not exist in v5.3, so any account signed up or linked on that version is written
without one — and both issuer backfills are already recorded as applied, so rolling forward does not
re-run them. Better Auth 1.7 keys accounts on (issuer, accountId), which makes those rows invisible to
password sign-in and password reset. SSO accounts repair themselves on the next successful sign-in;
password accounts have no such path. If anyone signed up during the rollback window, re-run the repair
after rolling forward. This is the statement the repair_account_issuer migration runs, and it is
idempotent — it matches the providers v5.4 ships, so if you have since enabled one that is not listed
here, take the current mapping from that migration rather than this copy:
Bundled Valkey 8.1.9 Upgrade (action required for some self-hosted deployments)
The old bundled digest selects onlylinux/amd64. The new digest selects the matching native image on
linux/amd64 and linux/arm64, without requiring QEMU or binfmt. Valkey 8.1.9 also contains upstream
security fixes that are not present in 8.1.1.
- One-Click
- Docker Compose
- Helm
Download the current stable updater so it can migrate the exact old Valkey pin in your existing Compose
file. It creates
formbricks/docker-compose.yml.before-valkey-8.1.9, validates the updated Compose file,
and restores the backup if validation fails. Custom Valkey images are left unchanged.Feedback Record Mutations by API Key (action required for some integrations)
A feedback dataset can be assigned to more than one workspace, and the records inside it carry no workspace of their own. When a dataset is shared that way, a workspace-scoped permission cannot establish which workspace a given record belongs to. Two rules now follow from that:- Deleting a feedback record requires
manage. A key withwriteis no longer enough. This brings feedback records in line with every otherDELETEin the API, which has always requiredmanage. - A key cannot update or delete records in a shared dataset. If a dataset is assigned to two or more
workspaces, an API key is refused regardless of its permission level, with a
403explaining why.
The same applies to the MCP tools
update_feedback_record and delete_feedback_record when the client
authenticates with an API key. The required MCP scope is unchanged — both remain feedbackRecords:write.
Only API keys are affected. Users signing in through the UI, OAuth/MCP connections, and SAML/OIDC
sessions authorize through their organization role instead, which is unchanged: organization owners and
managers can still update and delete feedback records, including in a shared dataset. Reading, searching
and creating records are unaffected for every principal.
What to do before upgrading
- Run the query above. If it returns rows, decide how each affected integration should proceed.
- Raise any API key that deletes feedback records from
writetomanage. - After deploying, smoke test your feedback-record integrations. Two log lines identify an integration
caught by the new rules — grep for
Feedback record mutation denied(the MCP path, which names the reason) and forFeedback records gateway authorization denied(the REST path, which logsverdict: "deny"alongside theoperationandfeedbackDirectoryId).
Rollback
This access-rule change itself ships no migration and touches no schema (the release’s migrations are listed above), so rolling the application images / Helm values back to v5.3 undoes it with nothing to restore. An API key raised tomanage in step 2 keeps working on v5.3 —
manage was always accepted wherever write was — so that step does not need reverting either.
Better Auth 1.7 upgrade (no action required for most instances)
v5.4 upgrades the Better Auth authentication core from the 1.6 line to 1.7, which fixes GHSA-p2fr-6hmx-4528: MCP OAuth access-token audiences are now bound to the resource a user actually approved during authorization, instead of trusting whatever the client requested. This is a coupled core upgrade — it touches login, sessions, 2FA, SSO and MCP OAuth — but for self-hosted instances it needs no manual database step. Every schema change is additive and nullable, and the required backfills (account identity, existing OAuth clients, refresh tokens, consents) run automatically during the upgrade — they are the twoeng_2343 entries in the
Database migrations table above.
The migration also adds two nullable columns, alg and crv, to the jwks table that holds your
instance’s JWT signing keys. Keys created before the upgrade keep working untouched — Better Auth reads a
null alg as the default algorithm — so there is nothing to backfill, but the columns must exist before
the new version mints its next key.
AZUREAD_TENANT_ID: set a concrete tenant for full id_token verification
Better Auth 1.7 verifies Microsoft id_tokens against the issuer advertised by the tenant’s OpenID discovery document wheneverAZUREAD_TENANT_ID names a concrete tenant — a Directory (tenant) ID GUID or
a verified domain. That is strictly stronger than v5.3 and needs no action.
Two of Microsoft’s multi-tenant authorities are different. common and organizations advertise the
literal placeholder https://login.microsoftonline.com/{tenantid}/v2.0 as their issuer, so verifying
real tokens against it fails every Microsoft sign-in with ?error=unable_to_get_user_info. Formbricks
therefore handles those two the way it already handles an unset AZUREAD_TENANT_ID: sign-in identity
comes from Microsoft’s userinfo endpoint over a client-authenticated call (the v5.3 behavior) and
id_tokens are not verified. The authority you configure still applies at sign-in, so organizations
keeps restricting sign-in to work/school accounts, and an unset value behaves as common. Formbricks
logs a warning at startup when either is set and Microsoft SSO is enabled. To get full id_token
verification, set your Directory (tenant) ID instead.
consumers needs no action and is not affected: every personal Microsoft account lives in one
well-known tenant, so that authority advertises a real issuer and keeps full id_token verification.
SSO callback URLs are unchanged
Your SSO callback URLs stay exactly as they are:
Better Auth 1.7 rebuilds its generic-OAuth support on top of the built-in social-provider route, which
would otherwise have moved these three callbacks to
/api/auth/callback/{providerId} and required you to
register a new redirect URI at your identity provider. OAuth requires redirect URIs to match exactly, so a
path change means editing every provider configuration by hand.
Formbricks pins the callback URL instead, so it no longer follows the authentication library’s internal
routing. The URLs above are the ones introduced in v5.2 and they are now a stable part of the Formbricks
API — a future authentication-core upgrade will not move them either.
The table writes
your-domain.com as your WEBAPP_URL, which is correct for a default install. The
callback is derived from the same auth base URL Better Auth itself uses, not from WEBAPP_URL: that is
BETTER_AUTH_URL, or NEXTAUTH_URL when only the legacy name is set, falling back to WEBAPP_URL when
neither is. So if either auth URL names a different origin, the callback lives on that origin — register
that URI at your identity provider. Either way it is unchanged by this upgrade.Nothing to do, and nothing to undo. If you added a
/api/auth/callback/{providerId} redirect URI in
advance based on an earlier draft of these notes, leaving it registered is harmless — most identity
providers accept several redirect URIs, and Formbricks simply will not use it.MCP access tokens now end with their session
An MCP access token used to stay valid until its own expiry, independently of the session that authorized it. In 1.7 it is bound to that session: once the session ends — the user signs out, or the session is revoked — the token is inactive./introspect reports active: false for it and
/api/auth/oauth2/userinfo rejects it, rather than honouring it until its TTL runs out.
This is upstream hardening, and it is the stricter of the two behaviours: a token that outlives the sign-out
which created it is exactly what a revocation is supposed to stop.
It applies to access tokens. A refresh token granted offline_access deliberately survives the session
ending, per OIDC Back-Channel Logout 1.0 §2.7, so that long-lived API access can outlive a browser session —
which is what MCP clients rely on. Signing out is therefore not a way to cut a client off permanently.
No action is required. MCP clients re-authorize on their own — Claude Code and MCP Inspector both do —
so a user who signs out and back in sees nothing. It is listed here for one case only: a custom integration
that caches a Formbricks MCP access token and reuses it across sessions will start receiving rejections
where it previously succeeded, and should obtain a fresh token instead of treating the cached one as valid
until its expiry.
Rollback (Better Auth 1.7)
TheAccount.issuer column and the new oauthResource, oauthClientResource and oauthClientAssertion
tables are additive and nullable, and are not read by v5.3, so rolling the application images / Helm values
back to v5.3 is safe without touching the database. SSO needs no attention in either direction: v5.3 and
v5.4 use the same callback URLs. If accounts are created while you are rolled back, see the
rollback note above before rolling forward — Account.issuer is the one value a
v5.3 instance cannot write.
v5.3
Formbricks v5.3 is a drop-in upgrade for most self-hosted instances. There are no breaking API, webhook, or authentication changes, and every database migration is additive and runs automatically on startup. The only self-hosting-relevant changes are a few new optional variables. The main thing worth setting before you deploy isTRUSTED_PROXY_HOP_COUNT for your proxy setup; everything else is optional.
All new environment variables are optional and defaulted — none of them block startup. You can upgrade
to v5.3 without changing any environment variables and everything will run.
Client IP Resolution Behind a Proxy (recommended)
v5.3 resolves the client IP from theX-Forwarded-For chain using a configurable number of trusted proxy
hops:
TRUSTED_PROXY_HOP_COUNT— optional, defaults to1. Set it to the number of trusted proxies / load balancers in front of Formbricks (e.g. an ingress plus a CDN =2). An explicit0trusts no proxy at all: the runtime exposes no socket peer address, so the client IP then resolves to a fixed “untrusted” placeholder that rate limiting and response audit logging record in place of a real address. A forwarding chain shorter than the configured count, or a malformed selected hop, also fails closed. Ports appended by gateways such as Azure Application Gateway are removed, and IPv6 addresses are normalized to their/64network. Leave the setting at1(or your real hop count) unless you deliberately want to stop capturing client IPs.
Renamed Configuration
- Scheduling variables renamed.
NEXT_PUBLIC_SURVEY_SCHEDULING_*→SURVEY_SCHEDULING_*(theNEXT_PUBLIC_prefix was dropped). This only affects you if you ran a v5.3 release candidate that used the old names; a clean v5.2 → v5.3 upgrade never set them.
Optional New Configuration
None of the following is required to run v5.3. Add them only if you want the associated feature.Survey Scheduling
The new survey-scheduling feature runs on a fixed daily local time. All three are optional and defaulted:SURVEY_SCHEDULING_TIME_ZONE— a valid IANA time zone. DefaultEurope/Berlin.SURVEY_SCHEDULING_LOCAL_HOUR— default0.SURVEY_SCHEDULING_LOCAL_MINUTE— default0.
Password Breach Check
PASSWORD_HIBP_CHECK_DISABLED— set to"1"to disable the “Have I Been Pwned” breached-password check on sign-up and password reset ("0"or unset keeps it on).
Analytics (Cube)
Two Cube-related deltas in v5.3. Neither changes the Cube schema, so there is no schema refresh to run:CUBEJS_EXTERNAL_DEFAULTnow defaults tofalse(in bothdocker/docker-compose.ymland the Helm chart). Cube Store is used only when you explicitly opt into external pre-aggregations — a safe default for most self-hosters. Set it back totrueonly if you run external pre-aggregations.- The Hub image moves to
0.8.1. The Helm chart pins0.8.1. Docker Compose instead resolves the Hub image through${HUB_IMAGE_REF:-:latest}, so a plaindocker compose pullfetches whatever:latestcurrently is — setHUB_IMAGE_REF=:0.8.1indocker/.envbefore pulling for a deterministic upgrade.
Upgrade Steps
- Back up your database.
- Set
TRUSTED_PROXY_HOP_COUNTto the number of proxies in front of your instance (default1). - Update your deployment assets to v5.3 before pulling images —
docker compose pullreads your local Compose file, so fetch the v5.3docker-compose.ymlfirst (Helm users use the v5.3 chart). For a deterministic Hub image on Compose, setHUB_IMAGE_REF=:0.8.1indocker/.env. - Pull and restart (
docker compose pull && docker compose down && docker compose up -d, orhelm upgradewith the new chart / tag). Migrations apply automatically on startup. - Smoke test: sign in (including 2FA), create and schedule a survey, and open the Feedback Records dashboards / charts to confirm analytics render correctly.
Rollback
The v5.3 migrations are additive and are not read by v5.2, so rolling back the application images / Helm values to v5.2 is safe without touching the database. Restore a database backup only if you specifically need to undo the schema additions — and note that doing so discards any data written after the backup was taken, so perform it within a maintenance window.v5.2
Formbricks v5.2 migrates authentication from NextAuth to Better Auth. For most self-hosted instances this is a drop-in upgrade, but if you use SSO (Azure AD / Entra ID, generic OIDC, or SAML) you must re-register the OAuth callback URL at your identity provider before the first v5.2 restart, or SSO sign-in will fail with a redirect-URI mismatch.SSO Callback URL Change (action required for SSO users)
Better Auth serves the OAuth2/OIDC callback at a new path:- old (NextAuth):
/api/auth/callback/{provider} - new (Better Auth):
/api/auth/oauth2/callback/{providerId}
https://your-domain.com
with your instance URL:
Google and GitHub sign-in do not change. They use Better Auth’s built-in social providers, whose
callback path (
/api/auth/callback/{provider}) is unchanged — nothing to do there.For SAML, only the Jackson connection’s redirect_uri changes. The IdP-side ACS endpoint
(/api/auth/saml/callback) is unchanged, so you do not need to reconfigure your IdP’s SAML app.Leave the old /api/auth/callback/* redirect URIs registered through the transition. Keeping them in
place is harmless and makes a rollback to v5.1 clean.Authentication Environment Variables
Better Auth introduces two optional variables. Neither is required for a standard upgrade — both fall back to your existing NextAuth configuration:BETTER_AUTH_SECRET— cookie/session signing secret. Falls back toNEXTAUTH_SECRETwhen unset. On these releases it must be at least 32 characters when set explicitly. Use the valueNEXTAUTH_SECRETalready holds: a different one invalidates every session and every outstanding invite and verification link.BETTER_AUTH_URL— the auth base URL. Falls back toNEXTAUTH_URL(and thenWEBAPP_URL) when unset.
If your instance already sets
NEXTAUTH_SECRET and NEXTAUTH_URL (all v5.1 instances do), you can
upgrade without adding any new auth variables.DISABLE_ACCOUNT_DELETION_SSO_CONFIRMATION no longer exists. Remove it from your environment
if you set it; it now has no effect.
Optional New Configuration
None of the following is required to run v5.2. Add them only if you want the associated feature.Hub Enrichment (sentiment, emotion, translation)
Hub>= 0.8.0 can classify sentiment and emotion and translate feedback. These are off unless you set
a provider and model, and they run in both the Hub API and the hub-worker, so set the variables on both
services. Bump your Hub image to >= 0.8.0 (via HUB_IMAGE_REF) to pick up these features.
SENTIMENT_PROVIDER,SENTIMENT_MODEL,SENTIMENT_PROVIDER_API_KEYEMOTIONS_PROVIDER,EMOTIONS_MODEL,EMOTIONS_PROVIDER_API_KEYTRANSLATION_PROVIDER,TRANSLATION_MODEL,TRANSLATION_PROVIDER_API_KEY,TRANSLATION_DEFAULT_LANGUAGE
openai, google (AI Studio, needs *_PROVIDER_API_KEY), and google-gemini
(Vertex, needs *_GOOGLE_CLOUD_PROJECT / *_LOCATION plus Application Default Credentials).
AI Taxonomy (beta)
An optional standalone taxonomy service, enabled in Docker Compose withCOMPOSE_PROFILES=taxonomy. It
shares an internal token with Hub:
TAXONOMY_SERVICE_URL(the internal URL to the taxonomy service)HUB_INTERNAL_API_TOKEN,TAXONOMY_SERVICE_TOKEN(strong random secrets)TAXONOMY_LLM_PROVIDER/TAXONOMY_LLM_MODEL/TAXONOMY_LLM_BASE_URL/TAXONOMY_LLM_API_KEY(openai-compatibleby default;bedrockandvertex-geminialso supported)TAXONOMY_IMAGE_REFto pin a released image (e.g.:v0.1.0)
COMPOSE_PROFILES=qwen,taxonomy to back it with the bundled Qwen/vLLM service.
Bundled Qwen / vLLM Runtime
Docker Compose can now run a bundled OpenAI-compatible LLM for self-hosted AI features withCOMPOSE_PROFILES=qwen. Tunable via QWEN_VLLM_IMAGE, QWEN_MODEL_ID, QWEN_SERVED_MODEL_NAME,
QWEN_MAX_MODEL_LEN, QWEN_MAX_NUM_SEQS, QWEN_GPU_MEMORY_UTILIZATION, QWEN_VLLM_HOST, and
QWEN_VLLM_PORT.
OpenTelemetry Log Export
OTEL_LOGS_ENABLED=1— export Pino application logs via OTLP (off by default; also needsOTEL_EXPORTER_OTLP_ENDPOINT).
Upgrade Steps
v5.2 requires no manual database migration step beyond the standard automatic Prisma migrations on startup (the credential-account backfill runs automatically). The Helm chart is unchanged from v5.1.- If you use SSO, add the new
/api/auth/oauth2/callback/{providerId}redirect URIs at your IdP (see the table above), keeping the old ones in place. - Back up your database.
- Pull the v5.2 images and restart (
docker compose pull && docker compose down && docker compose up -d, orhelm upgradewith the new tag). - After startup, sign in again (the one-time forced re-login) and verify each configured SSO provider completes sign-in.
Rollback
Because the old/api/auth/callback/* redirect URIs are left in place, rolling back to v5.1 is clean:
revert your image tags / Helm values, and the previous NextAuth callback path works again. Restore the
database backup only if you need to undo schema changes.
v5
Formbricks v5 changes the self-hosted runtime contract. If you are upgrading an existing Formbricks 4.x deployment, review this section before starting the new version.What Changes In v5
- Formbricks Hub is now mandatory for self-hosted Formbricks v5 deployments.
- Cube is now part of the baseline stack alongside Hub. Docker, one-click, and Helm all bundle Cube by
default;
CUBEJS_API_SECRETis required. Operators can disable the bundled Cube deployment in Helm to use an external cluster instead. - Edge rate limiting is now required for specific public and API-key routes. Those routes are no longer throttled inside the application server.
- AI features are configured at the instance level via
AI_*environment variables.
Before You Upgrade
Before you restart your instance on Formbricks v5:- back up your database
- identify your current deployment type: one-click, manual Docker Compose, or Kubernetes/Helm
- confirm Redis/Valkey and your file storage setup are already healthy from your v4 baseline
- identify whether file uploads use external S3-compatible storage or a legacy bundled MinIO service
- budget approximately ~500 MB additional RAM headroom for the bundled Cube container (dashboards and analysis are part of the baseline now)
- decide whether this instance needs optional AI features
- verify whether you already run Envoy Gateway or another equivalent edge rate limiter for the covered routes
Required Config And Infrastructure Changes
Formbricks Hub
Formbricks v5 expects Hub to be part of the self-hosted stack.HUB_API_KEYis requiredHUB_API_URLmust point to the Hub service the Formbricks app can reachHUB_DATABASE_URLis optional; when unset, Hub can share the same PostgreSQL database as Formbricks- bundled Docker and Helm assets run Hub database migrations automatically during startup or upgrade, and the migration steps are idempotent
Hub-specific source code and standalone deployment assets live in the
Formbricks Hub repository. For a normal Formbricks v5 upgrade, use that
repository as reference material only; the canonical migration steps stay in these Formbricks docs.
Edge Rate Limiting
Formbricks v5 splits rate limiting across two layers:- Envoy Gateway, or an equivalent edge rate limiter, for covered public and API-key routes
- the application server for the remaining session-authenticated routes, server actions, and uncovered APIs
AI Features
AI features are optional and only need instance-level configuration if you want to enable the related enterprise functionality.AI_PROVIDERandAI_MODELare the base settingsAI_PROVIDER=awsrequiresAI_AWS_REGION,AI_AWS_ACCESS_KEY_ID, andAI_AWS_SECRET_ACCESS_KEYAI_PROVIDER=googlerequiresAI_GOOGLE_CLOUD_PROJECTandAI_GOOGLE_CLOUD_LOCATION; credentials are optional overrides when Application Default Credentials are unavailableAI_PROVIDER=azurerequiresAI_AZURE_API_KEYand eitherAI_AZURE_BASE_URLorAI_AZURE_RESOURCE_NAMEAI_PROVIDER=openai-compatiblerequiresAI_OPENAI_COMPATIBLE_BASE_URL; the LLM GA v1 self-hosted path is Qwen served by vLLM- Helm operators can set
llm.enabled: trueto deploy the bundled Qwen/vLLM runtime - Docker Compose operators can set
COMPOSE_PROFILES=qwento deploy the bundled Qwen/vLLM runtime - Keep the bundled runtime disabled when you configure Google Vertex, Azure, AWS Bedrock, or an external OpenAI-compatible endpoint manually
Cube
Cube is part of the baseline Formbricks v5 stack.- the Docker, one-click, and Helm deployments all bundle the
cubeservice by default - the Formbricks app requires
CUBEJS_API_URLandCUBEJS_API_SECRET; the install/dev-setup scripts generate the secret automatically for new installs - Helm operators who want to run an external Cube cluster can set
cube.enabled: falseand provide their own endpoint viadeployment.env.CUBEJS_API_URL - if you run Cube yourself, you may also need to override
CUBEJS_DB_*values for the Cube service
Upgrade Steps By Deployment Type
1. Back up your database- One-Click
- Docker Compose
- Kubernetes
From the
formbricks directory created by the installer:If your PostgreSQL service name differs, run
docker compose ps first and adjust the command.- One-Click
- Docker Compose
- Kubernetes
The one-click Then compare
./formbricks.sh update command only pulls new images. It does not rewrite your existing
formbricks/docker-compose.yml, so you must merge the v5 stack changes before the first v5 restart.formbricks/docker-compose.v5.yml with your existing formbricks/docker-compose.yml and merge
the v5 additions:- set
POSTGRES_PASSWORDinformbricks/.envto the exact password used by the existing PostgreSQL volume; do not generate a new value during the upgrade. If it contains URI-reserved characters, also setPOSTGRES_PASSWORD_URL_ENCODEDto its percent-encoded form - add a non-empty
HUB_API_KEYand reuse the same value wherever your deployment resolves Hub auth - keep
HUB_API_URLathttp://hub:8080unless Hub runs elsewhere - include the bundled
formbricks-migrate,hub-migrate,hub, andhub-workerservices - sync
formbricks/cube/cube.jsandformbricks/cube/schema/FeedbackRecords.jsfrom the current release and ensureformbricks/.envcontainsCUBEJS_API_SECRET(Cube is part of the baseline stack in v5) - if your older setup still uses bundled MinIO for uploads, review that storage path separately before the
first v5 restart; newer self-hosting updates move the bundled object-storage path to RustFS, while
external S3-compatible storage keeps the same
S3_*app contract - add any
AI_*variables you need - if you prefer to run an external Cube instance, point
CUBEJS_API_URLat it and provide the matchingCUBEJS_API_SECRET; otherwise the bundledcubeservice runs against the local Postgres
Post-Upgrade Verification
After the upgrade:- confirm the Formbricks app starts and
GET /healthreturns successfully - confirm the Hub service is healthy and reachable from the Formbricks app
- verify any Hub-backed connector or feedback flows you use
- verify covered routes are rate-limited at the edge layer
- verify AI features only if you configured the required
AI_*variables - verify dashboards and analysis flows against the bundled (or external) Cube endpoint
Troubleshooting And Rollback
Common upgrade issues:- Missing
HUB_API_KEY: Formbricks cannot authenticate to Hub, and Hub itself may fail to start - Bad
HUB_API_URL: the app starts, but Hub-backed features fail because the service cannot be reached - No edge rate limiter in front of covered routes: the v5 deployment runs, but those routes are no longer protected by the legacy in-app limiter
- Missing AI provider configuration: AI features remain unavailable until
AI_PROVIDER,AI_MODEL, and the matching provider settings are set correctly - Missing
CUBEJS_API_SECRET(or unreachable Cube endpoint): the Formbricks app fails env validation at boot, or — if env vars are present but Cube is unreachable — dashboards and analysis queries fail while the rest of the app stays healthy - Cube healthcheck fails with
wget: not found: older v5 Docker Compose files usedwgetfor the bundled Cube healthcheck, but the bundled Cube image does not include it. Sync the wholecubeservice from the current Docker Compose file so the healthcheck checks/readyzwith Node instead (see Bundled Cube image).
- restore the pre-upgrade database backup if schema or data changes require it
- revert your image tags or Helm release values to the last known-good Formbricks 4.x version
- revert compose or Helm configuration changes introduced for the v5 rollout
Formbricks v5 also includes the Environment ID to Workspace ID transition for SDK setup. If you still rely on a
legacy Environment ID, read the Migrate to Workspace ID guide.
v4.7
Formbricks v4.7 introduces typed contact attributes with nativenumber and date data types. This enables comparison-based segment filters (e.g. “signup date before 2025-01-01”) that were previously not possible with string-only attribute values.
What Happens Automatically
When Formbricks v4.7 starts for the first time, the data migration will:- Analyze all existing contact attribute keys and infer their data types (
text,number, ordate) based on the stored values - Update the
ContactAttributeKeytable with the detecteddataTypefor each key - If your instance has fewer than 1,000,000 contact attribute rows: backfill the new
valueNumberandvalueDatecolumns inline. No manual action is needed. - If your instance has 1,000,000 or more contact attribute rows: the value backfill is skipped to avoid hitting the migration timeout. You will need to run a standalone backfill script after the upgrade.
Most self-hosted instances have far fewer than 1,000,000 contact attribute rows (a typical setup with 100K
contacts and 5-10 attributes each lands around 500K-1M rows). If you are below the threshold, the migration
handles everything automatically and you can skip the manual backfill step below.
Steps to Migrate
1. Backup your Database- Docker
- Kubernetes
Before running these steps, navigate to the
formbricks directory where your docker-compose.yml file is located.If you run into “No such container”, use
docker ps to find your container name, e.g.
formbricks_postgres_1.- Docker
- Kubernetes
- Docker
- Kubernetes
Skipping value backfill (X rows >= 1000000 threshold), proceed to step 4. Otherwise, the migration is complete and no further action is needed.
4. Run the Backfill Script (large datasets only)
If the migration skipped the value backfill, run the standalone backfill script inside the running Formbricks container:
- Docker
- Kubernetes
Replace
formbricks with your actual container name if it differs. Use docker ps to find it.- Safe to run while Formbricks is live — it does not lock the entire table or wrap work in a long transaction
- Idempotent — it only updates rows where the typed columns are still
NULL, so you can safely run it multiple times - Resumable — each batch commits independently, so if the process is interrupted you can re-run it and it picks up where it left off
- No timeout risk — unlike the migration, this script runs outside the migration transaction and has no time limit
- Access your Formbricks instance at the same URL as before
- If you use contact segments with number or date filters, verify they return the expected results
- Check that existing surveys and response data are intact
v4.0
Formbricks 4.0 is a major milestone that sets up the technical foundation for future iterations and feature improvements. This release focuses on modernizing core infrastructure components to improve reliability, scalability, and enable advanced features going forward.What’s New in Formbricks 4.0
🚀 New Enterprise Features:- Quotas Management: Advanced quota controls for enterprise users
- Enhanced File Storage: Improved file handling with better performance and reliability
- Improved Caching: New caching functionality improving speed, extensibility and reliability
- Database Optimization: Removal of unused database tables and fields for better performance
- Future-Ready Architecture: Standardized infrastructure components for upcoming features
What This Means for Your Self-Hosting Setup
These improvements in Formbricks 4.0 also make some infrastructure requirements mandatory going forward:- Redis for caching
- RustFS or S3-compatible storage for file uploads
Why We Made These Changes
We know this represents more moving parts in your infrastructure and might even introduce more complexity in hosting Formbricks, and we don’t take this decision lightly. As Formbricks grows into a comprehensive Survey and Experience Management platform, we’ve reached a point where the simple, single-service approach was holding back our ability to deliver the reliable, feature-rich product our users demand and deserve. By moving to dedicated, professional-grade services for these critical functions, we’re building the foundation needed to deliver:- Enterprise-grade reliability with proper redundancy and backup capabilities
- Advanced features that require sophisticated caching and file processing
- Better performance through optimized, dedicated services
- Future scalability to support larger deployments and more complex use cases without the need to maintain two different approaches
Migration Steps for v4.0
Additional migration steps are needed if you are using a self-hosted Formbricks setup that uses either local file storage (not S3-compatible file storage) or doesn’t already use a Redis cache.Legacy one-click v4.0 upgrade
For historical v4.0 upgrades, our original one-click migration script is still available:- Adds a Redis service to your setup and configures it
- Adds a bundled MinIO service to your setup, configures it, and migrates local files to it
- Pulls the latest Formbricks image and updates your instance
Manual Setup
If you use a different setup to host your Formbricks instance, you need to make sure to make the necessary adjustments to run Formbricks 4.0.Redis
Formbricks 4.0 requires a Redis instance to work properly. Please add a Redis instance to your Docker setup, your K8s infrastructure, or however you are hosting Formbricks at the moment. Formbricks works with the latest versions of Redis as well as Valkey. You need to configure theREDIS_URL environment variable and point it to your Redis instance.
S3-compatible storage
To use file storage (e.g., file upload questions, image choice questions, custom survey backgrounds, etc.), you need to have S3-compatible file storage set up and connected to Formbricks. Formbricks supports multiple storage providers (among many other S3-compatible storages):- AWS S3
- Digital Ocean Spaces
- Hetzner Object Storage
- Custom RustFS server
Upgrade Process
1. Backup your Database Critical Step: Create a complete database backup before proceeding. Formbricks 4.0 will automatically remove unused database tables and fields during startup.If you run into “No such container”, use
docker ps to find your container name, e.g.
formbricks_postgres_1.- Detect and apply required database schema updates
- Remove unused database tables and fields
- Optimize the database structure for better performance
- Access your Formbricks instance at the same URL as before
- Test file uploads to ensure S3 storage integration works correctly. Check the File Upload Troubleshooting section if you face any issues.
- Verify that existing surveys and data are intact
- Check that previously uploaded files are accessible
v3.3
With Formbricks 3.0, we introduced an automatic data migration system. If you’re using a version older than
v2.7, you must upgrade step-by-step through earlier versions (e.g. 2.4 → 2.5 → 2.6 → 2.7). Once you
reach v2.7, you can upgrade directly to any v3.x or future release without needing intermediate
migrations.
Steps to Migrate
This guide is for users who are self-hosting Formbricks using the one-click setup. If you have a different setup, you may need to adjust the commands accordingly. Before running these steps, navigate to theformbricks directory where your docker-compose.yml file is located.
- Backup your Database: Backing up your database is crucial before upgrading. Use the following command to create a backup:
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.*.sql file remove the -Fc (custom format) option. In case of a restore scenario you will need to use psql then with an empty formbricks database.
- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Access your updated instance
v3.2
With Formbricks 3.0, we introduced an automatic data migration system. If you’re using a version older than
v2.7, you must upgrade step-by-step through earlier versions (e.g. 2.4 → 2.5 → 2.6 → 2.7). Once you
reach v2.7, you can upgrade directly to any v3.x or future release without needing intermediate
migrations.
Steps to Migrate
This guide is for users who are self-hosting Formbricks using the one-click setup. If you have a different setup, you may need to adjust the commands accordingly. Before running these steps, navigate to theformbricks directory where your docker-compose.yml file is located.
- Backup your Database: Backing up your database is crucial before upgrading. Use the following command to create a backup:
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.*.sql file remove the -Fc (custom format) option. In case of a restore scenario you will need to use psql then with an empty formbricks database.
- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Access your updated instance
v3.1
With Formbricks 3.0, we introduced an automatic data migration system. If you’re using a version older than
v2.7, you must upgrade step-by-step through earlier versions (e.g. 2.4 → 2.5 → 2.6 → 2.7). Once you
reach v2.7, you can upgrade directly to any v3.x or future release without needing intermediate
migrations.
Steps to Migrate
This guide is for users who are self-hosting Formbricks using the one-click setup. If you have a different setup, you may need to adjust the commands accordingly. Before running these steps, navigate to theformbricks directory where your docker-compose.yml file is located.
- Backup your Database: Backing up your database is crucial before upgrading. Use the following command to create a backup:
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.*.sql file remove the -Fc (custom format) option. In case of a restore scenario you will need to use psql then with an empty formbricks database.
- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Access your updated instance
v3.0
This major release brings a better approach to data migrations.- If you’re using Formbricks v2.7 or later, you can upgrade directly to v3.x and future versions.
- If you’re on a version older than v2.7, you must upgrade step-by-step (e.g. 2.4 → 2.5 → 2.6 → 2.7) before moving to v3.x.
Steps to Migrate
This guide is for users self-hosting Formbricks with the one-click setup. If you’re using a different setup, you might adjust the commands.- Navigate to the Formbricks Directory
docker-compose.yml file is located.
- Backup Your Database
Note: If you see a “No such container” error, run
docker ps to find your actual container name (e.g.
formbricks_postgres_1).Tip: If you want to store the backup as a
.sql file, remove the -Fc (custom format) option. If you need
to restore it later, use psql with an empty Formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Access your updated instance
v2.7
AI Features & Database Update: This release lays the groundwork for upcoming AI features, now in private beta. Formbricks requires the
pgvector extension in PostgreSQL.One-Click Setup Users: Use the pgvector/pgvector:pg15 image instead of postgres:15-alpine to ensure compatibility.Additional Updates
If you previously used organisation-based access control (an enterprise feature) and theDEFAULT_ORGANIZATION_ROLE environment variable, update its value to one of these roles: owner, manager, or member. Read more about the new roles in the documentation.
Steps to Migrate
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly. To run all these steps, please navigate to the Formbricks folder where yourdocker-compose.yml file is located.
- Backup your Database: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database.
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.If you want to store the backup as an *.sql file, remove the
-Fc option. To restore, use psql with an
empty Formbricks database.- If you use an older
docker-compose.ymlfile from the one-click setup, modify it to use thepgvector/pgvector:pg15image instead ofpostgres:15-alpine:
- Pull the latest version of Formbricks:
- Stop the running Formbricks instance and remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
- That’s it! Once the migration is finished, you can access your Formbricks instance at the same URL as before.
v2.6
Formbricks v2.6 introduces advanced logic jumps for surveys, allowing more complex branching logic, including variables, conditions, and more. This release also includes numerous bug fixes, significant performance improvements for website and app surveys, and enhanced stability.This release includes the final step of deprecating server-side action tracking (previously used for segment
filtering by performed actions). The migrations will delete all tracked actions from the database. If you
still need these action records, ensure you export them before upgrading.
Steps to Migrate
This guide is for users who are self-hosting Formbricks using our one-click setup. If you’re using a different setup, you might need to adjust the commands accordingly. To run these steps, navigate to the folder where yourdocker-compose.yml file is located.
- Backup your Database: This is an important step. Make sure to back up your database before proceeding with the upgrade. You can use this command to back up your database:
If you get a “No such container” error, run
docker ps to find your container name, for example,
formbricks_postgres_1.If you prefer storing the backup as an
*.sql file remove the -Fc (custom format) option. In case of a
restore scenario, you will need to use psql then with an empty formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find the Docker network name for your Postgres database, run the command:
- Once the migration is complete, you can access your Formbricks instance at the same URL as before.
v2.5
Formbricks v2.5 lets you visualise responses in a data table format. This release also brings a few bug fixes and performance improvements.This release fixes the inconsistency of CTA and consent question values when a question is skipped. The
value will now be set to an empty string instead of “dismissed” to maintain consistency with other
questions.
Steps to Migrate
This guide is for users self-hosting Formbricks with our one-click setup. If you’re using a different setup, you may need to adjust the commands. To begin, navigate to the folder where yourdocker-compose.yml file is located.
- Backup your Database: This step is important. Please make sure to back up your database before upgrading. You can use the following command to back up your database:
If you run into “No such container”, use
docker ps to find your container name,
e.g.formbricks_postgres_1.If you prefer storing the backup as an
*.sql file remove the -Fc (custom format) option. In case of a
restore scenario you will need to use psql then with an empty formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find your Docker Network name for your Postgres Database, find it using
docker network ls- Once the migration is complete, you can access your Formbricks instance at the same URL as before.
v2.4
Formbricks v2.4 lets you create multiple endings for your surveys and choose which one users see based on logic jumps. This version also includes bug fixes and performance improvements.This release will remove support for advanced targeting (enterprise targeting for app surveys) using actions
(e.g. targeting users who triggered action x 3 times in the last month). Actions can still be used as
triggers, but they will no longer be stored on the server to improve Formbricks’ overall performance.
Steps to Migrate
This guide is for users who are self-hosting Formbricks with our one-click setup. If you’re using a different setup, you may need to adjust the commands accordingly. To follow these steps, go to the folder where yourdocker-compose.yml file is located.
- Backup your Database: This step is important. Please make sure to back up your database before upgrading. You can use the following command to back up your database:
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.If you prefer storing the backup as a
*.sql file, remove the -Fc (custom format) option. In case of a
restore scenario, you will need to use psql then with an empty formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
docker network ls
- That’s it! Once the migration is done, you can access your Formbricks instance at the same URL as before.
Additional Updates
- The
CRON_SECRETenvironment variable is now required to enhance the security of the internal cron APIs. Please ensure that this variable is set in your environment ordocker-compose.yml. You can generate a secure secret usingopenssl rand -hex 32
v2.3
Formbricks v2.3 introduces new colour options for rating questions, enhanced multi-language support for Chinese (Simplified & Traditional), and includes several bug fixes and performance improvements.Steps to Migrate
You only need to run the data migration if you have surveys set up in Chinese (
zh). If you don’t have any
surveys in Chinese, you can skip the migration step.docker-compose.yml file is located.
- Backup your Database: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
If you run into “No such container”, use
docker ps to find your container name, e.g.
formbricks_postgres_1.If you prefer storing the backup as an
*.sql file remove the -Fc (custom format) option. In case of a
restore scenario you will need to use psql then with an empty formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find your Docker Network name for your Postgres Database, find it using
docker network ls- Once the migration is complete, you can access your Formbricks instance at the same URL as before.
Additional Updates
The feature to create short URLs in Formbricks is now deprecated. Existing short URLs will continue to work for now, but it is recommended to use long URLs instead. Support for short URL redirects will be removed in a future release.v2.2
Formbricks v2.2 introduces XM research presets, providing a brand-new product onboarding experience. Our goal is to make user research “obviously easy” across different industries, starting with Software-as-a-Service and E-Commerce.Steps to Migrate
This guide is for users who are self-hosting Formbricks with our one-click setup. If you’re using a different setup, you may need to adjust the commands accordingly. To run all these steps, please navigate to theformbricks folder where your docker-compose.yml file is located.
- Backup your Database: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
If you run into “No such container”, use
docker ps to find your container name, e.g.
formbricks_postgres_1.If you prefer storing the backup as an
*.sql file remove the -Fc (custom format) option. In case of a
restore scenario you will need to use psql then with an empty formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find your Docker Network name for your Postgres Database, find it using
docker network ls- Once the migration is complete, you can access your Formbricks instance at the same URL as before.
Changes in Environment Variables
- The
ONBOARDING_DISABLEDenvironment variable is now deprecated. We have replaced the user onboarding with a product onboarding process that only runs when creating a new product.
v2.1
Formbricks v2.1 introduces more options for creating No-Code Actions and improves the self-hosting experience with a new onboarding process for fresh instances.To improve the user experience and simplify setup for self-hosting instances, we are transitioning to a single organization model. This change allows self-hosts to manage their instance centrally and more easily control access. A new permissions system will also be introduced soon, providing more granular control over projects and resources within an organization.If you’ve previously created multiple organizations, you can still switch between them in the UI, but creating new organizations will no longer be possible.
Steps to Migrate
This guide is for users self-hosting Formbricks using our one-click setup. If you are using a different setup, you may need to adjust the commands accordingly. To follow these steps, please navigate to the Formbricks folder where yourdocker-compose.yml file is located.
- Backup your Database: This is an essential step. Please ensure that you back up your database before proceeding with the upgrade. You can use the following command to back it up:
If you run into “No such container”, use
docker ps to find your container name, e.g.
formbricks_postgres_1.If you prefer storing the backup as a
*.sql file remove the -Fc (custom format) option. In case of a
restore scenario you will need to use psql then with an empty formbricks database.- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers.
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find your Docker network name for the Postgres database, run the following
docker network ls- That’s it! Once the migration is complete, you can now access your Formbricks instance at the same URL as before.
Changes in Environment Variables
- SIGNUP_DISABLED is now deprecated. Self-hosting instances have signup disabled by default, and new users can only be invited by the organization owner or admin.
- DEFAULT_TEAM_ID has been renamed to DEFAULT_ORGANIZATION_ID.
- DEFAULT_TEAM_ROLE has been renamed to DEFAULT_ORGANIZATION_ROLE.
v2.0
Formbricks v2.0 introduces significant features such as Multi-Language Surveys and Advanced Styling for Surveys, aimed at improving the flexibility and appearance of your surveys. Additionally, the release includes several optimisations, bug fixes, and smaller improvements to ensure a smoother user experience. If you are upgrading to Formbricks v2.0, follow this guide to ensure a smooth migration of your existing Formbricks instance without encountering issues or build errors.This upgrade requires a data migration. Please make sure to back up your database before proceeding.
Follow the steps below to upgrade your Formbricks instance to v2.0.
If you’ve used the Formbricks Enterprise Edition with a free beta license key, your instance will be
downgraded to the Community Edition 2.0. You can find all license details on the license
page.
Steps to Migrate
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly. To run all these steps, please navigate to theformbricks folder where your docker-compose.yml file is located.
- Backup your Database: This is an essential step. Please ensure that you back up your database before proceeding with the upgrade. You can use the following command to back it up:
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.*.sql file remove the -Fc (custom format) option. In case of a restore scenario you will need to use psql then with an empty formbricks database.
- Pull the latest version of Formbricks:
- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers with the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find your Docker Network name for your Postgres Database, find it using
docker network ls- That’s it! Once the migration is complete, you can now access your Formbricks instance at the same URL as before.
App Surveys with @formbricks/js
With this upgrade, we now dynamically fetch the package from our API endpoint and have updated the package entry points to support both app and website surveys. This ensures that you always have the latest version of the package (v2.0.0+). Old approach: (v1.6.5)v1.6
Formbricks v1.6 introduces key features like Advanced Targeting, Segmentation, on-the-fly survey triggers, and stability improvements. This update also changes some environment variables. Follow this guide to upgrade your Formbricks instance to v1.6 smoothly.This upgrade requires a data migration. Ensure you back up your database before proceeding. Follow the steps
below to upgrade your Formbricks instance to v1.6.
Steps to Migrate
This guide is for users self-hosting Formbricks with our one-click setup. If you’re using a different setup, adjust the commands accordingly. Navigate to the Formbricks folder containing yourdocker-compose.yml file to run the steps.
- Backup your Database: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
If you run into “No such container”, use
docker ps to find your container name,
e.g. formbricks_postgres_1.If you prefer storing the backup as a
*.sql file remove the -Fc (custom format) option. In case of a
restore scenario you will need to use psql then with an empty formbricks database.- Stop the running Formbricks instance & remove the related containers:
- Restarting the containers will automatically pull the latest version of Formbricks:
- Now let’s migrate the data to the latest schema:
To find your Docker Network name for your Postgres Database, find it using
docker network ps- That’s it! Once the migration is complete, you can now access your Formbricks instance at the same URL as before.
Restoring the database after a failed upgrade
Replace the path to
formbricks_pre_v1.6_<timestamp_of_your_dump_file>.dump with the exact path to your .dump file.This will wipe the database and restore from the
.dump file.App Surveys with @formbricks/js
If you’re using the @formbricks/js package, update it to version ~1.6.5 to access the latest features and improvements.
Currently, the package needs to be pinned to
~1.6.5, see this
issue.Upgrade and pin the client package
Deprecated Environment Variables
v1.2
Formbricks v1.2 introduces new features for Link Surveys and enhances security. However, there are some breaking changes related to environment variables. This guide will help you upgrade your Formbricks instance to v1.2 smoothly.New Environment Variables
Deprecated / Removed Environment Variables
v1.1
Formbricks v1.1 introduces new features and improvements but includes breaking changes with environment variables. This guide will help you upgrade your Formbricks instance to v1.1 without data loss.Renamed Environment Variables
Please note that their values and the logic remains exactly the same. Only the prefix has been deprecated. The other environment variables remain the same as well.
Deprecated Environment Variables
-
NEXT_PUBLIC_VERCEL_URL: Was used as deployment URL fallback (used instead ofWEBAPP_URL), but from v1.1, you can just set theWEBAPP_URLenvironment variable. -
RAILWAY_STATIC_URL: Was used as Railway Static URL (used instead ofWEBAPP_URL), but from v1.1, you can just set theWEBAPP_URLenvironment variable. -
RENDER_EXTERNAL_URL: Was used as an external URL to Render (used instead ofWEBAPP_URL), but from v1.1, you can just set theWEBAPP_URLenvironment variable. -
HEROKU_APP_NAME: Was used to build the App name on a Heroku hosted webapp, but from v1.1, you can just set theWEBAPP_URLenvironment variable. -
NEXT_PUBLIC_WEBAPP_URL: Was used for the same purpose asWEBAPP_URL, but from v1.1, you can just set theWEBAPP_URLenvironment variable. -
PRISMA_GENERATE_DATAPROXY: Was used to tell Prisma that it should generate the runtime for Dataproxy usage. But its officially deprecated now.
Helper Shell Script
For a seamless migration, below is a shell script for your self-hosted instance that will automatically update your environment variables to be compliant with the new naming conventions.Docker & Single Script Setup
Now that these variables can be defined at runtime, you can append them inside yourx-environment in the docker-compose.yml itself.
For a more detailed guide on these environment variables, please refer to the
Environment Variables page.
docker-compose.yml