Skip to main content
Version: 2.0.0

Developer / API

SEO Suite ships two independent tracking modules -- Google Analytics (GA4) and Google Tag Manager (GTM) -- both registered from Yui\SeoSuite\Plugin::addComponents(). Each is reachable from Twig through its own global variable, and both wire themselves automatically into yStore's storefront events when yui/craft-plugin is installed and enabled -- no template changes are required for the built-in event set.

Twig variables

VariableClassPurpose
craft.ga4Yui\SeoSuite\variables\ga4VariableBuild and queue GA4 events, or reach the low-level GoogleAnalyticsApi.
craft.gtmYui\SeoSuite\variables\gtmVariableRender GTM dataLayer.push() script snippets for the storefront.
craft.seosuiteYui\SeoSuite\Plugin (via CraftVariableBehavior)The plugin instance itself -- e.g. craft.seosuite.getSettings().

Both ga4 and gtm are also exposed as plain Twig globals ({{ ga4 }} / {{ gtm }}) on site requests only, registered by ga4TwigExtension / gtmTwigExtension.

craft.ga4

MethodPurpose
simpleEvent(eventName = '')Returns a BaseEvent for eventName, ready to have parameters added and be queued.
ga4()Returns the plugin's GoogleAnalyticsApi (Yui\SeoSuite\base\api\GoogleAnalyticsApi), the low-level event queue/sender used internally.
addProductViewEvent(product)Given a yStore Product element, builds and queues a view_item event using the product's final price and store currency.

A simpleGa4Event Twig filter/function (ga4TwigExtension) is also available as a shortcut for craft.ga4.simpleEvent().

craft.gtm

MethodPurpose
productViewEvent(product)Renders the seosuite/frontend/gtm/events/view-item template for the given product and returns the HTML (a <script> block pushing to dataLayer).

A productViewEvent Twig filter/function (gtmTwigExtension) wraps the same call.

Sending a GA4 event manually

Most storefront actions are tracked automatically (see GA4 events below). For a custom event, queue one directly:

{% do craft.ga4.ga4().addEvent(
craft.ga4.simpleEvent('my_custom_event')
) %}

Queued events are sent together on Response::EVENT_AFTER_SEND (GoogleAnalyticsService::triggerEvents() wires this in Plugin::_registerSiteEvents()), so calling addEvent() never delays the page response. GoogleAnalyticsApi::create() returns a GoogleAnalyticsEvents factory with one method per event class -- create()->PageViewEvent(), create()->AddToCartEvent(), create()->PurchaseEvent(), etc. -- each returning a fresh, empty event object.

GA4 events

Yui\SeoSuite\services\GoogleAnalyticsService::triggerEvents() wires GA4 event classes under Yui\SeoSuite\events\GoogleAnalytics\ to yStore service events, gated per-event by the Events checkboxes in Settings -> Google Analytics:

GA4 eventyStore triggerSettings key
page_viewView::EVENT_AFTER_RENDER_PAGE_TEMPLATE, only when Auto Send PageViews is on; skipped on AJAX requests.autoSendPageView
sign_upCustomerService::EVENT_AFTER_CUSTOMER_SIGN_UPevents.sign_up
loginCustomerService::EVENT_AFTER_CUSTOMER_LOGINevents.login
searchSearchService::EVENT_AFTER_SEARCHevents.search
view_itemProducts::EVENT_AFTER_PRODUCT_VIEWevents.view_item
add_to_cartCartService::EVENT_AFTER_ADD_TO_CARTevents.add_to_cart
remove_from_cartCartService::EVENT_AFTER_REMOVE_FROM_CARTevents.remove_from_cart
view_cartCartService::EVENT_AFTER_CART_VIEWevents.view_cart
begin_checkoutCheckoutService::EVENT_AFTER_BEGIN_CHECKOUTevents.begin_checkout
purchaseCheckoutService::EVENT_AFTER_PURCHASE_SUCCESS, only when the checkout event carries a completed order.events.purchase

