ConfettiDocs
Migration guides

Migrating to 0.5

Survey rendering now loads from the Confetti CDN by default, with a @opengovsg/confetti/static opt-out, plus provider and proxy changes.

Version 0.5 makes the root @opengovsg/confetti entry load survey rendering from the Confetti CDN at runtime, so new question types reach your users without re-installing the package. A self-contained build of the templates stays at @opengovsg/confetti/static. The release also splits the survey and visibility providers, renames apiBaseUrl to proxyUrl on the templates, and refines how dismissals are recorded.

If you use a drop-in template and never set apiBaseUrl, the only required change is allowing https://confetti.gov.sg in your script-src and style-src (it was already needed in connect-src). Building-block users and self-hosted proxy operators have more to do — see Are you affected?.

The happy path. If you embed a drop-in template from the package root, have no Content-Security-Policy restriction, never set apiBaseUrl, never used ConfettiTrigger, and never passed shouldAutoSubmit, you can upgrade with no code changes. Your existing import '@opengovsg/confetti/confetti.css' keeps working, though it is no longer needed on the root entry.

Update the package

Install 0.5

npm install @opengovsg/confetti@^0.5.0
# or
pnpm add @opengovsg/confetti@^0.5.0
# or
yarn add @opengovsg/confetti@^0.5.0

Choose your entry point

The package root is now CDN-backed and recommended. To keep 0.4's self-contained behaviour with no runtime CDN dependency, import the templates from @opengovsg/confetti/static instead. See Survey rendering now loads from the CDN.

Update your code, CSP, and proxy if needed

Find your group in Are you affected? and apply the relevant changes below.

Are you affected?

What you need to change depends on how you embed the widget.

Minimal changes if you use a drop-in template (EmbeddedConfetti, ModalConfetti, PopoverConfetti, StepperConfetti) from the package root. Allow https://confetti.gov.sg in your CSP, optionally drop the CSS import, and rename apiBaseUrl to proxyUrl only if you set it.

More changes if you use ConfettiTrigger. It has moved to @opengovsg/confetti/static, no longer wraps its children in a survey context, and it records a view each time it opens rather than once at mount.

You must update your code if you import from @opengovsg/confetti/components. useConfetti and ConfettiContext are gone, useSurvey no longer returns isVisible, ConfettiProvider no longer accepts alwaysRenderChildren, and dismiss() no longer tears the widget down.

Cross-cutting: if you run a self-hosted proxy for proxyUrl or apiBaseUrl. The widget now includes the /api path itself, probes availability with a HEAD request, and loads the hosted bundle from the /widget sub-path. Update your proxy accordingly.

Drop-in template changes

Survey rendering now loads from the CDN

The root @opengovsg/confetti entry now exports thin shells that fetch the survey-rendering bundle from https://confetti.gov.sg at runtime. New survey fields and question types reach your users without re-running npm install.

// Root entry (0.5): CDN-backed and auto-updating — no import change needed
import { PopoverConfetti } from '@opengovsg/confetti'

To keep 0.4's behaviour — self-contained, no runtime CDN dependency, no auto-update — import the templates from @opengovsg/confetti/static:

// Opt out: self-contained build, as in 0.4
import '@opengovsg/confetti/confetti.css'

import { PopoverConfetti } from '@opengovsg/confetti/static'

The hosted shells load the bundle and stylesheet from https://confetti.gov.sg. If your site sets a Content-Security-Policy, add https://confetti.gov.sg to script-src and style-src. It was already required in connect-src for the API calls.

The CSS import is now optional on the root entry

Hosted shells inject their stylesheet from the CDN automatically, so the root entry no longer needs a manual CSS import.

Before (0.4):

import '@opengovsg/confetti/confetti.css'

import { PopoverConfetti } from '@opengovsg/confetti'

After (0.5):

import { PopoverConfetti } from '@opengovsg/confetti'

The @opengovsg/confetti/static and @opengovsg/confetti/components surfaces still require import '@opengovsg/confetti/confetti.css'. Only the root entry injects it for you.

apiBaseUrl is now proxyUrl

The templates no longer accept apiBaseUrl. Pass proxyUrl with the origin that serves the proxied Confetti API, which for the hosted shells is also the origin that serves the hosted bundle. The @opengovsg/confetti/components building blocks keep apiBaseUrl.

Before (0.4):

<PopoverConfetti
  surveyId="<your-survey-id>"
  publishableKey="<your-publishable-key>"
  apiBaseUrl="https://your-origin.com"
/>

After (0.5):

<PopoverConfetti
  surveyId="<your-survey-id>"
  publishableKey="<your-publishable-key>"
  proxyUrl="https://your-origin.com"
/>

ConfettiTrigger moved to @opengovsg/confetti/static

ConfettiTrigger is no longer exported from the package root. For a CDN-backed trigger button, use TriggerPopoverConfetti from the root entry:

// CDN-backed trigger button (root)
import { TriggerPopoverConfetti } from '@opengovsg/confetti'

const App = () => (
  <TriggerPopoverConfetti
    surveyId="<your-survey-id>"
    publishableKey="<your-publishable-key>"
  />
)

To keep composing the trigger with a template yourself, import both from @opengovsg/confetti/static. Its children are a render prop: spread the props it provides onto the template. Its behaviour also changed. See ConfettiTrigger no longer provides a survey context.

import '@opengovsg/confetti/confetti.css'

import { ConfettiTrigger, PopoverConfetti } from '@opengovsg/confetti/static'

const App = () => (
  <ConfettiTrigger
    surveyId="<your-survey-id>"
    publishableKey="<your-publishable-key>"
  >
    {(childProps) => <PopoverConfetti {...childProps} />}
  </ConfettiTrigger>
)

