Hawaiian Hotel and Resort Chain – Migration from Adobe Analytics to Google Analytics 4 – including 100+ custom parameters and cross-domain tracking

< Back to Case Studies

Business Case:

A large hotel and resort chain based in Hawaii was launching a re-designed user experience and needed to understand how users interact with its promotions, property pages, and booking widget. They also had a requirement to add the OneTrust cookie management platform, including a cookie banner at the bottom of the page.

Solution:

We set up an Enhanced eCommerce implementation including cross-domain tracking for the main website and the connected Sabre (Synxis) booking and reservation management portal. We mapped out a wide variety of custom parameters that tracked every aspect of the user’s journey, including the reservation itself (conversion event). All Google Analytics 4, first-party and third-party tags and trackers were rolled into OneTrust, and cataloged for appropriate handling based on cookie consent categories.

Summary:

Architected and implemented a holistic cross-domain tracking solution for a regional hotel chain in Google Analytics 4 and Tag Manager. Using 100+ custom parameters, the hotel chain now captures granular information about how visitors interact with every promotion, property, room category, and offer on their site and reservation/booking engine.

Technical Documentation Sample:
(Google Analytics Documentation for Development Team)

—–

In addition to adding data-tag-item element attributes to track clicks on all relevant elements, there are a few other data attributes Google Analytics will need to capture to round out the data collection. Please note that all values have a character limit of 100.

Examples:

  • Page loads: data can be added using dataLayer.push in the header:

<script>
dataLayer.push({
 'event': 'booking_data_load',
  'property_id': '9999'
});
</script>

<div class="card" role="group" property_id=”9999” property_name=”Property Name” property_region=”Region Name” property_location=”Location Name”>

  • Hotels & Resorts Landing Page
    • Individual element (each property card)
      • property_id, property_name, property_region, property_location
  • Property Page
    • Page loads
      • property_id, property_name, property_region, property_location
      • 'event': 'property_page_data_load',
  • Individual element (each room card)
    • On Rooms & Suites page load or where individual rooms are selectable, and for each selectable room element
    • room_type_name, room_type_code, room_pms_code
  • Room Detail Page
    • Page loads
      • property_id, property_name, property_region, property_location, room_type_name, room_type_code, room_pms_code
      • 'event': 'room_page_data_load',
  • Offers Category
    • Page loads
      • Comma-delimited list of impression_promo_codes values, one for each offer card shown
      • 'event': 'offers_category_data_load',
  • Offers Detail
    • Page loads, and on page refreshes when a new property is selected from the ‘Participating Hotels’ menu
      • Although an offer can be associated with multiple properties, only one property is shown at a time to the user
      • property_id, property_name, property_region, property_location
      • 'event': 'offers_detail_data_load',
      • Also, include a comma-delimited list of each of the following codes associated with the offer:
        • impression_promo_codes (include any group codes, if applicable), search_rate_code, search_special_rate_code, room_type_code
  • All galleries / sliders
    • On outermost slider element (if scoped to a specific property):
      • property_id, property_name, property_region, property_location
    • Individual element (each gallery image):
      • image_position, gallery_title (and room_type_name, room_type_code, room_pms_code for the Rooms & Suites gallery)
  • Booking Widget search (initial load and when search criteria modified in widget)

search_destination
search_check_in_date
search_check_out_date
search_currency
search_adult_guests
search_child_guests
search_number_of_rooms
search_number_of_nights
search_promo_code (Group Codes can be combined with Promo Codes, with a prefix of “GROUP – “ added to them)
search_rate_code (there can be more than one)
search_special_rate_code
property_id
property_name
property_region
property_location

  • Example code snippet to be executed upon loading the booking widget:

<script>
dataLayer.push({
   'event': 'booking_data_load',
  'search_destination': 'Location Name',
   'search_check_in_date': 'MM/DD/YYYY',
   'search_check_out_date': 'MM/DD/YYYY',
   'search_currency': 'USD',
'search_adult_guests': '1',
   'search_child_guests': '0',
   'search_number_of_rooms': '1',
   'search_number_of_nights': '5',
   'search_promo_code': 'PROMOCODE',
   'search_rate_code': 'RATECODE',
   'search_special_rate_code': 'SPECIALRATECODE',
   'property_id': '12345'
  'property_name': 'Property Name',
   'property_region': 'Region Name',
   'property_location': 'Location Name'
});
</script>

  1. On all page loads that contain property information or are scoped in nature to a specific property (Examples: All property detail pages, Offer Details, booking widget searches, etc.):

property_id
property_name
property_region
property_location

Examples of property data types below:

Note: ‘Phuket’ is not a region, only Location Name has regions

