Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion guides/plugins/plugins/storefront/advanced/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ nav:

This section covers advanced topics for extending and customizing the Storefront in plugins.

These guides focus on performance optimization, caching behavior, cookie consent integration, and JavaScript plugin control:
These guides focus on performance optimization, caching behavior, cookie consent integration, analytics, and JavaScript plugin control:

* [Add caching to custom controller](./add-caching-to-custom-controller.md)
* [Add cookie to manager](./add-cookie-to-manager.md)
* [Reacting to cookie consent changes](./reacting-to-cookie-consent-changes.md)
* [Remove JavaScript plugin](./remove-unnecessary-js-plugin.md)
* [Storefront analytics with Google Analytics 4](./storefront-analytics.md)
240 changes: 240 additions & 0 deletions guides/plugins/plugins/storefront/advanced/storefront-analytics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
---
nav:
title: Storefront analytics with Google Analytics 4
position: 180

---

# Storefront analytics with Google Analytics 4

The Storefront reports ecommerce events to Google Analytics 4 (GA4) when analytics are active for a sales channel. This guide describes the data the Storefront sends, the identifiers your Google Tag Manager setup can rely on, and how an extension makes its own products and lists visible to analytics.

::: info
This guide describes the behavior of Shopware 6.7.16.0 and later. Earlier versions send a different payload. The release notes of 6.7.16.0 list what changed.
:::

## Prerequisites

Analytics are configured per sales channel in the Administration, in the analytics settings of the sales channel. The Storefront only loads the analytics script when a tracking ID is set and analytics are active. With the default cookie consent, the script only starts after the shopper accepted the Google Analytics or Google Ads cookie.

A sales channel without active analytics renders none of the analytics attributes described in this guide, loads no analytics script, and answers the analytics routes with a 404.

## Delivery

The Storefront chooses the transport by the tracking ID:

* A Google tag ID, such as `G-XXXXXXX`, sends every event with `gtag('event', name, parameters)`.
* A Google Tag Manager ID, such as `GTM-XXXXXXX`, pushes ecommerce events to `window.dataLayer` in the format Google documents for GA4. The previous ecommerce object is cleared first.

```javascript
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'EUR',
value: 39.98,
items: [{ item_id: 'SW10001', item_name: 'Shirt', price: 19.99, quantity: 2 }],
},
});
```

Events without ecommerce data, such as `login`, `sign_up` and `search`, are pushed with their parameters at the top level, for example `{ event: 'login', method: 'mail' }`.

In Google Tag Manager, read the parameters with data layer variables such as `ecommerce.items` or `ecommerce.value`, and use the GA4 event tag with "Send Ecommerce data" enabled.

## Events

| Event | Reported when |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `view_item_list` | A product listing is shown, filtered, sorted or paginated |
| `select_item` | The shopper follows a product link in a list |
| `view_item` | A product detail page is shown |
| `add_to_cart` | A product is added to the cart. Repeating an order in the account reports one event with all its line items |
| `remove_from_cart` | A product is removed from the cart |
| `add_to_wishlist` | A product is added to the wishlist |
| `remove_from_wishlist` | A product is removed from the wishlist |
| `view_cart` | The cart page is shown, or the off-canvas cart opens when its tracking is enabled in the analytics settings |
| `begin_checkout` | The shopper starts the checkout |
| `add_shipping_info` | A shipping method is selected, once per method and checkout |
| `add_payment_info` | A payment method is selected, once per method and checkout |
| `purchase` | An order is placed |
| `login` | A login succeeded. Reported on the page after the login, so a wrong password reports nothing |
| `sign_up` | A customer account was created. Reported on the page after the registration. A guest checkout and a failed registration report nothing |
| `search` | The shopper searches |

## Event and item parameters

Items use the GA4 property names, and all numeric values are numbers:

