Skip to main content

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

OptionTypeDefaultDescription
enabledbooleanfalseToggle the whole module.
versionnumber1Config version (set by Superform Labs).
dependencieslist[ga4]Required modules; the ga4 module must be enabled.
revenue_fieldstringecommerce.purchase_revenueDefault 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
OptionTypeDefaultDescription
modesession | source_changesessionsession = one traffic touch per ga4_sessions row; source_change = one per intra-session traffic-source change. See Touchpoint modes.
source_change_unique_fieldslistsource / medium / campaignsource_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%"'
FieldDescription
nameConversion name (appears as conversion_name / closing_conversion_name). Reuse a name to OR several definitions.
eventFilterEvent name, or list of event names, to match.
paramFilterOptional structured predicate: conditions (each param / value / operator) combined by logic (and / or). Operators include =, >=, in, like, and so on.
sqlOpen SQL boolean predicate - an escape hatch when eventFilter / paramFilter cannot express the match. Mutually exclusive with eventFilter / paramFilter.
revenue_fieldOptional 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 }
FieldApplies toDescription
name (dimensions)traffic rowsSource field path. On traffic rows the value is IFNULL(raw, nullLabel).
nullLabel (dimensions)traffic rowsPer-dimension null replacement. Default (not set); use Direct for source/channel dimensions.
renameTobothOutput column name (sanitized).
name (metrics)conversion rowsMetric field, populated on conversion rows only; traffic rows get a typed NULL.
Mode-specific dimensions

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
OptionTypeDefaultDescription
closing_conversionslist[]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_identifierstringuser_pseudo_idIdentity key; non-user_pseudo_id values fall back to user_pseudo_id via COALESCE.
channel_groupingstringdefault_channel_groupChannel grouping the path reports build on: default_channel_group or a configured GA4 CUSTOM_CHANNEL_GROUPING name.
inactivity_gap_daysnumber | nullnullSplit a journey when the next touch is more than N days later.
pre_conversion_lookback_daysnumber | nullnullA 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.