D
P
0
← All articles Baca dalam Bahasa Indonesia

JavaScript, DOM & Browser Animation

Duplicate Meta Pixel PageView in GTM? A Plain `fbq('track','PageView')` Fires for Every Initialized Pixel

· · 8 min read
Duplicate Meta Pixel PageView in GTM? A Plain `fbq('track','PageView')` Fires for Every Initialized Pixel

The new Meta pixel installed for the community landing page passed its retest on 30 September 2026 with exactly the numbers it was meant to produce: 1 PageView on that page, and 0 everywhere else. The ads specialist on the client side had set it up that same day through a Custom HTML tag that sends a PageView with trackSingle, only on that page's path.

The two old pixels on the same site were not that tidy. My draft for the ads specialist that same day carried a finding I had marked pre-existing, meaning it was already there: each old pixel logged 2 PageViews per page, and one of them logged 3 on the community landing page.

Every change to the GTM container in this case was the ads specialist's work, while my part was testing, diagnosis and drafts. I will call the four pixels on the community landing page the main pixel, old pixel A, old pixel B and the new pixel.

track does not pick a pixel

The cause sat in the main pixel's base code tag. That tag called a plain fbq("track","PageView"), and it fired after the template tags had initialized old pixel A and old pixel B.

On a page, fbq is a single global function. fbq('init', id) registers a pixel, and fbq('track', event) sends the event to every pixel already registered, regardless of which tag registered it. For one pixel only, there is fbq('trackSingle', id, event). The trap is easy to install because Meta's own standard base code snippet ends with fbq('track', 'PageView'), which is correct on a single-pixel site and turns into a broadcast as soon as other pixels have been initialized first. In simplified form, with the pixel IDs replaced by placeholders:

// Simplified. First, the template tags initialize the two old pixels.
fbq("init", "OLD_PIXEL_A");
fbq("init", "OLD_PIXEL_B");
 
// Then the main pixel's base code tag, before the fix:
fbq("init", "MAIN_PIXEL");
fbq("track", "PageView");                        // reaches all three pixels
 
// After the fix:
fbq("trackSingle", "MAIN_PIXEL", "PageView");    // main pixel only

The fix proposed in that draft was to use trackSingle in that base code tag. The ads specialist installed it in the next container version, and the re-measurement recorded 1 PageView per pixel on every page. The count dropped from 2 to 1 once that tag was fixed, so one of those two PageViews really was riding along on the plain call. But the same sentence in my notes is immediately followed by an exception.

The leftover on two URLs: a setup tag that fired twice

Old pixel B, the one that had logged 3 on the community landing page, still logged 2 PageViews there, and only there. The cause was the new pixel's tag added that day: that Custom HTML tag used tag sequencing, with old pixel B's PageView tag as its setup tag.

In GTM, a setup tag runs right before its main tag every time the main tag fires, separately from its own triggers. Old pixel B's PageView tag already fired on every page through its trigger, and on the community landing page it was called once more as a setup tag, so there it fired twice. The three PageViews were two from that tag firing twice plus one from the plain call in the base code tag, and each fix removed one:

PageViews for old pixel B on the community landing page
 
before any fix                                   3
after trackSingle in the base code tag           2
after setting the setup tag to "Once per page"   1

My suggestion for this leftover was to drop the setup tag and paste the self-guarding pixel loader line into the new pixel's tag. Meta's standard loader line opens with the check if(f.fbq)return;, so a tag carrying it does not need to wait for another tag to load the library.

The ads specialist went a different way: old pixel B's PageView tag was set to "Once per page" in the following container version. The re-measurement recorded 1 PageView per pixel on every page, including both community landing page URLs, and the new pixel still got its PageView because its tag still fired, with the setup tag kept in place. The retest on 1 October, on a newer container version, recorded 4 PageViews on the community landing page, one per pixel, and 3 on the other landing page. Where this differs from my suggestion is the dependency: the new pixel's tag still waited on a tag that belonged to old pixel B, and that comes back at the end.

Proving it with a setter on window.fbq

