Install the pixel in any stack
One script tag measures the audience for every website, web app, portal, dashboard, or embedded widget that displays FXMacroData content. It is the same tag everywhere; what changes between frameworks is where you put it and how you hand it route changes. Apps, bots, and assistants have no browser, and use the backend meter instead.
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
No key, no site id, no account number. The pixel identifies your account from the browser Origin, which is why the domain has to be registered in API Management first. An event from an unregistered origin is rejected with origin is not an approved redistribution domain.
1. Configuration attributes
| Attribute | Values | What it does |
|---|---|---|
data-fxmd-meter-mode | page (default), exposure | Whether every page view counts, or only views of a tagged FXMacroData component. See below. |
data-fxmd-attribution-mode | verify (default), inject | verify reports whether your own attribution element is present and visible. inject adds a small fixed badge in the bottom-right corner if none is found. |
data-fxmd-endpoint | An absolute URL | Overrides where events are sent. Only needed if you proxy the collector through your own domain. |
<script
async
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"
data-fxmd-attribution-mode="inject"></script>
The shorter forms data-meter-mode and data-attribution-mode are also accepted, for platforms whose tag editors strip vendor prefixes.
What it sends
A page_view on load and on each route change, a module_view when a tagged component becomes visible, one heartbeat 15 seconds in, and an attribution_click if someone clicks your credit link. Each carries an anonymous visitor id, the path, the viewport, timezone and language, and whether the attribution element was visible. Transport is navigator.sendBeacon with a fetch fallback, so it never blocks rendering and never fails loudly. No personal data, no cross-site identifier, and nothing about the individual beyond a random id your browser stores.
2. Which domains to register
- Apex and www are one entry. Registering
https://example.comalso acceptshttps://www.example.com, and the reverse. - Every other subdomain is its own entry.
app.example.com,portal.example.com, anddashboard.example.comeach need registering. Adding a domain never adds a licence fee. - The scheme and port matter. Register the origin you actually serve. A non-standard port is part of the origin.
- Register staging too if you want to test there, or accept that staging events are rejected. Rejected events are not billed, so an unregistered staging site is harmless, just invisible.
- Localhost is not registerable. Verify on a deployed origin.
The site identity in your reporting is the hostname the page was served from, so a single licence covering five subdomains still shows you five rows and one combined billable total.
3. Page mode vs exposure mode
Page mode (default)
Every human page view counts the visitor. Correct when the pixel only loads on pages that show FXMacroData content: a rates page, a calendar page, a set of market templates.
Do not put a page-mode pixel in a global layout on a site where most pages have no FXMacroData content. You will meter your marketing traffic.
Exposure mode
Only a view of a component you have tagged counts. Correct for a single-page app or a global layout, where you load the pixel once and let the tagging decide.
Exposure mode without tagging measures nobody. If you choose it, you must tag; the two halves are one decision.
There is a safety net: if an exposure-mode site records no tagged component views at all in a period, billing falls back to counting its human page views, so an untagged site is never silently metered at zero. It is a backstop, not a plan. Once you tag correctly the fallback stops applying and your marketing-page visitors go back to being uncounted, which is the whole point of choosing exposure mode.
4. Tagging the data components
Put both attributes on the outermost element of each FXMacroData-powered component. The pixel watches it with an IntersectionObserver and reports the first time at least 10% of it enters the viewport, once per route.
<section data-fxmd-metered data-fxmd-module="release-calendar">
<!-- your rendering of the FXMacroData calendar -->
</section>
<div data-fxmd-metered data-fxmd-module="usd-cpi-chart">...</div>
<aside data-fxmd-metered data-fxmd-module="rate-differentials">...</aside>
- Both attributes are required.
data-fxmd-modulealone names a component in the page payload but does not meter it;data-fxmd-meteredis what arms the observer. - Module names are lowercase, up to 64 characters, from
a-z 0-9 _ . : -. Anything else is dropped silently, which looks exactly like nothing happening. Keep them stable: they are how you read your own usage later. - Content added later is picked up. A MutationObserver rescans on DOM changes and on class, style, hidden, and aria-hidden changes, so tabs, accordions, modals, and lazy-loaded panels all work without extra code.
- Up to 16 module names are collected per event. Tag components, not every row in a table.
5. The JavaScript API
When a component is drawn to a canvas, rendered inside a web component, or only exists after a user action, tag it in code instead.
// Report a module view explicitly.
window.FXMacroDataPixel.trackModule("release-calendar");
// Options: dedupe defaults to true (once per module per route).
window.FXMacroDataPixel.trackModule("usd-cpi-chart", {
dedupe: false,
source: "chart-render",
element_label: "USD CPI, 12m",
});
// Read the current anonymous context, e.g. for your own debugging.
window.FXMacroDataPixel.getContext();
// { anonymous_visitor_id, anonymous_session_id, site_id, meter_mode, meter_version }
The script is loaded async, so it may not have booted when your code runs. Use the queue and nothing is lost:
window.FXMacroDataPixel = window.FXMacroDataPixel || { q: [] };
window.FXMacroDataPixel.q.push(["trackModule", "release-calendar"]);
Queued commands run as soon as the pixel initialises, and the same q.push form keeps working afterwards.
6. The attribution link
The licence requires visible FXMacroData credit wherever the data appears. Mark your own element and the pixel confirms it is present, on screen, and not hidden by CSS.
<a href="https://fxmacrodata.com/?utm_source=example.com&utm_medium=partner"
data-fxmd-attribution>Data powered by FXMacroData</a>
The pixel reports the element position, size, text, and whether it was visible in the viewport at the time of the event. It never moves or restyles your element. Set data-fxmd-attribution-mode="inject" only if you would rather we add a small fixed badge for you; a real credit inside your own layout is better for both sides.
7. Single-page frameworks
Route changes are handled for you. The pixel wraps history.pushState and history.replaceState and listens for popstate and hashchange, so every client-side navigation produces a fresh page view and rescans for tagged components. You do not need a router hook. Load the script once in the shell, choose exposure mode, and tag.
Plain HTML
<!-- before </body>, on pages that display FXMacroData content -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
React (Vite, Create React App)
<!-- index.html -->
<script
async
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
// ReleaseCalendar.tsx
export function ReleaseCalendar({ events }) {
return (
<section data-fxmd-metered data-fxmd-module="release-calendar">
{events.map((e) => <EventRow key={e.id} event={e} />)}
</section>
);
}
Next.js, app router
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
strategy="afterInteractive"
data-fxmd-meter-mode="exposure"
/>
</body>
</html>
);
}
App router navigation uses the History API, so route changes are picked up without a usePathname effect. Keep strategy="afterInteractive"; beforeInteractive would run the pixel before your app renders anything to observe.
Next.js, pages router
// pages/_document.tsx
<Head>
<script
async
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"
/>
</Head>
Vue 3
<!-- index.html -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
<!-- ReleaseCalendar.vue -->
<template>
<section data-fxmd-metered data-fxmd-module="release-calendar">
<EventRow v-for="e in events" :key="e.id" :event="e" />
</section>
</template>
Nuxt 3
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [{
src: "https://fxmacrodata.com/static/js/fxmacrodata-pixel.js",
async: true,
"data-fxmd-meter-mode": "exposure",
}],
},
},
});
Angular
<!-- src/index.html, before </body> -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
<!-- release-calendar.component.html -->
<section data-fxmd-metered data-fxmd-module="release-calendar">
<app-event-row *ngFor="let e of events" [event]="e"></app-event-row>
</section>
SvelteKit
<!-- src/app.html, inside %sveltekit.body% wrapper -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
Astro
---
// src/layouts/Base.astro
---
<slot />
<script is:inline async
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
is:inline stops Astro from bundling and rewriting the tag, which would strip the data attributes.
Remix and React Router
// app/root.tsx
<body>
<Outlet />
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure" />
<Scripts />
</body>
Gatsby
// gatsby-ssr.js
export const onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script key="fxmd" async
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure" />,
]);
};
Solid, Qwik, Preact, Alpine, htmx
<!-- Same tag in the shell HTML. Anything that navigates through the
History API or replaces DOM in place is already handled. -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
8. Server-rendered frameworks
With classic server rendering you have a real choice: put the tag in a partial included only by the templates that show FXMacroData content and stay in page mode, or put it in the base layout and switch to exposure mode. The first is simpler and usually more accurate.
Django
{# templates/partials/fxmd_pixel.html #}
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
{# templates/markets/calendar.html #}
{% include "partials/fxmd_pixel.html" %}
Flask / Jinja
{# templates/base.html #}
{% block fxmd_pixel %}{% endblock %}
{# templates/calendar.html #}
{% block fxmd_pixel %}
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
{% endblock %}
Ruby on Rails
<%# app/views/layouts/application.html.erb %>
<%= yield :fxmd_pixel %>
<%# app/views/markets/calendar.html.erb %>
<% content_for :fxmd_pixel do %>
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
<% end %>
Laravel / Blade
{{-- resources/views/layouts/app.blade.php --}}
@stack('fxmd')
{{-- resources/views/markets/calendar.blade.php --}}
@push('fxmd')
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
@endpush
ASP.NET Core, Razor
@* Views/Shared/_Layout.cshtml *@
@await RenderSectionAsync("FxmdPixel", required: false)
@* Views/Markets/Calendar.cshtml *@
@section FxmdPixel {
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
}
Spring Boot, Thymeleaf
<!-- templates/fragments/fxmd.html -->
<script th:fragment="pixel" async
src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
<!-- templates/calendar.html -->
<div th:replace="fragments/fxmd :: pixel"></div>
Express with EJS, Handlebars, Pug
<%- include('partials/fxmd-pixel') %> <!-- EJS -->
{{> fxmd-pixel}} <!-- Handlebars -->
script(async, src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js") //- Pug
Plain PHP
<?php if ($page_shows_fxmd_data): ?>
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
<?php endif; ?>
9. CMS and no-code platforms
Most hosted platforms give you a custom-code box for the site head or footer. Paste the tag there, then decide the mode: site-wide box plus exposure mode plus tagging, or a page-level box on just the data pages plus page mode.
WordPress, no plugin
// functions.php in a child theme
add_action('wp_footer', function () {
if (!is_page(['market-calendar', 'rates'])) return; // data pages only
echo '<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>';
});
WordPress, site-wide
<!-- Any header/footer scripts plugin, or theme options -> custom code -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
<!-- then wrap the shortcode/block output -->
<div data-fxmd-metered data-fxmd-module="release-calendar">[fxmd_calendar]</div>
Shopify
<!-- layout/theme.liquid, before </body> -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
Use the theme file, not a Custom Pixel in the Customer Events sandbox: the sandbox has no access to your DOM, so tagged components would never be seen.
Webflow
Project Settings -> Custom Code -> Footer Code
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
Then on the wrapper element: Settings -> Custom attributes
data-fxmd-metered = (leave blank)
data-fxmd-module = release-calendar
Squarespace
Settings -> Advanced -> Code Injection -> Footer
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
Per page: Page Settings -> Advanced -> Page Header Code Injection
Wix
Settings -> Custom Code -> Add Custom Code
Place in: Body - end
Load on: All pages, or Choose specific pages
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
Ghost
Settings -> Code injection -> Site footer
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"
data-fxmd-meter-mode="exposure"></script>
Drupal
// mytheme.theme
function mytheme_page_attachments_alter(array &$attachments) {
$attachments['#attached']['html_head'][] = [[
'#tag' => 'script',
'#attributes' => [
'src' => 'https://fxmacrodata.com/static/js/fxmacrodata-pixel.js',
'async' => TRUE,
'data-fxmd-meter-mode' => 'exposure',
],
], 'fxmd_pixel'];
}
Joomla, HubSpot, Framer, Bubble, Webnode
Site settings -> custom head or footer code -> paste the tag.
Add the two data attributes to the container element in whichever
element-settings panel the platform provides.
10. Static site generators
Hugo
<!-- layouts/partials/fxmd-pixel.html -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
<!-- layouts/_default/baseof.html -->
{{ if .Params.fxmd }}{{ partial "fxmd-pixel.html" . }}{{ end }}
Jekyll
<!-- _includes/fxmd-pixel.html -->
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
<!-- _layouts/default.html -->
{% if page.fxmd %}{% include fxmd-pixel.html %}{% endif %}
Eleventy
<!-- _includes/layouts/base.njk -->
{% if fxmd %}
<script async src="https://fxmacrodata.com/static/js/fxmacrodata-pixel.js"></script>
{% endif %}
Docusaurus, MkDocs, VitePress
// docusaurus.config.js
scripts: [{
src: "https://fxmacrodata.com/static/js/fxmacrodata-pixel.js",
async: true,
"data-fxmd-meter-mode": "exposure",
}]
11. Tag managers
Google Tag Manager
Tag type: Custom HTML
<script>
var s = document.createElement("script");
s.async = true;
s.src = "https://fxmacrodata.com/static/js/fxmacrodata-pixel.js";
s.setAttribute("data-fxmd-meter-mode", "exposure");
document.body.appendChild(s);
</script>
Trigger: All Pages (exposure mode), or a Page View trigger
limited to your data pages (page mode).
Build the element in code rather than pasting a raw script tag: GTM strips unknown attributes from injected tags, which would silently drop your mode setting. Do not tick Support document.write.
Segment, Tealium, and similar
// Any "custom JavaScript" destination, loaded once per page:
!function(){
var s = document.createElement("script");
s.async = true;
s.src = "https://fxmacrodata.com/static/js/fxmacrodata-pixel.js";
s.setAttribute("data-fxmd-meter-mode", "exposure");
document.body.appendChild(s);
}();
A tag manager adds a layer that can be misconfigured, blocked by consent tooling, or deployed late. If you control the site template, put the tag in the template.
12. CSP, consent, and privacy
Content Security Policy
script-src 'self' https://fxmacrodata.com;
connect-src 'self' https://fxmacrodata.com;
connect-src is the one people forget. Without it the script loads, runs, and every beacon is blocked, so the pixel looks installed and measures nothing. If you use nonces, put the nonce on the script tag; the pixel adds no inline script of its own.
What it stores on the visitor device
| Key | Where | Lifetime | Contents |
|---|---|---|---|
fxmd_partner_vid | Cookie, first-party, SameSite=Lax, Secure on HTTPS | 395 days | A random id generated in the browser |
fxmd_partner_vid | localStorage | Until cleared | The same random id, so a cleared cookie does not double-count |
fxmd_partner_sid | sessionStorage | The tab session | A random session id |
All three are first-party to your domain and meaningless anywhere else. Nothing is read from or written to any other site, there is no cross-site identifier, and no advertising use. If storage is unavailable the pixel falls back to an in-memory id for that page view, which over-counts rather than under-counts.
Consent
This is a licence-compliance meter, not analytics or advertising, and most operators treat it the way they treat any first-party measurement they are contractually obliged to run. Where your counsel decides it needs consent, gate the tag the same way you gate the rest, and understand the trade: an unconsented visitor is not metered, is not billed, and is not visible in your reporting.
Disclose it in your privacy policy the way you disclose other first-party measurement: the cookie name, the retention, and that it counts unique readers for a data licence and nothing else.
Ad blockers and network filtering
Some visitors block third-party scripts by hostname. That under-counts you, which is not a problem for us. If it is a problem for your own reporting, serve the file from your own domain and point the collector back with data-fxmd-endpoint="https://example.com/fxmd-collect", proxying to https://fxmacrodata.com/fxmacrodata-pixel/v1/events and preserving the Origin header. Do not modify the script itself: a changed meter is not a meter.
13. Verify and troubleshoot
Open a data page in a normal browser, then check the Network tab for a POST to /fxmacrodata-pixel/v1/events returning 200. Then open the redistribution panel in API Management: it shows registered surfaces, combined users, pixel status, and the estimated next invoice. Pixel status turns active after the first accepted event.
| Symptom | Cause | Fix |
|---|---|---|
origin is not an approved redistribution domain | The hostname is not registered, or you registered a different subdomain | Add the exact origin in API Management. Apex and www alias each other; nothing else does. |
| No network request at all | The script was not loaded, or a bundler rewrote the tag and dropped the attributes | Check the tag in view-source, not in your source file. Astro needs is:inline; GTM needs the createElement form. |
| Script loads, requests blocked | connect-src missing from your CSP | Add https://fxmacrodata.com to connect-src. |
| Events accepted, zero users counted | Exposure mode with no tagged components | Add data-fxmd-metered data-fxmd-module="..." to the data containers, or switch to page mode. |
| Module views never fire | Module name has uppercase, spaces, or is over 64 characters, or only one of the two attributes is present | Lowercase, a-z 0-9 _ . : -, both attributes on the same element. |
| Counted far higher than expected | Page mode in a global layout across a whole marketing site | Switch to exposure mode and tag, or load the tag only on data pages. |
| Visitors classified as bots | Automated tests, headless browsers, prefetch or preview fetches, or a burst of events from one visitor | Expected. Bot traffic is never billed. Verify with a real browser. |
| Attribution reported as not visible | The element is hidden, zero-size, fully transparent, or below the fold at event time | Put the credit where a reader can see it on the data screen. |
Related
- Commercial Redistribution setup - the licence, the billing meter, and the checklist.
- App and bot meter - for mobile apps, desktop apps, extensions, bots, and assistants.
- API reference and rate limits - for the data calls themselves.
- Commercial Redistribution Terms - server-side key requirement, no API mirroring, no marketplace resale.