Analytics & GTM
iConfigure records configurator sessions in the Manager dashboard when tracking is enabled on a product. On the host page (script inject), the tracking store also pushes events to window.dataLayer when that array exists — Google Tag Manager listens to those pushes.
Implementation source: packages/stores/tracking.js in the v2 monorepo.
Enable tracking
- Open Manager → your product → Submission & tracking.
- Enable Enable tracking to get insights (
tracking_enabledon the product). - Save and publish the product.
On load, _StartProductTracking in the form store starts a session and inserts a row in the Supabase tracking table. The same data appears on Dashboard and under Data → Export (dataset Tracking).
If the insert fails, tracking is disabled for that session (no further ic_update / ic_finish events; ic_step and ic_form_show still fire).
Tracking does not start when the configurator is opened with mode=debug or mode=manager in the URL.
iFrame vs inject
| Embed mode | Host-page GTM |
|---|---|
| iFrame | Configurator JS runs in the iframe's own window. Host-page GTM does not see dataLayer.push calls. |
| Script inject | Configurator runs on the host window — use this for GTM / GA4 on your site. |
The code does not check embed mode explicitly; it only checks typeof window.dataLayer !== 'undefined'. Inject works because configurator and GTM share the same window.
Prerequisites on the host page
- Tracking enabled on the product in Manager.
window.dataLayermust exist before the session starts — normally created by the GTM container snippet. Minimal alternative:
<script>window.dataLayer = window.dataLayer || [];</script>- Load GTM before
inject.iife.jsandinjectApp(). See Script inject.
GTM itself is optional; any script that defines dataLayer is enough for the pushes to land somewhere you can read.
dataLayer events (verified)
| Event | When | Supabase |
|---|---|---|
ic_setup | Session starts after product load | Insert |
ic_step | On every configurator step change | Update |
ic_form_show | The submission (quote) form becomes visible | — |
ic_update | Feature change recorded + debounce (see below) | Update |
ic_finish | Successful submit (info step / quotation) | Update |
Additionally, every successful submit pushes the legacy offerte_submit event (v1 format, flat offer fields) — independent of tracking_enabled. In the configurator's submission form it fires the moment the submit button is pressed; in the cart/showroom flow and the standalone contact form it fires as soon as the quote has been saved. In the configurator and cart/showroom flows the host page only receives the quotation/finished postMessage at least 1 second after the event, so the redirect to the thank-you page never outraces it.
The ic_step and ic_form_show events use a flat shape (no data wrapper) so they are directly usable in GTM:
{ event: 'ic_step', step: <1-based number>, step_name: '<step name>', product_id, company_id, dealer_id, total_price }
{ event: 'ic_form_show', step: <1-based number>, product_id, company_id, dealer_id, total_price }step is always the human-facing 1-based step number (UI step 2 → step: 2). The submission form is not a step value: the moment the form becomes visible is tracked by the separate ic_form_show event — fired on the output-button click that reveals the form (e.g. "vraag offerte aan"), or when the form is shown directly: on entering the info_form step when the product has no output buttons, or when every output button asks for contact details (the button screen is skipped). It fires on every reveal, so deduplicate in GTM if you need uniques.
ic_step and ic_form_show are always pushed (even without tracking_enabled); the matching updates[] write to Supabase only happens during an active tracking session for ic_step, and never for ic_form_show (dataLayer-only). The product_id / company_id / dealer_id fields are only populated during an active tracking session — without tracking_enabled they are null.
Each push has the shape:
{ event: 'ic_setup' | 'ic_update' | 'ic_finish', data: { /* tracking object */ } }data object (all events)
| Field | Description |
|---|---|
user.device | e.g. smartphone, desktop |
user.browser | e.g. Chrome, Mobile Safari |
user.os | e.g. iOS, Windows |
product_id | Product UUID |
company_id | Company UUID |
dealer_id | Dealer UUID or null |
email | Empty until submit; the visitor's email on ic_finish |
finished | false until submit; true on ic_finish |
time.loading | Ms from navigation start (approx.) |
time.total | Ms from session start (set on update/finish) |
updates[] | Step, feature ID, value, timestamp, timeSpentOnFeature, totalPrice |
On ic_finish the data object additionally contains phone, first_name and last_name when the visitor filled them in (dataLayer only, not stored in the Manager).
ic_setup
Fired once in trackingSetup, immediately before the Supabase insert.
ic_update — debounce
Fired from trackingUpdate when both are true:
- A feature change was recorded in
updates[]. lastChange.time - tracking.lastSync > 3000(3 second gap).
So it is not a fixed 3-second interval timer; rapid clicks may batch into fewer pushes.
Which interactions call trackingUpdate today:
| UI path | ic_update |
|---|---|
Number inputs (FormFeatureNumberBox) | Yes |
Radio/checkbox via legacy SelectInput (form app) — list, image buttons, round buttons, carousel, image list, image slide and selection slider | Yes |
| Other display widgets (dropdown, switch, color picker, text inputs) | Not wired yet |
ic_setup, ic_step and ic_finish still fire regardless. For feature-level GTM on dropdown/switch configurators, use ic_step for progress and ic_finish as the conversion event until those widgets emit updates.
ic_finish
Fired in trackingFinish when submit completes successfully (useSubmitQuote, cart flow, standalone contact form). Adds an update with changedFeature: 'info_form' and sets finished: true.
GTM setup example
1. Container snippet (head)
Standard GTM install — creates dataLayer.
2. Custom Event trigger
| Setting | Value |
|---|---|
| Trigger type | Custom Event |
| Event name | ic_finish (conversion), ic_form_show (form view / funnel), or ic_update (engagement) |
Repeat for ic_setup if you need a session-start tag.
3. Data Layer Variables
Create Data Layer Variable types in GTM:
| Variable name | Data Layer Variable Name |
|---|---|
| DL - product id | data.product_id |
| DL - finished | data.finished |
| DL - total time | data.time.total |
For the last feature changed, use a Custom JavaScript variable or map from data.updates in a tag template.
4. GA4 event tag (example)
| Setting | Value |
|---|---|
| Tag type | Google Analytics: GA4 Event |
| Event name | iconfigure_finish |
| Trigger | Custom Event ic_finish |
Event parameter product_id | GTM variable DL - product id |
Debug checklist
- Product has tracking enabled in Manager.
- Page uses script inject, not iframe.
- GTM Preview open;
dataLayerexists before configurator loads. - Console after load:
window.dataLayer.filter((e) => e.event?.startsWith('ic_'))- Change a number feature → expect
ic_updateafter debounce. - Complete submit → expect
ic_finishwithdata.finished === true.
Manager analytics vs GTM
| Source | Use case |
|---|---|
| Manager Dashboard | Funnel, conversion, step drop-off (Supabase tracking) |
| Data → Export (Tracking) | Raw export (CSV or JSON) |
| GTM / GA4 | Marketing tags on your domain |
See Marketing dashboard for the Manager side.