Configuration
The module uses a single YAML configuration file: includes/custom/modules/ga4_attribution/config.yaml.
It ships disabled. At minimum, set enabled: true, define your conversions, and set journey.closing_conversions to start producing journeys, models and reports.
Minimal example
version: 1
enabled: true
dependencies:
- ga4
touchpoint:
mode: session
conversions:
- name: purchase
eventFilter: purchase
revenue_field: ecommerce.purchase_revenue
journey:
closing_conversions:
- purchase
user_identifier: user_pseudo_id
channel_grouping: default_channel_group
attribution_models:
- { name: first_touch, type: first_touch }
- { name: last_touch, type: last_touch }
- { name: linear, type: linear }
- { name: position_based, type: position_based, first_weight: 0.4, last_weight: 0.4 }
- { name: time_decay, type: time_decay, half_life_days: 7 }
Top-level options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Toggle the whole module. |
version | number | 1 | Config version (set by Superform Labs). |
dependencies | list | [ga4] | Required modules; the ga4 module must be enabled. |
revenue_field | string | ecommerce.purchase_revenue | Default revenue path for conversions without their own revenue_field. |
Touchpoints
touchpoint:
mode: session # or: source_change
source_change_unique_fields:
- fixed_traffic_source.source
- fixed_traffic_source.medium
- fixed_traffic_source.campaign
| Option | Type | Default | Description |
|---|---|---|---|
mode | session | source_change | session | session = one traffic touch per ga4_sessions row; source_change = one per intra-session traffic-source change. See Touchpoint modes. |
source_change_unique_fields | list | source / medium / campaign | source_change only. Dotted field paths whose change starts a new traffic touch. |
Conversions
Each entry is either structured (eventFilter + optional paramFilter) or open SQL (sql:) - not both. First match wins, so order matters; entries sharing a name are OR'd together.
conversions:
# First-match-wins: a more specific subset must come before the general one.
- name: high_value_purchase
eventFilter: purchase
paramFilter:
conditions:
- { param: 'safe_cast(ecommerce.purchase_revenue as numeric)', value: 500, operator: '>=' }
- { param: 'geo.country', value: ['Belgium', 'Netherlands'], operator: 'in' }
logic: and
revenue_field: ecommerce.purchase_revenue_in_usd # per-entry override
- name: purchase
eventFilter: purchase
- name: newsletter_signup # shared name is OR'd together
eventFilter: [signup, sign_up]
- name: newsletter_signup
sql: 'event_name = "page_view" AND page.path LIKE "/thank-you%"'
| Field | Description |
|---|---|
name | Conversion name (appears as conversion_name / closing_conversion_name). Reuse a name to OR several definitions. |
eventFilter | Event name, or list of event names, to match. |
paramFilter | Optional structured predicate: conditions (each param / value / operator) combined by logic (and / or). Operators include =, >=, in, like, and so on. |
sql | Open SQL boolean predicate - an escape hatch when eventFilter / paramFilter cannot express the match. Mutually exclusive with eventFilter / paramFilter. |
revenue_field | Optional per-entry revenue path overriding the top-level revenue_field. |
Dimensions & metrics
Standard channel dimensions (source, medium, campaign, default channel group, device) are always emitted. Use dimensions / metrics to carry extra fields into journeys and the models/reports.
dimensions:
- { name: geo.country, renameTo: country }
- { name: session_source.term, renameTo: utm_term, nullLabel: 'Direct' }
metrics:
- { name: ecommerce.tax_value, renameTo: tax_value }
| Field | Applies to | Description |
|---|---|---|
name (dimensions) | traffic rows | Source field path. On traffic rows the value is IFNULL(raw, nullLabel). |
nullLabel (dimensions) | traffic rows | Per-dimension null replacement. Default (not set); use Direct for source/channel dimensions. |
renameTo | both | Output column name (sanitized). |
name (metrics) | conversion rows | Metric field, populated on conversion rows only; traffic rows get a typed NULL. |
Some fields only exist in one touchpoint mode - e.g. session_source.* and last_non_direct_traffic_source.* are session-mode fields. Choose dimensions that exist in your configured touchpoint.mode.
Journeys
journey:
closing_conversions:
- purchase
user_identifier: user_pseudo_id
channel_grouping: default_channel_group
inactivity_gap_days: null
pre_conversion_lookback_days: null
| Option | Type | Default | Description |
|---|---|---|---|
closing_conversions | list | [] | Conversion names that close a journey. Must exist in conversions[]. Empty = nothing closes and the models table is empty. Non-listed conversions become micro-conversions. |
user_identifier | string | user_pseudo_id | Identity key; non-user_pseudo_id values fall back to user_pseudo_id via COALESCE. |
channel_grouping | string | default_channel_group | Channel grouping the path reports build on: default_channel_group or a configured GA4 CUSTOM_CHANNEL_GROUPING name. |
inactivity_gap_days | number | null | null | Split a journey when the next touch is more than N days later. |
pre_conversion_lookback_days | number | null | null | A closing conversion only attributes touches within M days before it; older touches become a separate journey. |
See Journey boundaries for the full boundary semantics.
Attribution models
attribution_models:
- { name: first_touch, type: first_touch }
- { name: last_touch, type: last_touch }
- { name: linear, type: linear }
- { name: position_based, type: position_based, first_weight: 0.4, last_weight: 0.4 }
- { name: time_decay, type: time_decay, half_life_days: 7 }
# - { name: participation, type: participation } # full credit to every touch - overcounts (influence, not credit)
Defaults to the five standard fractional models. position_based takes first_weight / last_weight (default 0.4 / 0.4); time_decay takes half_life_days (default 7). The opt-in participation model gives full credit to every touch and overcounts by design - see Fractional credit vs influence. The models table only builds once journey.closing_conversions is set.