oro/google-tag-manager-bundle
The Oro Google Tag Manager Bundle integrates GTM into Oro applications with configurable container settings and ecommerce event tracking, helping storefronts send page, product, and order data to analytics and marketing tags with minimal setup.
This is not a Laravel package — it is an OroPlatform/OroCommerce Symfony bundle (oroinc namespace) for injecting Google Tag Manager and pushing e-commerce dataLayer events from an Oro storefront. Drop the idea of a service provider; the install target is an Oro application.
Verify before installing:
composer show oro/google-tag-manager --all # check "type" (symfony-bundle / oro-bundle) and requires
Expect hard version constraints against oro/platform and oro/commerce. Pin it to match your platform minor version — Oro bundles are released in lockstep and will not float across majors.
Install (inside an Oro app):
composer require oro/google-tag-manager
php bin/console oro:platform:update --env=prod
php bin/console cache:clear --env=prod
Where to look first, in this order:
Resources/config/oro/system_configuration.yml — the real API surface. This tells you the config keys, labels, and scopes (global / website) you'll toggle in the admin UI.Resources/views/ — the Twig snippet and where the dataLayer bootstrap is rendered.Resources/public/js/ — the RequireJS module(s) that push events. This is where the event contract lives.Resources/config/oro/layouts.yml or Resources/views/layouts/ — how the block is injected into head / before_body_end.DependencyInjection/Configuration.php and Resources/config/services.yml — anything you can decorate.First working use case: enable the integration in System → Configuration (look for the Google Tag Manager group under Sales/Commerce/Marketing), paste your GTM-XXXXXXX container ID, save with the correct scope, clear cache, then load the storefront and confirm the snippet appears once in <head>. Verify a real signal end-to-end by placing a test order and watching window.dataLayer in the console alongside GTM Preview mode.
Configuration-driven, not code-driven. The intended workflow is: admin sets container ID per website scope, the bundle renders the container snippet and emits a baseline dataLayer. Resist hardcoding the ID in your theme — you'll lose multi-website support and the ability to disable GTM per storefront.
Server-rendered dataLayer payloads. The common pattern is to render page-specific e-commerce context into dataLayer from Twig (product SKU, name, price, category on a product page), then let the JS module push events on interaction:
{# conceptual — match the actual template in Resources/views #}
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
ecommerce: null
});
dataLayer.push({
event: 'view_item',
ecommerce: { items: [{ item_id: '{{ product.sku }}', item_name: '...', price: {{ price }} }] }
});
</script>
Use GA4 event names (view_item, add_to_cart, begin_checkout, purchase) rather than legacy UA naming; the bundle's JS may still use UA-era names depending on version — normalize in your own layer if so.
Layout overrides are the extension point. Inject or reorder the GTM block through a layout theme file rather than editing vendor templates:
# Resources/views/layouts/<theme>/layout.yml
layout:
actions:
- '@setBlock': { id: gtm_container, ... } # or move/remove the existing block
Override the Twig template by placing the same relative path in your own bundle — Oro's template resolution gives your bundle priority. Keep the vendor file untouched so upgrades stay clean.
Service decoration for behavioural changes. If the bundle exposes services (e.g. a data provider or block type), decorate with decorates: in your services.yml instead of subclassing. That's the only upgrade-safe way to alter what gets pushed.
Consent gating. Do not render the container unconditionally. Pair with a cookie-consent bundle and wrap the snippet behind a consent check, or use GTM's own consent mode (gtag('consent', 'default', …)) rendered before the container. This is a legal requirement in the EU and the bundle will not do it for you.
QA loop. GTM Preview mode → connect to your storefront URL → click through product → cart → checkout. In parallel, keep a console tab open logging dataLayer. Clear Oro cache after every template or layout change; the layout cache is aggressive and will serve stale markup silently.
If you're porting the concept to Laravel (the realistic path for a Laravel developer): mirror the contract, not the code. One Blade partial in resources/views/layouts/, one config/gtm.php with the container ID, one small JS module that owns dataLayer.push, and the same GA4 event taxonomy. That gets you 90% of the value in a fraction of the coupling.
oro/platform constraints or install dead weight that never boots. It is a Symfony bundle for Oro. If this appears in a Laravel dependency graph, something is misconfigured upstream.NOASSERTION. No machine-readable SPDX identifier. Open LICENSE in the repo before you ship anything commercial — Oro packages are typically OSL-3.0, which has copyleft-flavoured obligations on distribution. Get this reviewed rather than assumed.composer.json and treat them as a unit.cache:clear plus oro:platform:update after config or layout changes. In prod, check var/cache/prod timestamps before debugging code — you're often debugging stale Twig.googletagmanager.com before enabling.ecommerce object leakage. dataLayer persists across pushes. Always dataLayer.push({ ecommerce: null }) before a new e-commerce event, or GTM merges stale items into cart/purchase payloads.purchase. Deduplicate with a transaction ID check server-side or a sessionStorage guard — the bundle won't do it for you.googletagmanager.com to script-src and google-analytics.com / analytics.google.com to connect-src in Oro's CSP config. Failures show only in the browser console, not in PHP logs.Resources/config/oro/system_configuration.yml and the JS module as the authoritative spec. Budget time to fork; keep your overrides in your own bundle so a fork stays optional.How can I help you explore Laravel packages today?