docs
/
AI skills

Google Analytics, the Facebook Pixel and site verification

A GA4 measurement ID and a Meta Pixel ID on the whole site or on one page, the Google and Bing verification tags, and how to see each one fire. Nothing waits for cookie consent unless you gate it.

Use this to put Google Analytics (GA4) and the Meta (Facebook) Pixel on an Appmint-hosted site, and to claim the site in Google Search Console and Bing Webmaster Tools. There are two IDs, set once for the site and optionally replaced on one page, plus two verification codes. Appmint writes the tags into every page for you.

The tags fire for every visitor, before any cookie consent. Appmint does not hold GA or the Pixel back until a visitor accepts cookies. In a test, a fresh visitor got the _ga cookie and a Pixel PageView on first load, with a consent banner still on screen. If the site needs consent first (visitors in the EU, UK and similar), use step 5, and make the consent banner with Site notice: banner, popup, age gate or cookie consent.

For cookies of your own (UTM source, a referral code), see Site cookies from the address, with consent.

Ask first

  • Which site? Tracking is set per site.
  • The GA4 measurement ID. Ask for it, don't guess. It's in Google Analytics › Admin › Data streams › the web stream, and looks like G-ABC123XYZ. A Universal Analytics UA-…, Google Ads AW-… or Google tag GT-… ID also works the same way.
  • The Pixel ID. It's in Meta Events Manager › Data sources, a number like 123456789012345.
  • The whole site, or one page? One page can use its own IDs (a campaign landing page, for example). Everywhere else keeps the site's IDs.
  • Does it need cookie consent first? If yes, use step 5 instead of the plain fields for the ID that has to wait.
  • Verification codes. For Search Console use the HTML tag method, and for Bing use the meta tag method. You need only the content value, e.g. abc123XYZ…, not the whole <meta> tag.

Steps

1. Site Config: the IDs for the whole site

In Studio, open /app/welcome › Site Config › Tracking & Social, or open the site switcher in Build Studio's header › Site Config. Fill in Google Analytics and Pixel, then Save changes. They are stored on the site record under data.tracking (websitemint site-config-form.tsx 938–948).

Field (Studio label)Stored asWhat goes inWhat the site does with it
Google Analyticstracking.googleAnalyticG-ABC123XYZ, or another Google tag IDLoads https://www.googletagmanager.com/gtag/js?id=<ID> and runs gtag('js', new Date()); gtag('config', '<ID>')
Pixeltracking.pixelthe Pixel IDRuns Meta's base code (loads fbevents.js), then fbq('init', '<ID>'); fbq('track', 'PageView')
Google site verificationtracking.googleSiteVerificationthe content value onlyStep 2
Bing site verificationtracking.bingSiteVerificationthe content value onlyStep 2
Cookiestracking.cookiessee Site cookies

How the IDs are cleaned (base-app app/[[...slug]]/page-assets.ts 89–110, every row tested):

TypedUsed asWhy
G-TEST123G-TEST123already right
test123G-TEST123no known prefix, so G- is added and the ID is upper-cased
g-partial1G-PARTIAL1upper-cased
ua-12345-1UA-12345-1known prefix (G, UA, AW, GT, GTM, DC), upper-cased
G-ABCG-ABCspaces around it are trimmed
12 34<x>, or anything with a character outside A–Z a–z 0–9 _ -, or over 64 charactersnothingdropped without a word. No tag at all is written for that field

The Pixel ID is only trimmed and checked against the same character rule. It isn't changed.

For the AI: read the site with repo_find_one on site, then write repo_update on site with patch: { "tracking": { …the whole existing tracking object…, "googleAnalytic": "G-…", "pixel": "…" } }. repo_update merges only one level deep, so a tracking object without cookies or the verification codes deletes them. Resend everything that was there (appengine repository.skill.ts 171–178).

Hands on: the IDs as stored. Check: repo_find_one on the site shows data.tracking.googleAnalytic and data.tracking.pixel, and nothing else under tracking was lost.

Stop: these tags go live on every page of the site the moment the site is saved, and they send each visitor's page views to Google and Meta. Show the person the IDs and which site, and wait for a yes. If they need consent first, do step 5 instead.

2. Site Config: Google and Bing verification