| Parameter | Content |
| ----------------------------------------- | -------------------------------------------------------------- |
| `item_id` | The product number, or the product ID without a product number |
| `item_name` | The translated product name |
| `item_brand` | The translated manufacturer name |
| `item_variant` | The selected options of a variant, for example `Red, L` |
| `item_category` to `item_category5` | The category path of the product, at most five levels |
| `price` | The unit price before discounts |
| `quantity` | The quantity of the line item |
| `item_list_id`, `item_list_name`, `index` | The list the product was presented in, see [Lists](#lists) |

The cart, checkout and `purchase` events report these event parameters:

* `value` is the position price: all line items including promotion discounts, without shipping.
* `coupon` holds the codes of the applied promotions, separated by a comma.
* `shipping` and `tax` of `purchase` are reported separately from `value`.
* Discounts and other line items that are not products are not reported as items.

`add_to_cart` reports the unit price of the graduated price that applies to the quantity the cart holds after the add, and the value of the added quantity.

### Categories

On a category page, the products of the category listing take their categories from the breadcrumb. On a product detail page, the main product does. For every other product, such as on the homepage, a landing page or the wishlist, in a product slider of a category page, or in the cross selling of a product detail page, the analytics script requests the path of the product when the shopper adds or removes it. The route `frontend.analytics.product-categories` answers `GET /widgets/analytics/product-categories?productId=<id>` with the breadcrumb of the product detail page as a list of names. It is HTTP cached, and its URL is only added to `window.router` when analytics are active.

Check warning on line 91 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L91

The word “cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1]) Suggestions: `cross-selling` URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:91:281: The word “cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1])
 Suggestions: `cross-selling`
 URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens 
 Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 91 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L91

This abbreviation for “identification” is spelled all-uppercase. (ID_CASING[2]) Suggestions: `ID` Rule: https://community.languagetool.org/rule/show/ID_CASING?lang=en-US&subId=2 Category: CASING
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:91:524: This abbreviation for “identification” is spelled all-uppercase. (ID_CASING[2])
 Suggestions: `ID`
 Rule: https://community.languagetool.org/rule/show/ID_CASING?lang=en-US&subId=2
 Category: CASING

Products added from a product box post their brand and categories in the line item payload, as `manufacturerName` and `categoryNames`. The cart, checkout and purchase events read them from there, so neither the cart nor the checkout loads anything for analytics.

A product added on a page without a breadcrumb about it has no categories in its payload. The browser keeps the categories requested for it in the local storage under `swGaProductCategories` for 30 days, and the cart, checkout and purchase events fill missing categories from there. The order removes them. A line item added in another browser or through the API is reported without categories.

## Lists

`view_item_list` and `select_item` report the list a product was presented in as `item_list_id` and `item_list_name`, and its position as `index`. The index counts from zero across the pages of a paginated listing. `view_item` repeats the list the product was selected from on its item, also when the shopper opened the product in another tab.

The list IDs are a contract that Google Tag Manager triggers can rely on:

| List | `item_list_id` | `item_list_name` |
| -------------------------------------- | ---------------------------------- | ----------------------------------------------- |
| Category listing | `category:<category ID>` | Translated category name |
| Listing outside a category | `cms:<slot ID>` | None |
| Search results | `search` | Translated snippet `search.analyticsListName` |
| Wishlist | `wishlist` | Translated snippet `wishlist.analyticsListName` |
| Cross selling tab | `cross_selling:<cross selling ID>` | Translated name of the cross selling group |

Check warning on line 109 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L109

The word “Cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1]) Suggestions: `Cross-selling` URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:109:2: The word “Cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1])
 Suggestions: `Cross-selling`
 URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens 
 Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 109 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L109

The word “cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1]) Suggestions: `cross-selling` URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:109:59: The word “cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1])
 Suggestions: `cross-selling`
 URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens 
 Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 109 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L109

The word “cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1]) Suggestions: `cross-selling` URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:109:103: The word “cross-selling” is spelled with a hyphen. (CROSS_COMPOUNDS[1])
 Suggestions: `cross-selling`
 URL: https://languagetool.org/insights/post/hyphen/#compound-nouns-with-and-without-hyphens 
 Rule: https://community.languagetool.org/rule/show/CROSS_COMPOUNDS?lang=en-US&subId=1
 Category: GRAMMAR
