Skip to main content

Advanced GTM Integration

By default, the Sealmetrics tracker fires the initial pageview as soon as the script finishes loading. For most sites that is exactly what you want, and the no-code GTM Template is the recommended path.

This guide is for a different scenario: you need to pass dynamic values to the initial pageview — typically a content_grouping derived from dataLayer variables — without losing hits and without sending duplicates.

To support this, Sealmetrics exposes two opt-in features that work together:

FeatureWhat it does
?auto=0Loads the library without firing the automatic initial pageview. You decide when to fire it and with what payload.
Buffer stubA small inline snippet that queues calls to sealmetrics(...) made before the library finishes loading, so none are lost.

If you don't need dynamic values on the first pageview, stick with the standard install — nothing in this guide is required.


When to use this guide

Use the advanced pattern if any of the following applies:

  • You need the first pageview to carry a content_grouping that lives in the dataLayer (page type, section, vertical, etc.).
  • You fire conversions or microconversions from above-the-fold elements (CTAs, hero buttons) that a user could click before the tracker finishes downloading.
  • Your team owns the GTM container but not the site HTML, and you need a workaround for the timing window between requesting the script and the browser executing it.

If none of these apply, the standard template installation is simpler and equally accurate.


Standard install (recap)

<script src="https://t.sealmetrics.com/t.js?id=YOUR_ACCOUNT_ID" defer></script>

This handles everything automatically: it loads the library, fires the pageview, and detects SPA navigation. No additional setup needed.


Manual mode: ?auto=0

Append &auto=0 to the script URL so the tracker loads but does not fire the initial pageview:

<script src="https://t.sealmetrics.com/t.js?id=YOUR_ACCOUNT_ID&auto=0" defer></script>

When ?auto=0 is set:

  • window.sealmetrics is exposed exactly as in the default mode.
  • The initial pageview is not sent. You fire it yourself with sealmetrics({ group: '...' }).
  • SPA navigation detection (pushState, replaceState, popstate) remains active — only the first pageview is skipped.
  • sealmetrics.conv() and sealmetrics.micro() work exactly as before.

Any other value of auto (auto=1, auto=foo, or no auto at all) keeps the default behaviour.


Buffer stub

When you load the tracker with async or through a tag manager, there is a window between requesting the script and the browser executing it. If a user clicks a button during that window and you call sealmetrics(...), the call is lost silently because window.sealmetrics does not exist yet.

The buffer stub solves this. It is a one-line snippet placed in <head> before the tracker loads. It accepts calls and stores them in an internal queue; when the real library loads, it drains the queue in FIFO order.

<script>
!function(w){w.sealmetrics=w.sealmetrics||function(){(w.sealmetrics.q=w.sealmetrics.q||[]).push(['pv',arguments])};w.sealmetrics.q=w.sealmetrics.q||[];w.sealmetrics.conv=w.sealmetrics.conv||function(){w.sealmetrics.q.push(['cv',arguments])};w.sealmetrics.micro=w.sealmetrics.micro||function(){w.sealmetrics.q.push(['mc',arguments])}}(window);
</script>

Properties of the stub:

  • About 180 bytes gzipped. No measurable impact on page performance.
  • Idempotent. If injected twice, the second injection is a no-op.
  • Three entry pointssealmetrics, sealmetrics.conv, sealmetrics.micro — matching the real library's API.

Readable version (equivalent to the minified snippet above)

(function (w) {
w.sealmetrics = w.sealmetrics || function () {
(w.sealmetrics.q = w.sealmetrics.q || []).push(['pv', arguments]);
};
w.sealmetrics.q = w.sealmetrics.q || [];
w.sealmetrics.conv = w.sealmetrics.conv || function () {
w.sealmetrics.q.push(['cv', arguments]);
};
w.sealmetrics.micro = w.sealmetrics.micro || function () {
w.sealmetrics.q.push(['mc', arguments]);
};
})(window);

Where to place the stub