In the same Tracking & Social card, fill in Google site verification and Bing site verification. The site writes them as <meta name="google-site-verification" content="…"> and <meta name="msvalidate.01" content="…"> in the head of its pages (base-app meta.tsx 202–210). Then press Verify in Search Console or Bing.

  • These are site-level only. A page can't carry its own verification codes.
  • Paste only the content value. A whole <meta …> tag is written into the tag's content attribute as text, and the check fails.
  • They must be under tracking. base-app's own copy of the site model also declares seo.googleSiteVerification and seo.bingSiteVerification (lib/models/site.ts 625–632), but nothing reads those. Studio's form and its Classic form both write tracking.*, which is the right place.

Hands on: nothing new. Check: open the home page's source (view-source:https://<site host>/) and find both meta tags. Then press Verify in Search Console and Bing.

3. Build Studio: one page with its own IDs (optional)

A page can carry its own tracking.googleAnalytic, tracking.pixel and tracking.cookies. Each field is chosen separately: a page with only its own googleAnalytic still uses the site's Pixel. An empty or invalid page value falls back to the site's (page-assets.ts 122–123).

Don't set a page's tracking in Build Studio. Page settings › Page Config shows a Tracking group, but it saves the value under a key literally named tracking.googleAnalytic instead of inside tracking (websitemint build-studio-property.tsx 126, with the field path from form-render.tsx 139). The site never reads that key. Tested: a page saved this way, by create or by a full update, rendered with no GA tag and no Pixel. Set it through the API instead.

Write it one of these ways (both tested):

  • repo_update on page with patch: { "tracking": { "googleAnalytic": "G-LANDING1", "pixel": "…", "cookies": [ …any it already has… ] } }. Again, send the whole object.
  • Raw HTTP: POST /repository/update-partial/page/<sk> with dot-paths, e.g. { "data.tracking.googleAnalytic": "G-LANDING1" }. This changes only that field. Never send { "data": { … } }, which replaces the page's whole data.

Where a page's own tracking counts:

