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.0Choose 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/:surveyIdAfter (0.5):
# Widget includes /api itself — forward the request as-is
GET {proxyUrl}/api/v1/cfti/:surveyIdA 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?
- See when to show the survey for examples that use the building blocks.
- Email confetti@open.gov.sg
- Slack #confetti (internal use only)
Was this page helpful?