LocationRecommendation
Inline in <head> (a <script> block directly in your HTML)Recommended. Runs before anything else and offers maximum reliability.
Tag inside GTM with Initialization triggerWorks in practice, but depends on the GTM container loading before the tag that loads the tracker. Use only if you cannot edit the HTML <head>.

Canonical GTM pattern

This is the recommended setup when you need a dynamic content_grouping on the first pageview. It combines the stub + ?auto=0 + a tag that fires the pageview manually once the dataLayer is ready.

Step 1 — Inline stub in <head> (in your site HTML)

<script>
!function(w){w.sealmetrics=w.sealmetrics||function(){(w.sealmetrics.q=w.sealmetrics.q||[]).push(['pv',arguments])};w.sealmetrics.q=w.sealmetrics.q||[];w.sealmetrics.conv=w.sealmetrics.conv||function(){w.sealmetrics.q.push(['cv',arguments])};w.sealmetrics.micro=w.sealmetrics.micro||function(){w.sealmetrics.q.push(['mc',arguments])}}(window);
</script>

Step 2 — GTM Tag "Sealmetrics — Load library" (trigger: Initialization)

(function () {
var s = document.createElement('script');
s.async = true;
s.src = 'https://t.sealmetrics.com/t.js?id={{ACCOUNT_ID}}&auto=0';
document.head.appendChild(s);
})();

Replace {{ACCOUNT_ID}} with a GTM Constant Variable holding your Sealmetrics Site ID.

Step 3 — GTM Tag "Sealmetrics — Pageview" (trigger: your dataLayer-ready event)

sealmetrics({ group: {{dlv.content_grouping}} });

{{dlv.content_grouping}} is a GTM Data Layer Variable that reads your content_grouping key. Replace it with whatever variable holds the grouping you want to attribute.

How it works

  • The stub defines window.sealmetrics from the very first millisecond of the page.
  • Step 2 starts the tracker download asynchronously — it does not block rendering.
  • Step 3 can fire before or after Step 2 finishes. If the stub is in place, the call is queued; if the library is already loaded, it executes immediately. Order does not matter.
  • When the tracker finishes loading, it drains the queue (1 pageview with the correct grouping) and does not fire its automatic pageview (because ?auto=0 is set).
  • Result: exactly one pageview per page load, with the correct grouping, regardless of which tag arrives first.

Alternative without the stub

If you cannot inject inline code into your site's HTML, you can achieve a similar result with ?auto=0 plus a custom event. The trade-off is described below.

GTM Tag 1 — load library and announce when ready

(function () {
var s = document.createElement('script');
s.async = true;
s.src = 'https://t.sealmetrics.com/t.js?id={{ACCOUNT_ID}}&auto=0';
s.onload = function () {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ event: 'sealmetrics_loaded' });
};
document.head.appendChild(s);
})();

GTM Tag 2 — fire pageview (trigger: Custom Event sealmetrics_loaded)

sealmetrics({ group: {{dlv.content_grouping}} });

Limitation. If the user leaves the page before s.onload fires, the pageview is lost. The canonical pattern with the stub does not have this problem because the queue lives in memory the moment the page starts parsing.


Anti-pattern: stub without ?auto=0

If you install the stub but forget to add ?auto=0 to the tracker URL:

  • Your code queues a pageview, e.g. sealmetrics({ group: 'checkout' }).
  • The tracker loads, drains the queue → fires 1 pageview.
  • The tracker also fires its automatic pageview → fires another pageview.
  • Result: 2 pageviews per session (duplicates).

Rule of thumb. If you queue pageviews in the stub, always pair it with ?auto=0.

If you only queue conv() or micro() calls (no pageviews), you can skip ?auto=0 — the tracker's automatic pageview is what you want in that case.


API reference

// Pageview using the grouping from the query string (?group=...)
sealmetrics();

// Pageview with an explicit grouping override
sealmetrics({ group: 'checkout' });

// Conversion with a monetary value
sealmetrics.conv('purchase', 89.99);

// Conversion with custom properties
sealmetrics.conv('purchase', 89.99, {
currency: 'EUR',
brand: 'Nike',
model: 'Air Max 90'
});