URLWhose tracking is used
An ordinary pagethe page's own fields, then the site's
A URL a Site Feature owns (/store, /blog, /book, …)the page the feature borrows as its frame, not a stub page at that URL (base-app app/[[...slug]]/layout.tsx 58–68)
/login, /register, /account/…, /checkout/…, /order/…the site's, plus the My Account layout page's (or account-layout's) own. With neither layout page, none load there (lib/resolve-layout-page.ts 45–65)
A 404the site's

A parent page's or root's own tracking is not passed down to the pages inside it. Only the page itself counts.

Hands on: the page's id and its IDs. Check: repo_find_one on the page shows data.tracking.googleAnalytic as an object field, not a top-level data["tracking.googleAnalytic"] key.

Stop: as in step 1, the page sends data the moment it is saved.

4. Check it fired

Do all three. A tag that is in the page can still fail to send.

  1. Page source. view-source:https://<site host>/<page> should contain gtag/js?id=G-…, gtag('config','G-…'), and fbq('init','<pixel>') with fbq('track','PageView'). If one is missing, the ID was dropped by the rule in step 1, or it was saved in the wrong place (step 3).
  2. The network. In DevTools › Network, reload. GA sends https://www.google-analytics.com/g/collect?…tid=G-… and the Pixel sends https://www.facebook.com/tr/?id=<pixel>&ev=PageView. Both were seen in a local test.
  3. The vendors' own tools.
    • In Google Analytics, use Admin › DebugView. It shows events only from a browser in debug mode: open the site through Tag Assistant (tagassistant.google.com), or use Reports › Realtime.
    • For Meta, use the Meta Pixel Helper Chrome extension, or Events Manager › your Pixel › Test events.

One broken line of page code silences all of it. The page's JavaScript, root's and the site's are joined into one <script> with the GA config, the cookies and the Pixel at the end (page-assets.ts 44–60, 121–143). A syntax error anywhere, or an error thrown at the top level, stops everything after it. Tested: a page whose script called document.getElementById('nope').addEventListener(…) still loaded gtag.js, but sent no config, no hit, no Pixel and set no cookies. When tags are in the source but nothing is sent, look for a red error in the console first.

Appmint can't hold back the tags it writes, so a site that needs consent first does it in code. This needs a cookie-consent site notice: its Accept All sets localStorage.site_cookie_consent to 'true' and fires a site-cookie-consent event on window. Reject sets 'minimal' (see Site notice).

  • GA: keep the field, use Google Consent Mode. root's JavaScript runs before the GA tag in that one script (root, then parent, then page, then site, then tracking: page-assets.ts 44–60). So a consent default set in root's Page settings › Style › JavaScript is in place before config.
  • Pixel: leave the Pixel field empty and load the Pixel from the same script after consent. Meta's base code quits if fbq already exists, so it can't be "pre-blocked" the way GA can.

Tested on a page with a consent notice: before Accept, GA sent only consent-denied pings (gcs=G100), with no _ga cookie and no Pixel. Right after Accept, _ga was set and the Pixel sent PageView. On the next load GA sent normal hits (gcs=G111).

(function () {
  if (document.body && document.body.getAttribute('data-design-mode') === 'true') return;
  var ok = false;
  try { ok = localStorage.getItem('site_cookie_consent') === 'true'; } catch (e) {}
  // Google Consent Mode default: set before the site's own gtag('config') runs.
  window.dataLayer = window.dataLayer || [];
  window.gtag = window.gtag || function () { window.dataLayer.push(arguments); };
  var state = ok ? 'granted' : 'denied';
  window.gtag('consent', 'default', { ad_storage: state, analytics_storage: state, ad_user_data: state, ad_personalization: state });
  // The Pixel, loaded here (Tracking › Pixel left EMPTY) so it can wait.
  function loadPixel() {
    if (window.fbq) return;
    !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
    window.fbq('init', '123456789012345');
    window.fbq('track', 'PageView');
  }
  if (ok) loadPixel();
  window.addEventListener('site-cookie-consent', function () {
    window.gtag('consent', 'update', { ad_storage: 'granted', analytics_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted' });
    loadPixel();
  });
})();

Put it in root's Page settings › Style › JavaScript (see Shared header and footer) so it covers every page. Write it with page_write on root's id, and check that it parses first, because it shares one script with everything else. Root's script reaches login, account and checkout only while a My Account layout page or account-layout exists.

  • Consent Mode still sends cookieless pings. To send nothing to Google before consent, leave Google Analytics empty as well. Then load https://www.googletagmanager.com/gtag/js?id=G-… and call gtag('js', new Date()); gtag('config', 'G-…') yourself, in a function that runs only after consent, the way loadPixel does.
  • Without a cookie-consent notice on the site, nobody can ever accept, and everything stays denied.

Hands on: nothing new. Check: open the site in a private window. Before you accept, there is no _ga cookie, no facebook.com/tr request, and GA hits carry gcs=G100. After Accept All, _ga appears and the Pixel's PageView is sent.

Stop: root's script runs on every page. Show the person the script and wait for a yes before saving it.

When it's done

Tell the person:

  • which IDs are on the site and which page (if any) has its own;
  • whether the verification tags are in;
  • whether the tags wait for consent or fire for everyone;
  • what you saw fire: the source, g/collect, tr?ev=PageView.

Point them to GA's Realtime report and Meta's Test events to watch it themselves.

What it can't do yet

  • No consent gating of its own. The tags fire on load for everyone (page-assets.ts 121–143). Step 5 is the workaround.
  • Page views only. GA gets its automatic page_view and the Pixel gets PageView. No add-to-cart, purchase, lead or other event is sent by Appmint. A page can call gtag('event', …) or fbq('track', …) from its own JavaScript.
  • Once per full page load. The tags run in the server-rendered document. A client-side route change doesn't run them again, and doesn't switch to another page's own IDs. A notice link to a page on the same site (router.push) and some built-in screens change pages this way. The Pixel sends no PageView for those. GA counts them only if its own Enhanced measurement › Page changes based on browser history events is on. Plain links and the runtime's navigate: effect are full loads (base-app runtime/dispatcher.ts 447–448).
  • Not the Tag Manager snippet. A GTM-… ID is loaded through gtag/js with a config call, not Google Tag Manager's own gtm.js snippet. Untested with a real container. Put a GTM container's standard snippet in root's JavaScript instead.
  • Page settings can't set a page's tracking (step 3 warning). Use the API.
  • One ID of each kind. A second GA property or Pixel has to be added in code: gtag('config', 'G-SECOND') or fbq('init', '…').

Don'ts

  • Don't paste the whole <script> snippet from Google or Meta into the field. Paste only the ID.
  • Don't paste a whole <meta> tag into a verification field. Paste only its content.
  • Don't send a partial tracking object with repo_update. It wipes the cookies and verification codes you didn't resend.
  • Don't set a page's tracking in Build Studio's Page Config. It never applies.
  • Don't put the IDs in a <script> in the page HTML. Script in page HTML doesn't run there.
  • Don't tell an EU or UK business it is "consent-ready" because it has a cookie banner. The banner doesn't stop these tags unless you did step 5.
  • Don't use real IDs to test on someone else's site, and don't leave test IDs on a live site.