| Product slider | `slider:<element ID>` | Title of the slider |
| Any other Shopping Experiences element | `cms:<element ID>` | None |

The list of a selected product is kept in the local storage of the browser under `swGaSelectedItemLists` for 30 minutes and is removed by the first view of the product.

## Make extension markup visible to analytics

### Products

Analytics treat every element with a `data-product-information` attribute as a product, not only the `.product-box` of the default theme. The attribute holds a JSON object with at least the product `id`:

```twig
{# a matching price rule or graduated prices replace the default price #}
{% set price = product.calculatedPrices.count > 0
? product.calculatedPrices.first.unitPrice
: product.calculatedPrice.unitPrice %}

{% set productInformation = {
id: product.id,

Check warning on line 128 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L128

This abbreviation for “identification” is spelled all-uppercase. (ID_CASING[2]) Suggestions: `ID` Rule: https://community.languagetool.org/rule/show/ID_CASING?lang=en-US&subId=2 Category: CASING
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:128:4: This abbreviation for “identification” is spelled all-uppercase. (ID_CASING[2])
 Suggestions: `ID`
 Rule: https://community.languagetool.org/rule/show/ID_CASING?lang=en-US&subId=2
 Category: CASING

Check warning on line 128 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L128

If a new sentence starts here, add a space and start with an uppercase letter. (LC_AFTER_PERIOD[1]) Suggestions: ` Id`, ` id` Rule: https://community.languagetool.org/rule/show/LC_AFTER_PERIOD?lang=en-US&subId=1 Category: CASING
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:128:16: If a new sentence starts here, add a space and start with an uppercase letter. (LC_AFTER_PERIOD[1])
 Suggestions: ` Id`, ` id`
 Rule: https://community.languagetool.org/rule/show/LC_AFTER_PERIOD?lang=en-US&subId=1
 Category: CASING
sku: product.productNumber,
name: product.translated.name,
brand: product.manufacturer.translated.name|default(''),
price: price
} %}

<article class="my-product-teaser" data-product-information="{{ productInformation|json_encode }}">
<a href="{{ seoUrl('frontend.detail.page', { productId: product.id }) }}">{{ product.translated.name }}</a>

Check warning on line 136 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L136

If a new sentence starts here, add a space and start with an uppercase letter. (LC_AFTER_PERIOD[1]) Suggestions: ` Id`, ` id` Rule: https://community.languagetool.org/rule/show/LC_AFTER_PERIOD?lang=en-US&subId=1 Category: CASING
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:136:68: If a new sentence starts here, add a space and start with an uppercase letter. (LC_AFTER_PERIOD[1])
 Suggestions: ` Id`, ` id`
 Rule: https://community.languagetool.org/rule/show/LC_AFTER_PERIOD?lang=en-US&subId=1
 Category: CASING
</article>
```

| Property | Required | Reported as |
| --------- | -------- | --------------------------------------------------------- |
| `id` | Yes | Used to match the product, and as `item_id` without `sku` |
| `sku` | No | `item_id` |
| `name` | No | `item_name` |
| `brand` | No | `item_brand` |
| `price` | No | `price` |
| `variant` | No | `item_variant` |

Report the price the shopper pays for one piece, as in the example: `calculatedPrices.first` when a price rule or graduated prices apply, otherwise `calculatedPrice`. The product box and the product detail page do the same.

Only these properties are reported. Other keys of the object are ignored, so the attribute can carry data for your own scripts.

A link inside the element reports `select_item`. Buttons and forms inside the element, such as the buy button, are not reported as a selection.

### Lists

A product element inside a Shopping Experiences element is attributed to that element as `cms:<element ID>` without any change. To report your own list ID and name, set `data-list-id` and `data-list-name` on an element that contains the product elements:

```twig
{% set analyticsActive = storefrontAnalytics and storefrontAnalytics.isActive() %}

<div class="my-recommendations"
{% if analyticsActive %}
data-list-id="my-extension:recommendations"
data-list-name="{{ 'my-extension.recommendations'|trans }}"

Check warning on line 165 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L165

Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:165:67: Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION
{% endif %}>
{# product elements #}
</div>
```