The initial proof was to log every fbq() call through a setter on window.fbq installed with addInitScript, while the hits to Meta were faked. Because addInitScript runs before any of the page's own scripts, the function the base code assigns is wrapped immediately, and every call is recorded with its arguments, including track calls that carry no pixel ID at all. A simplified version:

// Simplified, not the original script. Records every fbq() call.
await page.addInitScript(() => {
  const calls = (window.__fbqCalls = []);
  let wrapped;
  Object.defineProperty(window, "fbq", {
    configurable: true,
    get: () => wrapped,
    set(fn) {
      wrapped = new Proxy(fn, {
        apply(target, self, args) {
          calls.push(args.slice(0, 3));
          return Reflect.apply(target, self, args);
        },
      });
    },
  });
});

Counting the hits has a trap of its own. The Playwright MCP browser in use has a persistent profile and restores the last tabs when it relaunches after browser_close, and a context-level route counts the hits from those old tabs too. On 30 September the result was worse than an inflated number: a test that ended on a stubbed button destination URL came back on the next launch without routes, loaded the real page, and sent 1 real PageView to the new pixel. That was a mistake on my side, and the heads-up went to the ads specialist in the Slack thread that same day. Since then every run starts by closing stray tabs, only hits whose req.frame().page() is the test page are kept, and every tracking test ends with page.goto('about:blank') before the routes are removed or the browser is closed.

// Simplified. A context-level route, but only hits from the test tab count.
for (const p of context.pages()) if (p !== page) await p.close();
 
const hits = [];
await context.route(/facebook\.com\/tr/, (route, req) => {
  if (req.frame().page() === page) hits.push(req);
  return route.fulfill({ status: 200, body: "" });
});
 
// ... open the page, act, read hits and window.__fbqCalls ...
 
await page.goto("about:blank");   // leave the stubbed URL first
await context.unrouteAll();

The last trap is nastier because the result looks like a diagnosis. Every live pixel's config carries Meta's botblocking list, which drops events from a user agent containing HeadlessChrome. A headless run without a normal UA shows a single PageView, to old pixel A, which has no config, so tracking looks broken and the one pixel that seems to work is the dead one. The countermeasure is to give the browser the UA of the same Chrome in normal mode.

What trackSingle cannot control

A code reading of the container on 6 October, without executing anything in a browser, rejected two suspicions. The GTM snippet really was printed twice in the head, but a once-only guard drops every later gtm.js event, so the duplicate PageView did not come from there. Cross-posting between pixels was rejected too, because every call in the container was by then trackSingle or trackSingleCustom with an ID.

But trackSingle only governs events sent through a tag's call, and Meta's automatic events do not go through that call. On a click, the automatic SubscribedButtonClick goes to every pixel. In the 1 October test, a click on the community landing page sent a custom click event to the new pixel, while the other pixels received the automatic SubscribedButtonClick. The new pixel itself also received it on a Join button click, because its config had InferredEvents switched on.

Two old pixels still active

After that, the ads specialist asked for 2 pixels they could not see in Events Manager to be removed. My notes only guess that these are old pixel A and old pixel B. The answer in the draft: do it in GTM only, pause the pixels rather than delete them, and drop the new pixel tag's sequencing on old pixel B's PageView tag first.

This is where the dependency comes back, because the new pixel's tag only ran after its setup tag had reported. Tag sequencing in GTM has an option not to fire the main tag if its setup tag fails or is paused, so removing the sequencing first keeps the new pixel from being stopped along with it.

The two old pixels are not equivalent either. Old pixel A only received an empty config from Meta, which makes it a dead pixel. Old pixel B was a live dataset, with Event Setup Tool rules that derive Lead from "continue", "submit" and "subscribe" buttons across the whole site. As of 1 October both were still active and not paused, and as of 6 October the request to pause them had not been carried out. As of that date, the duplicate PageView was fixed and the cleanup was not.

What is worth remembering from this case fits in one sentence: on a page with several pixels, an fbq('track', ...) without an ID is a broadcast to every pixel already initialized, and a PageView count only means something once it is split per pixel.