Funnel
Funnel is the dispatcher at the centre of the library. You create one instance, register your
plugins, initialize them with their IDs, and then call track(). Every track() call fans out
to all registered plugins, and each plugin failure is isolated so one broken plugin never
blocks the others.
import { Funnel } from "@sunwjy/funnel-client";// or: import { Funnel } from "@sunwjy/funnel-core";Both @sunwjy/funnel-client and @sunwjy/funnel-core export Funnel; the client package
re-exports it for convenience so you can import everything from one place.
FunnelConfig
Section titled “FunnelConfig”The object you pass to new Funnel(config).
| Field | Type | Required | Description |
|---|---|---|---|
plugins |
FunnelPlugin[] |
Yes | List of plugins to register. |
debug |
boolean |
No | When true, prints debug logs to the console. Defaults to false. |
onError |
(error: unknown, context: FunnelErrorContext) => void |
No | Called when any plugin throws during a lifecycle method. Replaces the default console.error. |
FunnelErrorContext
Section titled “FunnelErrorContext”Passed as the second argument to onError so you can tell which plugin and lifecycle phase
failed.
| Field | Type | Description |
|---|---|---|
plugin |
string |
Plugin name where the error originated. |
phase |
"initialize" | "track" | "setUser" | "resetUser" | "setConsent" |
Lifecycle method that threw. |
eventName |
EventName (optional) |
Event name, present only when phase === "track". |
Errors are always isolated: one plugin throwing never blocks the others. Providing onError
just lets you forward those errors somewhere (Sentry, Bugsnag, etc.) instead of logging them.
import { Funnel } from "@sunwjy/funnel-client";import { createGA4Plugin } from "@sunwjy/funnel-client/ga4";import * as Sentry from "@sentry/browser";
const funnel = new Funnel({ plugins: [createGA4Plugin()], debug: true, onError(error, context) { Sentry.captureException(error, { extra: { ...context } }); },});new Funnel(config)
Section titled “new Funnel(config)”Creates a new instance. The constructor only stores configuration. No plugin is touched until
you call initialize().
const funnel = new Funnel({ plugins: [createGA4Plugin(), createMetaPixelPlugin()],});initialize(pluginConfigs?)
Section titled “initialize(pluginConfigs?)”initialize(pluginConfigs?: Record<string, Record<string, unknown>>): voidInitializes every registered plugin. pluginConfigs is a map keyed by plugin name
(plugin.name). The value for each key is passed to that plugin’s initialize(). A plugin
with no entry receives an empty object ({}).
funnel.initialize({ ga4: { measurementId: "G-XXXXXXXXXX" }, "meta-pixel": { pixelId: "1234567890" },});Key behaviours:
- Idempotent per plugin. A plugin that has already been initialized is skipped on subsequent calls. This makes repeated calls safe during hot-module-reload / dev reloads.
- Order of effects after init. Any consent stored via
setConsentis applied first, then any stored user identity viasetUser, then the pre-init event queue is replayed (seetrack).
track(eventName, params)
Section titled “track(eventName, params)”track<E extends EventName>(eventName: E, params: EventMap[E]): voidSends one event to every registered plugin. eventName must be a key of
EventMap and params is checked against that event’s parameter type.
A fresh EventContext (carrying a unique eventId) is generated
for each call and passed to every plugin, so all platforms see the same event identity.
funnel.track("page_view", { page_title: "Home" });
funnel.track("purchase", { transaction_id: "T-1", currency: "KRW", value: 29000,});Queuing before initialize
Section titled “Queuing before initialize”You can call track() before initialize(). Such events are queued and replayed in order
once initialization completes:
- The queue holds up to 100 events. Beyond that, new events are dropped with a console warning.
- The
eventIdis generated at call time, so a queued event keeps its original identity when it is later replayed.
setUser(properties)
Section titled “setUser(properties)”setUser(properties: UserProperties): voidSets user identity and properties across all plugins that implement setUser. Uses the GA4
user-properties model as the canonical format. If called before initialize(), the properties
are stored and applied to each plugin during initialization (before the queued events replay).
UserProperties fields:
| Field | Type | Description |
|---|---|---|
user_id |
string (optional) |
Stable, cross-device user identifier (GA4 user_id). |
email |
string (optional) |
Email address. Used for advanced matching on Meta, TikTok, X, Google Ads. |
phone_number |
string (optional) |
Phone number in E.164 format. Used for advanced matching. |
first_name |
string (optional) |
First name. Used for Meta Advanced Matching and Google Enhanced Conversions. |
last_name |
string (optional) |
Last name. Used for Meta Advanced Matching and Google Enhanced Conversions. |
[key: string] |
unknown |
Arbitrary custom user properties. |
funnel.setUser({ user_id: "u-123", email: "user@example.com" });resetUser()
Section titled “resetUser()”resetUser(): voidClears user identity across all plugins (logout scenario). Clears the stored user properties and
calls resetUser() on each plugin that implements it.
funnel.resetUser();setConsent(state)
Section titled “setConsent(state)”setConsent(state: ConsentState): voidUpdates the user’s consent state across all plugins that implement setConsent. Accepts
partial updates: the given signals are merged into the last known state, and the full
accumulated ConsentState is forwarded to each plugin. If called before initialize(), the
state is stored and applied during initialization, before user identity and queued events.
ConsentState follows the Google Consent Mode v2 signal model. Each field is a ConsentStatus
("granted" | "denied"), and all fields are optional.
| Field | Type | Description |
|---|---|---|
ad_storage |
ConsentStatus (optional) |
Cookies/identifiers for advertising purposes. |
analytics_storage |
ConsentStatus (optional) |
Cookies/identifiers for analytics purposes. |
ad_user_data |
ConsentStatus (optional) |
Sending user data to Google for advertising (Consent Mode v2). |
ad_personalization |
ConsentStatus (optional) |
Personalized advertising (Consent Mode v2). |
funnel.setConsent({ analytics_storage: "granted" });// later, merged into the previous statefunnel.setConsent({ ad_storage: "granted", ad_user_data: "granted" });See also
Section titled “See also”EventMap: the event names and parameterstrack()accepts.FunnelPlugin: the interface each registered plugin implements.EventContext: the per-event context generated on eachtrack().