shouldAutoSubmit removed from the templates

The popover, stepper, modal, and embedded templates no longer accept shouldAutoSubmit. Each layout now has a single correct behaviour: the popover and stepper auto-submit on completion, while the modal and embedded templates use an explicit submit button. Remove the prop.

Before (0.4):

<PopoverConfetti
  surveyId="<your-survey-id>"
  publishableKey="<your-publishable-key>"
  shouldAutoSubmit
/>

After (0.5):

<PopoverConfetti
  surveyId="<your-survey-id>"
  publishableKey="<your-publishable-key>"
/>

ConfettiController and the Confetti component still accept shouldAutoSubmit for callers building their own survey UI.

Building-block changes (@opengovsg/confetti/components)

These changes affect you only if you import from @opengovsg/confetti/components.

useConfetti splits into two hooks

ConfettiProvider is now composed from SurveyProvider and RespondentVisibilityProvider. ConfettiContext and useConfetti are gone. Read the survey and the submit and dismiss actions from useSurvey, and the resolved respondent from useRespondentVisibility.

Before (0.4):

import { useConfetti } from '@opengovsg/confetti/components'

const { survey, submit, dismiss, isVisible } = useConfetti()

After (0.5):

import {
  useRespondentVisibility,
  useSurvey,
} from '@opengovsg/confetti/components'

const { survey, submit, dismiss } = useSurvey()
const { respondent } = useRespondentVisibility()

isVisible is no longer returned by any hook. RespondentVisibilityProvider now gates rendering itself, so code below it renders only when the survey is visible for the respondent and does not need to branch on isVisible. useRespondentVisibility exposes the resolved respondent. If you previously branched on isVisible, rely on the provider gating the render, or evaluate your own isSurveyVisible predicate.

ConfettiProvider no longer accepts alwaysRenderChildren

ConfettiProviderProps no longer accepts alwaysRenderChildren. Each provider reuses its own ancestor context, so supplying a survey context alone no longer stops ConfettiProvider from resolving a respondent and probing availability.

Hosts that render the building blocks against a preloaded survey must now provide RespondentVisibilityContext alongside SurveyContext, instead of relying on alwaysRenderChildren.

dismiss() only records the event

dismiss() from useSurvey now only records the dismiss event. It no longer tears the widget down, so you are responsible for hiding the widget yourself if you want it to disappear on dismissal.

ConfettiTrigger no longer provides a survey context

In 0.4, ConfettiTrigger wrapped its children in a survey context. In 0.5 it no longer does, so the render prop carries the full survey configuration and you spread it onto the rendered template, which fetches the survey itself. The render prop provides surveyId, publishableKey, proxyUrl, metadata, and options, plus the onDismiss, onFinished, and onClose callbacks. If you relied on the provided context instead of spreading these props, spread them onto your template now.

ConfettiTrigger now fetches the survey and records a view event each time it opens, instead of once at page mount. Pages where the trigger is never opened record no views, and each reopen records an additional view.

ConfettiProps and BaseConfettiProps

The shared template prop types were reorganised. ConfettiProps now matches the full set of props the Confetti component accepts (including children and shouldAutoSubmit), and a new BaseConfettiProps holds the survey config shared by the templates. Both are exported from @opengovsg/confetti/components. Update any type imports that referenced the old shape.

Self-hosted proxy changes

These apply if you serve proxyUrl (templates) or apiBaseUrl (building blocks) from your own origin. For a worked Cloudflare Workers example of the current contract, see Cloudflare Workers proxy.

Proxies must not prepend /api

The widget now includes the /api path component in all proxied API calls. Previously the proxy was expected to prepend /api; it must no longer do so.

Before (0.4):

# Widget requests v1/cfti/... and the proxy prepends /api
GET {proxyUrl}/v1/cfti/:surveyId  ->  forwarded to /api/v1/cfti/:surveyId

After (0.5):

# Widget includes /api itself — forward the request as-is
GET {proxyUrl}/api/v1/cfti/:surveyId

A proxy that still prepends /api after upgrading will produce /api/api/v1/cfti/... and 404. Remove the old prepend.

Forward HEAD requests

The widget now probes survey availability with a HEAD /api/v1/cfti/:surveyId request before rendering, and does not render when the probe fails. Proxies must forward HEAD in addition to GET, POST, and PUT, or the widget will never appear.

Forward the /widget sub-path

The hosted shells load the rendering bundle and stylesheet from the /widget sub-path of the same origin. Proxies serving the hosted shells must forward both /api and /widget.

Content-Security-Policy

If your site sets a CSP, allow the Confetti origin (or your proxy origin) on these directives:

script-src  https://confetti.gov.sg;
style-src   https://confetti.gov.sg;
connect-src https://confetti.gov.sg;

script-src and style-src are new requirements for the hosted shells; connect-src was already required in previous versions.

Dismissal and completion tracking

The dismiss respondent event and the onDismiss callback now fire only when a respondent abandons the survey before submitting — via the close button or by closing the modal. The close-on-finish timer, the thank-you close button, and any close after submission no longer record a dismissal. A new onClose callback fires on every close path for ModalConfetti and PopoverConfetti. The EmbeddedConfetti and StepperConfetti templates never close themselves, so their onClose fires only from the close button on the thank-you screen. ConfettiController gains an onSubmitted callback.

As a result, lastDismissedAt now strictly means the respondent abandoned the survey without responding.

If your isSurveyVisible predicate suppressed the survey based only on lastDismissedAt, also check lastRespondedAt. Respondents who complete the survey no longer set lastDismissedAt, so without this they would see the survey again.

Need help?

Was this page helpful?

On this page