Consent Banner
MarkTag ships with a built-in, brand-agnostic consent banner. Turn it on with a single boolean at init; until the visitor answers it, MarkTag collects and sends nothing.
Enable it
<script>
mtag("init", "{{TAG_DOMAIN}}?tagId={{TAG_ID}}", {
consentBanner: true,
});
</script>The only thing you can configure is the privacy-policy link shown next to the copy — omitted entirely if not set:
mtag("init", "{{TAG_DOMAIN}}?tagId={{TAG_ID}}", {
consentBanner: true,
consentBannerOptions: {
policyUrl: "https://yoursite.com/privacy",
},
});What "won't work until consent" means
With consentBanner: true and no answer yet, MarkTag will not:
- mint or store the visitor id (
_muid) in a cookie or localStorage - send any
/markevent - fetch the tag's event configuration or attach click/page-view listeners
- run session recording
It does still capture utm_* / mtc_* / click-ID params from the landing URL — that's campaign metadata, not a visitor identifier, and nothing built from it is transmitted until consent is granted.
Events fired before the visitor answers
If your page calls mtag('event', ...) before the visitor has answered (e.g. a Purchase event that fires on page load), MarkTag holds up to 512 of them in memory — oldest evicted first once full — and replays them automatically the moment Accept is clicked. Nothing is written to storage or sent over the network while they're held, and the buffer is discarded if the visitor declines.
Appearance
The banner renders inside a Shadow DOM, so the host site's CSS cannot alter it and its own CSS cannot leak out. It's a compact card fixed to the bottom-left corner (a full-width sheet on narrow/mobile screens), always renders in a light theme regardless of the host page's or system's color scheme, and respects prefers-reduced-motion.
Its two actions are Accept and Change preferences — the latter opens a second screen inside the same card with per-category toggles (Analytics, Marketing, Personalization — all on by default) plus a non-toggleable "Necessary — Always active" row, and a single Save preferences button. A close (×) button sits in the top-right corner of both screens; closing without a decision leaves consent pending, so the banner shows again on the next page load. A decision is re-asked after 180 days.
Runtime control
await mtag('consent', 'status'); // 'granted' | 'denied' | 'pending'
await mtag('consent', 'grant'); // record acceptance (e.g. from your own UI)
await mtag('consent', 'deny');
await mtag('consent', 'reset'); // forget the decision so the banner re-asks
mtag('consent', 'show'); // re-open the banner — "Cookie settings" linkConsent is stored client-side only (cookie + localStorage) — nothing about the decision is sent to any server.
Using an external CMP instead
Pass consent: true once your CMP resolves and the built-in banner never appears:
mtag("init", url, { consentBanner: true, consent: myCmp.hasAnalyticsConsent() });Or drive it after the fact with mtag('consent', 'grant' | 'deny').
Existing integrations are unaffected
Without consentBanner: true, behaviour is exactly as described above under Consent Management: the consent boolean is stored as-is and only gates whether /mark events are sent.