Example code snippet to be executed upon loading property-related content specific to a single property:

<script>
dataLayer.push({
   'event': 'property_page_data_load',
   'property_id': '9999',
  'property_name': 'Property Name',
   'property_region': 'Region Name',
   'property_location': 'Location Name'
});
</script>

Additionally, the same values should be added to the parent/wrapper DOM elements that reference a property, when multiple properties appear on the page:

<div class="card" role="group" property_id=”9999” property_name=”Property Name” property_region=”Region Name” property_location=”Location Name”>

  1. On all property pages that show multiple available rooms:

room_type_name
room_type_code
room_pms_code

Values should be added to the parent/wrapper DOM elements that reference a room, when multiple rooms appear on the page:

<div class="card" role="group" room_type_name=”Partial Ocean View - 2 Queen Beds” room_type_code=”ZCKK” room_pms_code=”XSAAA”>

  1. On all page loads that contain multiple promotions (example: Offers Category Landing):


impression_promo_codes (comma delimited list of multiple promo codes)

Example code snippet to be executed on page load:

<script>
dataLayer.push({
   'event': 'offers_category_data_load',
   'impression_promo_codes': 'promo1,promo2,promo3'
});
</script>

  1. On all gallery / slider images:

gallery_title
image_position

<div class="card card-padding swiper-slide" role="group" aria-label="3 / 5" gallery_title=”Property Name” image_position=”3”>

For image_position, we can re-purpose the value used in the aria-label, but we should to trim the value to only save the first integer (“3” instead of “3 / 5”)

  1. Top Promotion Banner
    1. On the outermost element for the banner: impression_promo_codes associated with the advertised promotion
  2. Loyalty Signup Form + General Contact Form

          a.   On all forms, add a unique data attribute on the error message element, such as: data-tag-item=’form_error_message’

    <script>
    dataLayer.push({
       'event': 'form_error_message',
       'form_error_message': 'Invalid email address'
    });
    </script>

— For reference – some notes on rate and room codes below: —-

  1. There are property_ids for each property (e.g. ‘a resort or a specific condo unit)
  2. There are room type codes (e.g. ‘ZCXD’, which could translate as a Partial Ocean View w/ 1 King Bed)
  3. There are rate codes (e.g. ‘GHADAMP’ which could translate to ‘Book Early’ or ‘Flexible Cancellation’ or ‘Breakfast for 2’)
  4. A product is therefore the property_id + room type codes + rate codes

Some nuances:

  1. Property have an associated property_region (e.g. Region Name) and a property_location (e.g. Location Name)
  2. Room Type Codes are the concatenation of the room category (e.g. ‘Partial Ocean View’) and the bedding (e.g. ‘1 King Bed’). There is a field called room type name which is inconsistent and sometimes pulls the category ‘Partial Ocean View’ or sometimes pulls the bedding ‘1 King Bed’. So don’t use room type name, always use room type code.
  3. Room Type Codes can exist on multiple properties, (e.g. the code for a partial ocean view w/ 1 king bed could be at Waikiki Beach and Waikiki Reef properties)
  4. Rate Codes can be aggregated into Rate Categories. Rate Categories might be ‘military rates’.
  5. Rate Codes and Rate Categories can exist on multiple properties
  6. Promo Codes are groups of rate codes. So a promo code of ‘LOCAL’ might be 6 different rate codes. Therefore if a user enters a promo code additional rate codes will appear for selection. But the product ultimately selected by the user is still the property_id + room_type_code + rate_code since the rate code selected was made visible by promo code. We still want to capture if a promo code was applied, but the product itself isn’t a promo code
  7. Promo codes can exist on multiple properties
  1. The PMS code is the same as the room_type_code. It is just from a different system. So there is benefit in grabbing it since it connects to other data source but it isn’t unique data.

How this might manifest:

  1. On a offer page there might be a single promo code for that offer, but there could be multiple property_id and multiple room_type_code and multiple rate_codes associated with it since it’s just a bucket of options
  2. In the booking widget you select a property_id and maybe apply a promo, but there are still a variety of room_type_codes and a variety of rate_codes available.
  3. In the booking widget when you select the exact rate you want you are now selecting a specific room_type_code with a specific rate_code that is at a property_id. And if you selected a rate_code associated with a promo then you’d also have a promo_code

It’s like a “triple matrix” – so properties are their own, room types are their own and rates are their own – each exists independently of the others (no parent/child hierarchy). For instance, you can have the same room type at different properties across multiple rates. Same with rates, the same rate can be across different properties and different room types.

A product = property_id + a single room type code + a single rate code


< Back to Case Studies