Jump to

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

  • theme option on init() - '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.
  • className option on init() - 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-survey plus .ftools-popup or .ftools-embed; the feedback button host carries .ftools-feedback-button with the parts button and logo. Site CSS styles the widget through these hosts with --ft-* variables and ::part().
  • allowScreenshots option on init() - false hides 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 language like the other built-in labels.
  • Right-to-left surveys - language set 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 in hsl(): 220 13% 13% becomes hsl(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 - metadata passed to init() was sent only with embed surveys. Popups opened by a trigger, by open() without metadata, 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 later init() 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.type now supports general: 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()'s options.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, whose text field only accepts plain text.

[2.6.0] - 2026-09-02

Added

  • options.language on init() - Accepts an optional BCP-47 language tag (e.g. de, pt-BR), forwarded to POST surveys/{uuid}/sessions/ as language. Lets a host app override the visitor's Accept-Language header 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.
  • textButtonSkipLabel from API - The feedback screen's skip-button label was hardcoded to 'Skip' pending backend support. Survey.textButtonSkipLabel is 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()'s userId/userEmail now travel in the sessions/ request body instead of responses/, 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 new PATCH 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 new SurveyResponse row on every call. The SDK now POSTs at most once per session, storing the returned response uuid on the widget, and appends any later step (feedback text, then a collected email) via the new PATCH 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/userEmail documented in init() - 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 checked userSubmittedScore; a text-only response (no score) wasn't counted as "answered," so the popup could reappear. It now also checks userSubmittedText.
  • Reopening an already-answered widget could silently drop the new score - a widget reopened after already producing a response (allowMultipleResponses re-arming a trigger/feedback button, or an explicit force: true reopen) kept the prior round's response identity attached. The next send() treated it as already created and appended via PATCH, which doesn't accept score, so the new answer was recorded nowhere, while onSubmit still 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/userSubmittedText are now one Widget.response object, 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 public onSubmit callback (see "Responses are append-only" above), but the callback payload never carried the email itself, so integrators had no way to tell that submission apart from the previous one. WidgetEventCallbacks.onSubmit now includes email?: string, and WidgetManager.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 from README.md or anywhere else, and had gone stale since the 2.3.0 apiKey → publishableKey rename. 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 a surveyId that already has an active widget (popup or embed, any container) is now hard-refused with a logger.error instead of silently sharing/overwriting the first instance's state. Previously a second embed container with the same surveyId could stay silently empty, or a popup/embed pair with the same surveyId could contaminate each other's state (e.g. an embedded flag bleeding across instances). Call destroy() first to free the surveyId for a fresh init().
  • Widgets bundle built as IIFE - dist/widgets.js is 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) became window properties in that context, colliding with and overwriting same-named globals on third-party host pages.
  • Embed channel default - Embed-mode widgets now default channel to web-inline instead of null, which previously caused the backend to fall back to a generic API channel.

[2.3.0] - 2026-07-19

Changed

  • apiKey renamed to publishableKey - The init() option is now named publishableKey to match the parameter name expected by the backend. Only the public init() option changed; the internal Widget/API plumbing still uses apiKey.

[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 via contactQuestion, contactSubtext, contactButtonSendLabel, and contactButtonSkipLabel. The SDK is the sole decider of whether it's skipped: only a userEmail already supplied on init() skips it. A known userId alone does not, since a known account ID doesn't guarantee an email is on file. A submitted email is sent to postSurveyResponse() 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

  • textButtonLabel renamed to textButtonSendLabel - 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-height increased from 14px/1.4 to 16px/1.5.
  • Dependencies - Bumped react-feedback-surveys to 1.7.0.

[2.1.0] - 2026-07-13

Added

  • User attribution on init - init() now accepts userId and userEmail alongside apiKey, forwarded as attribution fields on survey response submission (the only endpoint the API accepts them on).
  • Flat metadata type - metadata on survey options and API payloads is now a flat key-value shape instead of nested objects.
  • Typed API endpoint wrappers - postSurveySession(), postSurveyEvent(), and postSurveyResponse() in src/lib/api.ts replace raw sendApiRequest() call sites in survey.ts/widget.tsx, each owning its request-body interface so a wrong field or endpoint is caught at compile time.
  • Reserved page_url metadata key - Every survey response now automatically includes metadata.page_url set to the current page URL, regardless of whether the caller passes metadata. If a caller supplies their own page_url, the SDK overwrites it and logs a warn.

Fixed

  • Embed metadata propagation - metadata passed via init() was only stored on the widget in popup mode, so embed widgets always submitted metadata: 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 Surface so 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.
  • channel scoping - channel is now sent only on survey response requests, and omitted from session and event requests.
  • Dependencies - Bumped react-feedback-surveys to 1.6.1.

[2.0.0] - 2026-06-22

Added

  • apiKey required on init - InitOptions now requires apiKey: string (a per-survey publishable key with pk_ prefix). Omitting it causes an early error return without making any API calls. The key is stored on the Widget and forwarded as Authorization: Bearer <apiKey> on every subsequent request.
  • Embed (inline) mode - Surveys can now be rendered directly inside a host-page container by passing root: '#selector' or root: HTMLElement to init(). 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 via MutationObserver if it is removed from the DOM.
  • Widget lifecycle callbacks - on(surveyUuid, { onRender, onSubmit, onClose }) lets callers react to widget lifecycle events. onRender fires when the widget mounts (immediately if already mounted), onSubmit receives the submitted score and text, and onClose fires when a popup is dismissed. onClose is a no-op in embed mode.
  • channel option - init() and open() now accept a channel value (web-popup, web-inline, direct-link, qr-code, email) that is forwarded to the backend. Defaults to web-popup for popup mode and web-inline for 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 by ftools('init', { surveyId: 'uuid', apiKey: 'pk_...', ... }). The survey identifier is now the surveyId field 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/. surveyUuid has 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 from value to score.
  • BREAKING: Session tracking moved from cookies to localStorage - sessionUuid is now read from localStorage before each init request and saved back after a successful response. The key format is ft_<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 via sessionUuid in the request body.
  • OpenWidgetOptions replaced by OpenOptions / InitOptions - OpenOptions adds the force, metadata, and channel fields to open(); InitOptions extends it with root for embed mode. The old OpenWidgetOptions type 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: uid renamed to surveyUuid - Widget.uid is replaced by Widget.surveyUuid across all types, managers, and API payloads. Update any direct references to widget.uid in callbacks or custom integrations.
  • sessionUuid added to API requests - All API calls (surveys/init/, surveys/event/, surveys/respond/) now include sessionUuid in the request body for explicit session tracking alongside surveyUuid.

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) and nps_10 (10-point NPS) survey types.
  • CES 7 support - Added ces_7 Customer Effort Score survey type.
  • CSAT 5 scale visualization - csat_5 surveys now support numbers, stars, and emoji scale 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 feedbackButton survey 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-surveys UI library - Migrated survey rendering to the shared react-feedback-surveys component 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 root option, in addition to the default popup mode.
  • Powered by label - Branded attribution label shown below popup surveys.
  • Manual tracking mode - Surveys with manualTracking: true are opened programmatically via open() without automatic triggers.
  • Open/close event API - SDK now sends survey_opened and survey_closed events to the backend.

Changed

  • Survey UI redesign - Updated widget visuals, icons, and animations.
  • CSS units - Switched from rem to px for 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 stopPropagation on 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 → survey and answer → survey response throughout 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.py for uploading build artifacts with correct Content-Type headers and public-read ACL.