SDK: Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
themeoption oninit()-'light','dark'or'auto'(follows the system color scheme), for popup and embed widgets. The feedback button takes the theme of the survey it opens.classNameoption oninit()- Added to the widget's host next to.ftools-survey, so site CSS can style one widget when several are on the page.- Public host classes for site CSS - Widget hosts carry
.ftools-surveyplus.ftools-popupor.ftools-embed; the feedback button host carries.ftools-feedback-buttonwith the partsbuttonandlogo. Site CSS styles the widget through these hosts with--ft-*variables and::part(). allowScreenshotsoption oninit()-falsehides the screenshot control on a page. The control is also hidden when the survey's dashboard setting turns screenshots off.- Up to 3 screenshots per response, matching the backend limit.
- Translated screenshot button - The "Capture screenshot" label follows
languagelike the other built-in labels. - Right-to-left surveys -
languageset to Arabic, Persian or Hebrew renders the survey right to left.
Changed
- react-feedback-surveys 2.1.0 -
--ft-color-*variables take any CSS color instead of bare HSL channels. Wrap old values inhsl():220 13% 13%becomeshsl(220 13% 13%). Minimum browsers: Chrome and Edge 111, Firefox 120, Safari 16.4. - Screenshots - A capture is the visible viewport only, saved as WebP (JPEG where WebP can't be encoded), without the popup and the feedback button, and named
screenshot-<local date and time>. Only what is on screen is cloned in full, so a capture on a heavy page is much faster. A capture that takes longer than 10 seconds fails. close()on a popup that isn't open does nothing - It used to send a close event and count as the visitor dismissing the survey. A popup that waits in the queue is only removed from the queue.
Fixed
init()metadata on popups -metadatapassed toinit()was sent only with embed surveys. Popups opened by a trigger, byopen()withoutmetadata, or reopened after a close now send it too.- Feedback button after
destroy()- The button stayed on the page and did nothing when the destroyed survey was the last one using it. - Popup queue after
destroy()- Destroying the open popup didn't open the next popup in the queue and didn't bring back the feedback button. - SPA navigation after
destroyAll()- A laterinit()didn't follow URL changes. It now watches the History API again. - Screenshots on Safari and iPhone - Fixed ring shadows and font-relative margins, and the spinner now shows before the capture starts.
[2.7.0] - 2026-09-11
Added
- General feedback survey type -
Survey.typenow supportsgeneral: a survey with no rating scale, text feedback only. It goes straight to the feedback/contact screens with no rating step. - Screenshot attachments - The feedback step now includes an optional "Capture screenshot" control for every survey type (no per-survey opt-out). Nothing is captured until the respondent clicks it; a capture (PNG, up to 5 MB) is attached to the response, and can be removed again before submitting. A failed or oversized capture is dropped silently and never blocks the rest of the submission.
- Localized built-in survey strings - Default button labels, aria-labels, and other built-in UI copy (skip/send buttons, close label, feedback/contact form labels, etc.) are now translated into 50 languages based on
init()'soptions.language. Without it, they stay in English.
Fixed
- Multi-select ("choices") answers sent as an array - A feedback answer with multiple selected choices is now flattened into a single string (joined by
;\n) before being sent to the API, whosetextfield only accepts plain text.
[2.6.0] - 2026-09-02
Added
options.languageoninit()- Accepts an optional BCP-47 language tag (e.g.de,pt-BR), forwarded toPOST surveys/{uuid}/sessions/aslanguage. Lets a host app override the visitor'sAccept-Languageheader when it knows their locale but the browser doesn't reflect it (e.g. an in-app language switch). The SDK doesn't validate or transform the value: it's forwarded as-is; the backend resolves it (whole tag, then falling back to its base subtag) and silently ignores a tag it can't resolve.textButtonSkipLabelfrom API - The feedback screen's skip-button label was hardcoded to'Skip'pending backend support.Survey.textButtonSkipLabelis now read from the API response and passed through to the widget.
[2.5.0] - 2026-08-11
Changed
- User attribution moved to session init -
init()'suserId/userEmailnow travel in thesessions/request body instead ofresponses/, matching the backend attaching attribution to the session rather than the response. Identity learned after the session already started (an email typed on the contact screen) is now attached via the newPATCH surveys/{uuid}/sessions/{sessionUuid}/(patchSurveySession()) instead of riding along with the response submission. - Responses are append-only -
POST responses/no longer upserts by session: the backend now creates a newSurveyResponserow on every call. The SDK nowPOSTs at most once per session, storing the returned responseuuidon the widget, and appends any later step (feedback text, then a collected email) via the newPATCH surveys/{uuid}/responses/{responseUuid}/(patchSurveyResponse()) instead of posting again.
Added
Survey.allowMultipleResponses- When a survey has this flag on, the widget's already-answered gate no longer suppresses the popup after a response, and the SDK clears the persisted session after a response so the next visit starts fresh instead of resuming the answered one, letting respondents submit again.userId/userEmaildocumented ininit()- Added to the README's parameter reference (they've been accepted since 2.1.0, just weren't documented there).
Fixed
- Text-only submissions no longer leaked past the already-answered gate -
isAnsweredOrClosed()previously only checkeduserSubmittedScore; a text-only response (no score) wasn't counted as "answered," so the popup could reappear. It now also checksuserSubmittedText. - Reopening an already-answered widget could silently drop the new score - a widget reopened after already producing a response (
allowMultipleResponsesre-arming a trigger/feedback button, or an explicitforce: truereopen) kept the prior round's response identity attached. The nextsend()treated it as already created and appended viaPATCH, which doesn't acceptscore, so the new answer was recorded nowhere, whileonSubmitstill fired as if it had landed.open()now clears the prior round's response state before rendering, so a repeat viewing always posts a fresh response. Internally,Widget.responseUuid/userSubmittedScore/userSubmittedTextare now oneWidget.responseobject, so they can no longer drift out of sync. - Contact-screen email missing from
onSubmit- Submitting the contact screen's email resends the score/text and re-invokes the publiconSubmitcallback (see "Responses are append-only" above), but the callback payload never carried theemailitself, so integrators had no way to tell that submission apart from the previous one.WidgetEventCallbacks.onSubmitnow includesemail?: string, andWidgetManager.send()passes it through.
Removed
docs/framework guides - Dropped the unlinked per-framework snippets (ai.md,html.md,nextjs.md,nuxt3.md,php.md,react.md,shopify.md,vue3.md). They weren't referenced fromREADME.mdor anywhere else, and had gone stale since the 2.3.0apiKey→publishableKeyrename. The current framework guides live in the SDK docs and in the per-framework snippets of the dashboard's Embed tab.
[2.4.0] - 2026-08-05
Fixed
- Multiple widgets with the same surveyId no longer silently corrupt state - Calling
init()a second time for asurveyIdthat already has an active widget (popup or embed, any container) is now hard-refused with alogger.errorinstead of silently sharing/overwriting the first instance's state. Previously a second embed container with the samesurveyIdcould stay silently empty, or a popup/embed pair with the samesurveyIdcould contaminate each other's state (e.g. anembeddedflag bleeding across instances). Calldestroy()first to free thesurveyIdfor a freshinit(). - Widgets bundle built as IIFE -
dist/widgets.jsis now built as an IIFE instead of an ES module. The SDK is loaded via a classic<script src>tag, but the ES module build's bundler-preamble variables (e,t,n,r,i,a,o,s) becamewindowproperties in that context, colliding with and overwriting same-named globals on third-party host pages. - Embed
channeldefault - Embed-mode widgets now defaultchanneltoweb-inlineinstead ofnull, which previously caused the backend to fall back to a generic API channel.
[2.3.0] - 2026-07-19
Changed
apiKeyrenamed topublishableKey- Theinit()option is now namedpublishableKeyto match the parameter name expected by the backend. Only the publicinit()option changed; the internalWidget/API plumbing still usesapiKey.
[2.2.0] - 2026-07-18
Added
- Contact (email collection) screen - An optional screen shown after the rating/feedback step, before the thank-you screen, controlled by
Survey.collectContact. Copy is configurable viacontactQuestion,contactSubtext,contactButtonSendLabel, andcontactButtonSkipLabel. The SDK is the sole decider of whether it's skipped: only auserEmailalready supplied oninit()skips it. A knownuserIdalone does not, since a known account ID doesn't guarantee an email is on file. A submitted email is sent topostSurveyResponse()alongside the score/text already collected earlier in the flow.
Fixed
- Embed title centering on narrow hosts - In embed (inline) mode, the survey title is now centered once the Surface's own width narrows past a breakpoint, using a CSS container query scoped to the Surface. Previously the title's alignment only ever responded to the library's viewport-based media queries, so it never adapted to a widget squeezed into a narrow host container on an otherwise wide viewport.
- Feedback skip now recorded - Skipping the feedback follow-up screen previously dropped the response silently; it now still submits so the backend has a response for that session.
Changed
textButtonLabelrenamed totextButtonSendLabel- Aligns the feedback screen's send-button prop with the naming used by the new contact screen (contactButtonSendLabel).- Base typography - Shadow DOM base
font-size/line-heightincreased from14px/1.4to16px/1.5. - Dependencies - Bumped
react-feedback-surveysto 1.7.0.
[2.1.0] - 2026-07-13
Added
- User attribution on init -
init()now acceptsuserIdanduserEmailalongsideapiKey, forwarded as attribution fields on survey response submission (the only endpoint the API accepts them on). - Flat metadata type -
metadataon survey options and API payloads is now a flat key-value shape instead of nested objects. - Typed API endpoint wrappers -
postSurveySession(),postSurveyEvent(), andpostSurveyResponse()insrc/lib/api.tsreplace rawsendApiRequest()call sites insurvey.ts/widget.tsx, each owning its request-body interface so a wrong field or endpoint is caught at compile time. - Reserved
page_urlmetadata key - Every survey response now automatically includesmetadata.page_urlset to the current page URL, regardless of whether the caller passesmetadata. If a caller supplies their ownpage_url, the SDK overwrites it and logs awarn.
Fixed
- Embed metadata propagation -
metadatapassed viainit()was only stored on the widget in popup mode, so embed widgets always submittedmetadata: undefined; it now propagates correctly to the embed response payload. - Route wildcard matching -
matchRoutes()previously only expanded the first*in a pattern, so patterns like/shop/*/item/*failed to match beyond the first segment. - Embed readability on themed pages - Embed widgets are now wrapped in a
Surfaceso the background stays readable regardless of the host page's theme.
Changed
- Powered-by branding - Logo and text colors in the "Powered by" attribution now track the survey theme.
channelscoping -channelis now sent only on survey response requests, and omitted from session and event requests.- Dependencies - Bumped
react-feedback-surveysto 1.6.1.
[2.0.0] - 2026-06-22
Added
apiKeyrequired on init -InitOptionsnow requiresapiKey: string(a per-survey publishable key withpk_prefix). Omitting it causes an early error return without making any API calls. The key is stored on theWidgetand forwarded asAuthorization: Bearer <apiKey>on every subsequent request.- Embed (inline) mode - Surveys can now be rendered directly inside a host-page container by passing
root: '#selector'orroot: HTMLElementtoinit(). Embed widgets render immediately, have no close button, survive URL changes, and support multiple simultaneous instances (one per container). The container is automatically cleaned up viaMutationObserverif it is removed from the DOM. - Widget lifecycle callbacks -
on(surveyUuid, { onRender, onSubmit, onClose })lets callers react to widget lifecycle events.onRenderfires when the widget mounts (immediately if already mounted),onSubmitreceives the submitted score and text, andonClosefires when a popup is dismissed.onCloseis a no-op in embed mode. channeloption -init()andopen()now accept achannelvalue (web-popup,web-inline,direct-link,qr-code,email) that is forwarded to the backend. Defaults toweb-popupfor popup mode andweb-inlinefor embed mode.- Command dispatcher -
window.ftools()now routes through an explicit allowlist (init,on,off,open,close,destroy,destroyAll), blocking unknown commands so that third-party scripts or XSS cannot call private SDK methods or prototype properties. - Integration docs - Added platform-specific integration guides for React, Next.js, Vue 3, Nuxt 3, plain HTML, PHP, Shopify, and AI-driven surfaces.
Changed
- BREAKING:
init()now takes a single options object -ftools('init', 'uuid', { ... })is replaced byftools('init', { surveyId: 'uuid', apiKey: 'pk_...', ... }). The survey identifier is now thesurveyIdfield inside the options object rather than a positional argument. All other commands (open,close,destroy,on,off) keep their existing positional signature. - BREAKING: API v2 endpoints - All backend calls now target
/api/public/v2/.surveyUuidhas been moved from the request body to the URL path. Endpoint mapping:surveys/init/→surveys/{uuid}/sessions/,surveys/event/→surveys/{uuid}/events/,surveys/respond/→surveys/{uuid}/responses/. The score field in the response submission was renamed fromvaluetoscore. - BREAKING: Session tracking moved from cookies to
localStorage-sessionUuidis now read fromlocalStoragebefore eachinitrequest and saved back after a successful response. The key format isft_<surveyUuid>. Previously, session continuity relied on browser cookies set by the server. - Removed
credentials: 'include'- API requests no longer send cookies. All session state is handled explicitly viasessionUuidin the request body. OpenWidgetOptionsreplaced byOpenOptions/InitOptions-OpenOptionsadds theforce,metadata, andchannelfields toopen();InitOptionsextends it withrootfor embed mode. The oldOpenWidgetOptionstype is removed.
[1.5.1] - 2026-06-18
Changed
- Dependencies - Updated all packages to latest stable versions.
- Production logs - Disabled verbose logging in production builds to reduce noise in end-user consoles.
[1.5.0] - 2025-12-26
Changed
- BREAKING:
uidrenamed tosurveyUuid-Widget.uidis replaced byWidget.surveyUuidacross all types, managers, and API payloads. Update any direct references towidget.uidin callbacks or custom integrations. sessionUuidadded to API requests - All API calls (surveys/init/,surveys/event/,surveys/respond/) now includesessionUuidin the request body for explicit session tracking alongsidesurveyUuid.
Fixed
- Scroll trigger - Fixed an issue where scroll-based triggers did not fire correctly in certain scroll container configurations.
Added
- Widget style preloading - CSS is now preloaded to reduce flash of unstyled content on first render.
[1.2.0] - 2025-12-23
Added
- Comprehensive test suite - 214 unit tests covering the SDK core, all managers, library utilities, and components using Vitest and Testing Library.
- New survey types - Added support for
csat_2(2-point) andnps_10(10-point NPS) survey types. - CES 7 support - Added
ces_7Customer Effort Score survey type. - CSAT 5 scale visualization -
csat_5surveys now supportnumbers,stars, andemojiscale styles in addition to the default. - Feedback button - Optional floating feedback button that users can click to trigger a survey on demand. Enabled via the
feedbackButtonsurvey setting. - Shadow DOM style isolation - Widget components are now rendered inside a Shadow DOM to prevent host-page styles from leaking into surveys.
react-feedback-surveysUI library - Migrated survey rendering to the sharedreact-feedback-surveyscomponent library for consistent styling across products.- Cloudflare cache purging - CI/CD pipeline now automatically purges the CDN cache after each publish.
- CI/CD test job - GitLab pipeline now includes a dedicated test stage that runs on every push.
Changed
- SDK refactoring - Internal architecture cleaned up to the manager pattern:
SurveyManager,TriggerManager,WidgetManager,FeedbackManager,LocationManager. - Model field renames - Several internal API response fields renamed for consistency with backend conventions.
- Dependencies - Updated all packages to latest stable versions.
[1.0.0] - 2025-01-14
Added
- Delay and scroll triggers - Surveys can now be configured to appear after a set time delay or after the user scrolls past a percentage of the page.
- Widgets queue - Multiple surveys can be queued; the next one opens automatically after the previous is closed.
- Embed mode - Surveys can be rendered inline into a specific DOM container via the
rootoption, in addition to the default popup mode. Powered bylabel - Branded attribution label shown below popup surveys.- Manual tracking mode - Surveys with
manualTracking: trueare opened programmatically viaopen()without automatic triggers. - Open/close event API - SDK now sends
survey_openedandsurvey_closedevents to the backend.
Changed
- Survey UI redesign - Updated widget visuals, icons, and animations.
- CSS units - Switched from
remtopxfor consistent sizing regardless of host-page root font size. - Publish pipeline - Updated S3/Digital Ocean Spaces publish script and CI job images.
[0.2.3] - 2024-07-26
Fixed
- Type safety - Stronger TypeScript type checks across the codebase; various runtime edge cases hardened.
[0.2.2] - 2024-07-23
Fixed
- Form field event propagation - Added
stopPropagationon keydown events inside survey input fields to prevent host-page keyboard handlers from interfering.
[0.2.1] - 2024-07-23
Fixed
- Metadata payload - Fixed incorrect serialization of metadata in the survey response request body.
[0.2.0] - 2024-07-19
Added
- Choices response type - Surveys can now present a set of predefined answer options (checkboxes) as the follow-up feedback step.
Changed
- Naming - Renamed
widget→surveyandanswer→survey responsethroughout the codebase and API payloads for clarity.
Fixed
- Error handling - Added proper error states when the API returns a failed or missing survey.
[0.1.0] - 2024-06-24
Added
- Initial release - Embeddable feedback widget SDK built with React, TypeScript, and Vite. Compiles to a single JS file served via CDN.
- CSAT 5 survey - Initial survey type with emoji scale visualization.
- CI/CD pipeline - GitLab CI with build and publish stages. Artifacts uploaded to Digital Ocean Spaces and served via CDN.
- S3 publish script -
publish.pyfor uploading build artifacts with correct Content-Type headers and public-read ACL.