All of the above only fire when Plugin::$shopPlugin is set (yStore installed and enabled) and Google Analytics is enabled (googleAnalyticsServiceEnabled). Handlers translate the yStore event's currency/value/items into the matching GA4 event (GoogleAnalyticsService::addProductViewEvent(), addProductToCartEvent(), addPurchaseSuccessEvent(), ...) -- these private methods aren't called directly, but are useful when tracing why a particular event did or didn't fire.

page_view additionally picks up the page title from SEOmatic or Ether SEO when either plugin is installed and enabled (AnalyticsHelper::getTitleFromSeomatic() / getTitleFromEtherSeo()), falling back to the rendered template's entry/product/seoTitle variable, then the raw template path.

GTM events

Yui\SeoSuite\services\GoogleTagManagerService renders dataLayer.push() script snippets from seosuite/frontend/gtm/events/* templates. Two paths feed them:

  • Automatic, template-hook driven (Plugin::_registerSiteEvents(), View::EVENT_BEFORE_RENDER_PAGE_TEMPLATE / EVENT_AFTER_RENDER_PAGE_TEMPLATE): reads the rendering template's product/pageType variables and session login/sign-up flashes to emit view_item, view_cart, begin_checkout, purchase, login, and sign_up snippets via the after-body-start / before-body-end hooks -- no yStore service event needed, this only depends on which template/variables the current page renders.
  • add_to_cart via a real yStore event (GoogleTagManagerService::triggerEvents(), gated by both googleTagManagerServiceEnabled and googleTagManagerTrackingEventsEnabled plus the Events checkbox under the Google Tag Manager settings -- see SEO Suite overview for current module documentation status): listens to CartService::EVENT_AFTER_ADD_TO_CART and stores the rendered snippet in Craft::$app->getSession()->set('sessionEventHtml', ...), since a cart add is usually followed by a redirect -- the snippet is flushed on the next page load's before-body-end hook.

Plugin::$currentTemplate / Plugin::$currentTitle are populated on every page render and reused by both the GA4 page_view title fallback and GTM's view_item tracking.

GTM's view_item snippet (seosuite/frontend/gtm/events/view-item.twig) always sends quantity: 1 as of v1.4.1. Earlier versions sent the product's stock quantity (product.qty) instead of the quantity being viewed, which produced incorrect item value totals in GA4/GTM reporting.

Rendering GTM/GA global scripts

Two Craft hooks control what's injected into <head> / after <body>, handled in Plugin::_registerSiteHooks() and Plugin::ga4InsertGtag():

  • after-head-start / after-body-start -- when Google Tag Manager is enabled, render seosuite/frontend/gtm/global-scripts (the gtm.js loader) and seosuite/frontend/gtm/global-no-script (the <noscript> iframe fallback), both using the configured googleTagManagerId.
  • {% hook 'ga4InsertGtag' %} -- renders seosuite/_includes/gtag (gtag('config', ...)) using googleAnalyticsMeasurementId; adds user_id to the config when Send User ID is on and a user ID is resolvable (AnalyticsHelper::getUserId()).
  • {% hook 'ga4SendPageView' %} -- shortcut for queueing a page-view event from a template, equivalent to calling Plugin::$plugin->getGoogleAnalytics()->addPageViewEvent($title).

These hooks are placed by the base craft-plugin layout templates -- you normally don't need to call them yourself unless building a custom layout.

Services

ServiceAccessPurpose
GoogleAnalyticsServicePlugin::getInstance()->getGoogleAnalytics()Owns the GA4 event queue/API, triggerEvents() wiring, and addPageViewEvent() / getSimpleEvent().
GoogleTagManagerServicePlugin::getInstance()->getGoogleTagManager()Renders GTM dataLayer snippets and wires the add_to_cart yStore event.
GoogleCruxServicePlugin::getInstance()->getGoogleCrux()fetchHistory() calls the Chrome UX Report History API for the configured domain and caches the result for 24h. See Dashboard -> Google CrUX.
SchemaMarkupServicePlugin::getInstance()->getSchemaMarkup()Renders the per-page JSON-LD block injected in after-head-start when Schema Markup is enabled.
ModuleRegistryServicePlugin::getInstance()->getModuleRegistry()Single source of truth for the module list (handle, label, category, settings URL) and per-module configured/partial/disabled status, used by both the module sidebar and the dashboard KPI cards.
DashboardServicePlugin::getInstance()->getDashboard()Extends yui\craftcore\services\CoreDashboardService; builds the KPI/chart card set described in Dashboard and persists drag/resize layout.
LicenseServicePlugin::getInstance()->getLicense()Shared YUI license activation/redemption flow, same pattern as other YUI plugins.

JSON-LD output hardening (v1.4.1)

SchemaMarkupService encodes the rendered JSON-LD with JSON_HEX_TAG | JSON_HEX_AMP (previously JSON_UNESCAPED_SLASHES, which allowed literal </script> sequences from field content to break out of the <script type="application/ld+json"> block). Any custom code that also serializes user-supplied content into a <script type="application/ld+json"> tag should use the same flags rather than JSON_UNESCAPED_SLASHES.

Control panel routes

Registered on UrlManager::EVENT_REGISTER_CP_URL_RULES (Yui\SeoSuite\plugin\Routes::_registerCpRoutes()), all under seosuite/*:

  • seosuite -- plugin dashboard/index.
  • seosuite/settings, seosuite/settings/general/settings -- settings index and General tab.
  • seosuite/settings/google-analytics, seosuite/settings/google-tag-manager -- see this page for GA4/GTM.
  • seosuite/settings/facebook-pixel, seosuite/settings/twitter-pixel, seosuite/settings/pinterest-tag, seosuite/settings/linkedin-insights-tag, seosuite/settings/hotjar-tracking-code -- see Social Pixels & Hotjar.
  • seosuite/settings/google-crux -- see Dashboard -> Configuring CrUX.
  • seosuite/settings/search-console, seosuite/settings/schema-markup, seosuite/settings/structured-data -- as of v1.4.1 these are fully functional settings pages (previously non-functional upsell/CTA placeholders): Search Console injects a site-verification <meta> tag from a token field; Schema Markup is an Organization JSON-LD generator (org type/name/logo/social profile fields, rendered by SchemaMarkupService, see JSON-LD output hardening above); Structured Data is a single toggle that adds a JSON-LD preview/ validation panel. New, functional in this release but not yet covered by dedicated CP-facing documentation -- see SEO Suite overview for current module status.
  • seosuite/license, seosuite/license/redeem, seosuite/license/revoke, seosuite/license/activate, seosuite/license/delete, seosuite/license/copy-token -- license management, shared flow used by other YUI plugins.

Extensibility

  • Custom GA4 events: build one with craft.ga4.simpleEvent() (or the BaseEvent/AbstractEvent classes and GoogleAnalyticsApi::create() in PHP) and queue it with craft.ga4.ga4().addEvent() -- see Sending a GA4 event manually.
  • Custom GTM snippets: render your own template and push it into the page via Plugin::getInstance()->renderPluginTemplate() plus a view->hook() call, the same pattern GoogleTagManagerService and Plugin::_registerSiteHooks() use internally.
  • SEOmatic (nystudio107/seomatic) and Ether SEO (ether/seo) are detected automatically at Plugins::EVENT_AFTER_LOAD_PLUGINS (Plugin::$seomaticPlugin / Plugin::$etherSeoPlugin) and, when enabled, their page title is used for the page_view GA4 event instead of the raw template title -- no configuration needed on either side.
  • Per-event opt-out: every automatic GA4/GTM event is gated by its own Settings checkbox (see the tables above), so integrations that only need a subset of tracking can disable the rest without touching code.