// Microconversion (funnel step, engagement)
sealmetrics.micro('add_to_cart');
sealmetrics.micro('add_to_cart', { product_id: 'SKU-123' });

The three entry points (sealmetrics, sealmetrics.conv, sealmetrics.micro) are available both in the stub and in the real library, with the same signatures.


Single Page Applications (SPAs)

This section applies if your site is Next.js, Nuxt, React Router, Vue Router, Angular, or any framework where clicking a link does not reload the HTML — only the route changes via pushState.

In a SPA, the behaviour of each event depends on who fires it:

EventWho fires itCorrect group in a SPA?
First pageview (initial load)GTM (tag with Initialization trigger)Yes — GTM runs, computes group, and passes it through
SPA navigation pageview (pushState)The tracker internallyNo — the tracker does not go through GTM, so it emits the pageview without your group logic
Conversion (sealmetrics.conv(...))Your code (a GTM tag, button handler, etc.)Yes — URL and properties are captured correctly
Microconversion (sealmetrics.micro(...))Your codeYes — same guarantees as conversion

What this means in practice

Conversions and microconversions work fully in SPAs. You can queue them early through the stub, fire them from GTM, attach dynamic properties — all of it captured correctly. The URL (u) attached to each event always reflects the current route.

The only caveat is group on SPA navigation pageviews. If your site is a SPA and you want to attribute a content_grouping to every pageview, you have three options today:

  1. Site-wide fixed grouping (simplest). Add ?group=my-site to the script URL. Every pageview — initial and SPA — will carry that same grouping. Limitation: a single value for the whole site, not per route.

  2. Accept the trade-off. Use the canonical pattern (stub + ?auto=0). The first pageview carries the correct grouping; SPA navigation pageviews will appear as (not set) in the dashboard. Acceptable when most of your traffic enters directly (organic, social, paid landing pages).

  3. Wait for ?auto=manual (on the roadmap). A mode where the tracker fires no automatic pageview — initial or SPA — leaving full control to the client. Combined with a GTM History Change trigger, it allows dynamic per-route grouping with no duplicates.

Summary for SPAs

  • If you only care about conversions, microconversions, and custom events, the canonical pattern (stub + ?auto=0) covers everything without limitations.
  • If you also need per-route pageview grouping, use ?group=X with a fixed value today, or wait for ?auto=manual for dynamic per-route grouping.

Choosing the right setup

Your situationRecommended setup
Multi-page site, no advanced GTM needsStandard install (no auto, no stub) — see the GTM Template guide
Multi-page site with content_grouping from dataLayer (typical GTM case)Canonical pattern (stub + ?auto=0 + manual pageview tag)
You only need to queue early conv/micro calls (above-the-fold buttons)Stub without ?auto=0
You cannot edit the HTML <head>?auto=0 + GTM loader tag with s.onload + custom event
SPA with conv/micro only (no dynamic grouping)Canonical pattern — works fully for conv/micro
SPA with a fixed site-wide grouping?group=my-site in the script URL (no ?auto=0 required)
SPA with dynamic per-route groupingWait for ?auto=manual (on the roadmap)

Testing your setup

Verify the integration end to end before you publish the container:

  1. GTM Preview mode. Confirm that the loader tag fires on Initialization and the pageview tag fires on your dataLayer-ready event. Both should appear in the Tags Fired column.
  2. Browser DevTools → Network tab. Filter by sealmetrics. You should see:
    • One request to t.js?id=...&auto=0 (the library).
    • One pixel request per pageview, carrying your group value as a query parameter.
  3. Sealmetrics Real-time dashboard. Open my.sealmetrics.com, check Real-time, and confirm that pageviews arrive with the expected content_grouping. Click through several pages and verify there are no duplicates.

If you see two pageviews per load, you are hitting the anti-pattern — your script URL is missing ?auto=0.

If you see zero pageviews, your manual pageview tag is not firing. Re-check its trigger and confirm that {{dlv.content_grouping}} is resolving to a non-empty value in Preview mode.