Explore — Google Analytics 4

Give Miso the reader id GA4 already has, and send a click on a related question to GA4 as an event.

Explore lists related questions under an article, and a reader who picks one lands on your answers page. Below we join GA4 and Miso on one reader id, then report the pick as a GA4 event, so the path from article to answer shows in one GA4 session.

You need gtag.js and the SDK on the page. See Explore — Quick Start for the SDK install.


One reader id on both sides

GA4 knows every browser by a client_id, and a signed-in reader by a user_id on top. Miso has the same pair: anonymous_id for the browser and user_id for the reader. Give Miso the GA4 values for both, and a report in either system can be joined to the other, before and after sign-in.

IdGA4Miso
The browser, alwaysclient_id, minted by gtag.jsclient.context.anonymous_id, set to that client_id
The reader, when signed inuser_id, set by youclient.context.user_id, the same value

The SDK reads the ids from client.context at the moment it sends a request, so the handoff runs before the first request. The function below does the handoff. It goes in a script of its own, or at the top of the SDK script:

// If gtag.js has not run yet, define the queue it expects. The calls below then
// wait in dataLayer until it loads. Harmless when the gtag snippet already ran.
window.dataLayer = window.dataLayer || [];
window.gtag = window.gtag || function () { dataLayer.push(arguments); };

// Give Miso the GA4 ids. Resolves true when GA4 answered, false when it did not.
async function ga4Ids(client, measurementId) {
  if (window.READER_ID) {
    client.context.user_id = READER_ID;
    client.context.user_type = "subscriber"; // if you meter, see /operate/metering
  }

  const clientId = await new Promise((resolve) => {
    gtag("get", measurementId, "client_id", resolve);
    setTimeout(() => resolve(null), 2000);
  });
  if (!clientId) return false;

  client.context.anonymous_id = clientId;
  if (window.READER_ID) gtag("config", measurementId, { user_id: READER_ID });
  return true;
}

Three names in there are yours:

  • G-XXXXXXXXXX, the argument you pass as measurementId, is the Measurement ID of your GA4 web data stream. It is under Admin › Data streams › your stream in GA4, and in your existing gtag snippet after gtag/js?id=.
  • READER_ID stands for wherever your page keeps the signed-in reader's id: a global your template writes, a cookie, or your auth library.
  • "subscriber" is the reader's tier, when you meter.

The return value is the all-or-none switch. When gtag.js is blocked, or loads after the two seconds, GA4 never answers with a client_id. The function then returns false and the page skips every GA4 event, so a session is either fully joined or absent from GA4. Miso still receives user_id and its own anonymous_id, and the answer box starts.

With Google Tag Manager there is no gtag.js on the page. Read the browser id from the _ga cookie instead, whose value is GA1.1. followed by the client_id, and send the events through dataLayer and a GTM trigger:

const match = document.cookie.match(/(?:^|; )_ga=GA\d\.\d\.([^;]+)/);
if (match) client.context.anonymous_id = match[1];

Send the click to GA4

A click on a related question emits select on the Explore workflow, with the question the reader chose:

function ga4ExploreEvents(client) {
  client.ui.explore.on("select", ({ question }) => {
    gtag("event", "miso_explore_click", { question: question.text.slice(0, 100) });
  });
}

The click then goes wherever your link function points, usually the answers page. There the same question arrives as a miso_question with question_source: _organic, if that page reports to GA4 too. See Ask — Google Analytics 4.

GA4 keeps parameter values to 100 characters. That is why question is cut at 100. To see it in reports, register it as a custom dimension under Admin › Custom definitions.


Put it together

The id handoff runs first, and start() sends the first request:

const misocmd = window.misocmd || (window.misocmd = []);
misocmd.push(async () => {
  const client = new MisoClient("YOUR_PUBLISHABLE_API_KEY");
  const explore = client.ui.explore;

  const ga4 = await ga4Ids(client, "G-XXXXXXXXXX");
  if (ga4) ga4ExploreEvents(client);

  explore.useApi({ product_id: "art_001" });
  explore.useLink((q) => `/ask.html?q=${encodeURIComponent(q)}`);
  explore.start(); // the first request goes out here
});

Check it

  1. On a test page, add debug_mode: true to the config call in your gtag snippet, so the page reports to DebugView:

    gtag("config", "G-XXXXXXXXXX", { debug_mode: true });
    

    To test a live page without editing it, install the Google Analytics Debugger extension and turn it on. It puts the browser in debug mode for every page.

  2. Load the page, ask a question, click a citation, click Helpful.

  3. In GA4, open Admin › DebugView and pick your device in the top-left list. Each event appears in the timeline with its parameters.


Summary

StepWhat you did
One reader idGA4's client_id is Miso's anonymous_id, and your user_id is on both when signed in.
All or noneNo client_id from GA4 means no GA4 events, and the answer box starts anyway.
Eventsmiso_explore_click

Next