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
| Variable | Class | Purpose |
|---|---|---|
craft.ga4 | Yui\SeoSuite\variables\ga4Variable | Build and queue GA4 events, or reach the low-level GoogleAnalyticsApi. |
craft.gtm | Yui\SeoSuite\variables\gtmVariable | Render GTM dataLayer.push() script snippets for the storefront. |
craft.seosuite | Yui\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
| Method | Purpose |
|---|---|
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
| Method | Purpose |
|---|---|
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 event | yStore trigger | Settings key |
|---|---|---|
page_view | View::EVENT_AFTER_RENDER_PAGE_TEMPLATE, only when Auto Send PageViews is on; skipped on AJAX requests. | autoSendPageView |
sign_up | CustomerService::EVENT_AFTER_CUSTOMER_SIGN_UP | events.sign_up |
login | CustomerService::EVENT_AFTER_CUSTOMER_LOGIN | events.login |
search | SearchService::EVENT_AFTER_SEARCH | events.search |
view_item | Products::EVENT_AFTER_PRODUCT_VIEW | events.view_item |
add_to_cart | CartService::EVENT_AFTER_ADD_TO_CART | events.add_to_cart |
remove_from_cart | CartService::EVENT_AFTER_REMOVE_FROM_CART | events.remove_from_cart |
view_cart | CartService::EVENT_AFTER_CART_VIEW | events.view_cart |
begin_checkout | CheckoutService::EVENT_AFTER_BEGIN_CHECKOUT | events.begin_checkout |
purchase | CheckoutService::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'sproduct/pageTypevariables and session login/sign-up flashes to emitview_item,view_cart,begin_checkout,purchase,login, andsign_upsnippets via theafter-body-start/before-body-endhooks -- no yStore service event needed, this only depends on which template/variables the current page renders. add_to_cartvia a real yStore event (GoogleTagManagerService::triggerEvents(), gated by bothgoogleTagManagerServiceEnabledandgoogleTagManagerTrackingEventsEnabledplus the Events checkbox under the Google Tag Manager settings -- see SEO Suite overview for current module documentation status): listens toCartService::EVENT_AFTER_ADD_TO_CARTand stores the rendered snippet inCraft::$app->getSession()->set('sessionEventHtml', ...), since a cart add is usually followed by a redirect -- the snippet is flushed on the next page load'sbefore-body-endhook.
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_itemsnippet (seosuite/frontend/gtm/events/view-item.twig) always sendsquantity: 1as 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, renderseosuite/frontend/gtm/global-scripts(thegtm.jsloader) andseosuite/frontend/gtm/global-no-script(the<noscript>iframe fallback), both using the configuredgoogleTagManagerId.{% hook 'ga4InsertGtag' %}-- rendersseosuite/_includes/gtag(gtag('config', ...)) usinggoogleAnalyticsMeasurementId; addsuser_idto 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 callingPlugin::$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
| Service | Access | Purpose |
|---|---|---|
GoogleAnalyticsService | Plugin::getInstance()->getGoogleAnalytics() | Owns the GA4 event queue/API, triggerEvents() wiring, and addPageViewEvent() / getSimpleEvent(). |
GoogleTagManagerService | Plugin::getInstance()->getGoogleTagManager() | Renders GTM dataLayer snippets and wires the add_to_cart yStore event. |
GoogleCruxService | Plugin::getInstance()->getGoogleCrux() | fetchHistory() calls the Chrome UX Report History API for the configured domain and caches the result for 24h. See Dashboard -> Google CrUX. |
SchemaMarkupService | Plugin::getInstance()->getSchemaMarkup() | Renders the per-page JSON-LD block injected in after-head-start when Schema Markup is enabled. |
ModuleRegistryService | Plugin::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. |
DashboardService | Plugin::getInstance()->getDashboard() | Extends yui\craftcore\services\CoreDashboardService; builds the KPI/chart card set described in Dashboard and persists drag/resize layout. |
LicenseService | Plugin::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 bySchemaMarkupService, 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 theBaseEvent/AbstractEventclasses andGoogleAnalyticsApi::create()in PHP) and queue it withcraft.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 aview->hook()call, the same patternGoogleTagManagerServiceandPlugin::_registerSiteHooks()use internally. - SEOmatic (
nystudio107/seomatic) and Ether SEO (ether/seo) are detected automatically atPlugins::EVENT_AFTER_LOAD_PLUGINS(Plugin::$seomaticPlugin/Plugin::$etherSeoPlugin) and, when enabled, their page title is used for thepage_viewGA4 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.