Add CLItrail to Material for MkDocs
Where the CLItrail tag goes in Material for MkDocs, in its own idiom, with the consent, CSP and client-side routing details and how to check it works.
#Where the tag goes
Edit overrides/main.html (with custom_dir: overrides in mkdocs.yml). Put the tag on every page that shows your install command; site-wide is best, after your existing analytics tags.
{% extends "base.html" %}
{% block extrahead %}
{{ super() }}
<script defer src="https://YOUR_SERVICE/browser.js" data-website="YOUR_WEBSITE_ID"></script>
{% endblock %}
Turn on the override
theme:
name: material
custom_dir: overrides
#Only after consent
Material's built-in cookie consent lists CLItrail as its own cookie; the script loads the tag only when the visitor accepted it. Leave the override's tag out when you gate on consent.
// Material reloads the page after a consent choice, so one check per page load is enough.
(() => {
const consent = __md_get('__consent');
if (!consent || !consent.clitrail) return;
const tag = document.createElement('script');
tag.src = 'https://YOUR_SERVICE/browser.js';
tag.dataset.website = 'YOUR_WEBSITE_ID';
document.head.append(tag);
})();
Ask for consent and load the script
extra:
consent:
# …your title, description and actions
cookies:
# …your existing cookies
clitrail: CLItrail
extra_javascript:
- javascripts/clitrail-consent.js
#Client-side routing
With navigation.instant, pages change without a reload. Material's document$ emits on every page, including the first (which the tag already recorded).
// Records a visit for each instant-loaded page.
(() => {
let recorded;
const recordVisit = () => {
recorded ??= new URL(performance.getEntriesByType('navigation')[0]?.name ?? location.href).pathname;
const sdk = window.InstallAttribution;
if (!sdk?.ready || location.pathname === recorded) return;
recorded = location.pathname;
sdk.ready = sdk.init({ service: 'https://YOUR_SERVICE', project: 'YOUR_WEBSITE_ID' });
};
document$.subscribe(recordVisit);
})();
#Content Security Policy
The build is static files, so the policy lives in your host's header settings (for example _headers on Cloudflare Pages or Netlify, or vercel.json): add https://YOUR_SERVICE to script-src and connect-src, and blob: to worker-src (only Safari before 26 needs that one).
script-src 'self' https://YOUR_SERVICE;
connect-src 'self' https://YOUR_SERVICE;
worker-src 'self' blob:;
#Verify it works
- Run
mkdocs serve(http://127.0.0.1:8000) or deploy. - Open a page with the tag in Chrome or Firefox (accept analytics first if you gate CLItrail on consent), open the developer console and run
await InstallAttribution.ready. It resolves to{ ok: true, urlId, hasAnalyticsContext, expiresAt }. - In the CLItrail dashboard, open Visits & identities: the visit is listed.
- Anything else names the cause:
disabled(no consent yet, ordata-enabled="false"),opfs_unavailable(the page is not served over https or from localhost),Error(the service refused the visit: add the page's origin, including a development origin such ashttp://localhost:3000, under Settings → Additional domains). IfInstallAttributionis undefined, the tag did not load: check the Network tab and your Content-Security-Policy.
Then run your installer on the same computer and check Install events, or run the hook with --doctor.
#Notes
extra_javascript cannot set data-* attributes, so the tag goes in the extrahead block of a theme override.
Material for MkDocs documentation: squidfunk.github.io/mkdocs-material/customization
Using other install paths or destinations? The setup generator puts this snippet together with your hooks and destination checklist.