Prefix your list IDs with the name of your extension, so they cannot collide with the IDs of Shopware or other extensions.

A list that is paginated can set `data-list-start` on itself or on an element inside it, with the position of its first product. The index then continues across pages.

A theme that renders `@Storefront/storefront/component/product/listing.html.twig` can pass the list through the `listId` and `listName` variables instead.

::: warning
Render analytics attributes only when `storefrontAnalytics and storefrontAnalytics.isActive()` is true, as Shopware does. A sales channel without analytics must not carry analytics data in its HTML.
:::

## Checkout steps

The shipping and payment method forms of the confirm page reload the page on every change. `add_shipping_info` and `add_payment_info` are therefore reported once per selected method of a checkout. A reload that keeps the method reports nothing, and selecting another method reports it. The reported steps are kept in the session storage under `swGaReportedCheckoutSteps` and are cleared by `begin_checkout` and `purchase`.

A new customer reports `add_shipping_info` with the shipping method of the context when submitting the registration on the checkout register page.

## Custom events

To report your own events, extend `AnalyticsEvent` from `src/plugin/google-analytics/analytics-event` and call `pushEvent()`. It chooses the transport of the sales channel and normalizes the item parameters. Pass `{ ecommerce: false }` as the third argument for an event without ecommerce data.

```javascript
import AnalyticsEvent from 'src/plugin/google-analytics/analytics-event';

export default class GenerateLeadEvent extends AnalyticsEvent {
supports() {
return true;
}

execute() {
document.addEventListener('submit', (event) => {
if (!this.active || !event.target.matches('.my-lead-form')) {

Check warning on line 201 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L201

Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:201:54: Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION

Check warning on line 201 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L201

Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:201:68: Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION
return;
}

this.pushEvent('generate_lead', { currency: 'EUR', value: 50 });
});
}
}
```

Register the event by overriding the `GoogleAnalytics` plugin. The plugin is only registered when analytics are active, so the override does nothing in other sales channels:

```javascript
// <plugin root>/src/Resources/app/storefront/src/my-google-analytics.plugin.js
import GoogleAnalyticsPlugin from 'src/plugin/google-analytics/google-analytics.plugin';
import GenerateLeadEvent from './generate-lead.event';

export default class MyGoogleAnalyticsPlugin extends GoogleAnalyticsPlugin {
registerDefaultEvents() {
super.registerDefaultEvents();

this.registerEvent(GenerateLeadEvent);
}
}
```

```javascript
// <plugin root>/src/Resources/app/storefront/src/main.js
if (window.gtagActive) {
window.PluginManager.override('GoogleAnalytics', () => import('./my-google-analytics.plugin'));

Check warning on line 230 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L230

Add a space between sentences. (SENTENCE_WHITESPACE) Suggestions: ` PluginManager` Rule: https://community.languagetool.org/rule/show/SENTENCE_WHITESPACE?lang=en-US Category: TYPOGRAPHY
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:230:11: Add a space between sentences. (SENTENCE_WHITESPACE)
 Suggestions: ` PluginManager`
 Rule: https://community.languagetool.org/rule/show/SENTENCE_WHITESPACE?lang=en-US
 Category: TYPOGRAPHY

Check warning on line 230 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L230

Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:230:66: Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION

Check warning on line 230 in guides/plugins/plugins/storefront/advanced/storefront-analytics.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/storefront/advanced/storefront-analytics.md#L230

Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/plugins/plugins/storefront/advanced/storefront-analytics.md:230:95: Unpaired symbol: ‘'’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION
}
```

The guide [Override existing JavaScript](../javascript/override-existing-javascript.md) explains plugin overrides in detail.

## More information

* [Google Analytics 4 ecommerce events](https://developers.google.com/analytics/devguides/collection/ga4/ecommerce)
* [Cookie consent management](../../../../../concepts/commerce/content/cookie-consent-management.md)
* [Reacting to cookie consent changes](./reacting-to-cookie-consent-changes.md)
Loading