{"_id":"@axiomhq/do11y","_rev":"11-69c47554bf25eb7891d063f9ef4edf45","name":"@axiomhq/do11y","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.2":{"name":"@axiomhq/do11y","version":"0.0.2","keywords":["axiom","analytics","documentation","observability"],"license":"MIT","_id":"@axiomhq/do11y@0.0.2","maintainers":[{"name":"lukasmalkmus","email":"mail@lukasmalkmus.com"},{"name":"gabrielelpidio","email":"gabrieldeandradeleal@gmail.com"},{"name":"kevinehosford","email":"kev.e.hosford@gmail.com"},{"name":"bahlo","email":"hey@arne.me"},{"name":"mhr3","email":"michal.mhr@gmail.com"},{"name":"seiflotfy","email":"seif@axiom.co"},{"name":"flbn","email":"oliver@axiom.co"},{"name":"manototh","email":"manototh@gmail.com"},{"name":"islamshehata","email":"thesollyz@proton.me"}],"dist":{"shasum":"78253d6dbe9fd19266fbce6d7aa5e497accc6415","tarball":"https://registry.npmjs.org/@axiomhq/do11y/-/do11y-0.0.2.tgz","fileCount":6,"integrity":"sha512-nK15KwPRT+i+bfewNelRadGROlaTEopY0k54Pg355iNk1JUw358sAvtoxbcMPLaPY6shSfAb7VKD51B7uza3tg==","signatures":[{"sig":"MEUCIBc3+Zy7pkkTEiCVYnVnKyqJuVmNO8dgNBLsw+YC3TYbAiEAm40zmKg5hJ5BiF+FbzY/oSV6dUTtjsCJKdkRlBx4X8w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":83698},"type":"module","gitHead":"c096bc7cc1879378cad91b7c2618a29360506429","scripts":{"lint":"oxlint src","build":"rolldown -c rolldown.config.ts","check":"tsc --noEmit","format":"oxfmt src"},"_npmUser":{"name":"manototh","email":"manototh@gmail.com"},"_npmVersion":"11.12.0","description":"Documentation observability for Axiom","directories":{},"_nodeVersion":"22.13.0","_hasShrinkwrap":false,"devDependencies":{"oxfmt":"latest","oxlint":"latest","rolldown":"latest","typescript":"latest"},"_npmOperationalInternal":{"tmp":"tmp/do11y_0.0.2_1776456542354_0.9010383027162099","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@axiomhq/do11y","version":"0.0.3","keywords":["axiom","analytics","documentation","observability"],"license":"MIT","_id":"@axiomhq/do11y@0.0.3","maintainers":[{"name":"lukasmalkmus","email":"mail@lukasmalkmus.com"},{"name":"gabrielelpidio","email":"gabrieldeandradeleal@gmail.com"},{"name":"kevinehosford","email":"kev.e.hosford@gmail.com"},{"name":"bahlo","email":"hey@arne.me"},{"name":"mhr3","email":"michal.mhr@gmail.com"},{"name":"seiflotfy","email":"seif@axiom.co"},{"name":"flbn","email":"oliver@axiom.co"},{"name":"manototh","email":"manototh@gmail.com"},{"name":"islamshehata","email":"thesollyz@proton.me"}],"dist":{"shasum":"cd5745282eff335011ef82ee9a012eb6495b2936","tarball":"https://registry.npmjs.org/@axiomhq/do11y/-/do11y-0.0.3.tgz","fileCount":6,"integrity":"sha512-qM0kk8/KU06MF6yXeyXZSezgxQy06ySjdw4mIAlh5Vwym7UPywrIwaCeFS2l7Qt2ATMGLlS5hllONvwsYVl53Q==","signatures":[{"sig":"MEYCIQCmhyrBNFkpsg+tBy65KJJtV3UX5FA7zo7s5yL8anor9QIhANAcdPWlj5vAPeivzLLx49xrUyhaBIhZIcIajonG3xYz","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":86286},"main":"./dist/do11y.min.js","type":"module","exports":{".":"./dist/do11y.min.js","./min":"./dist/do11y.min.js"},"gitHead":"f15ba00bf64d93767a363c2cf18a53f6c268d191","scripts":{"lint":"oxlint src","build":"rolldown -c rolldown.config.ts","check":"tsc --noEmit","format":"oxfmt src"},"_npmUser":{"name":"manototh","email":"manototh@gmail.com"},"_npmVersion":"11.12.0","description":"Documentation observability for Axiom","directories":{},"_nodeVersion":"22.13.0","_hasShrinkwrap":false,"devDependencies":{"oxfmt":"latest","oxlint":"latest","rolldown":"latest","typescript":"latest"},"_npmOperationalInternal":{"tmp":"tmp/do11y_0.0.3_1776507900962_0.9383171081972002","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@axiomhq/do11y","version":"0.0.4","keywords":["axiom","analytics","documentation","observability"],"license":"MIT","_id":"@axiomhq/do11y@0.0.4","maintainers":[{"name":"lukasmalkmus","email":"mail@lukasmalkmus.com"},{"name":"gabrielelpidio","email":"gabrieldeandradeleal@gmail.com"},{"name":"kevinehosford","email":"kev.e.hosford@gmail.com"},{"name":"bahlo","email":"hey@arne.me"},{"name":"mhr3","email":"michal.mhr@gmail.com"},{"name":"seiflotfy","email":"seif@axiom.co"},{"name":"flbn","email":"oliver@axiom.co"},{"name":"manototh","email":"manototh@gmail.com"},{"name":"islamshehata","email":"thesollyz@proton.me"}],"dist":{"shasum":"97a036ce7cffb0aa6f3a6e9f67758ba5d23280e2","tarball":"https://registry.npmjs.org/@axiomhq/do11y/-/do11y-0.0.4.tgz","fileCount":6,"integrity":"sha512-GSA1XOUC9F6bJO6Zxwd3TiNyJaRKqiy68YyXbb1qgNwE/0bRL6megsCmkgd9LYcjYnolzaBK4oWFzmL/MEQJ6w==","signatures":[{"sig":"MEYCIQCLBsYso3tXv6Xj8fbQM1p0V84A4WjY0Jv8tXMxKKELZQIhAKb7cvbp4NkJZshxxyn5rXQsnWki45v+ag0U1a+xre9G","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84625},"main":"./dist/do11y.min.js","type":"module","exports":{".":"./dist/do11y.min.js","./min":"./dist/do11y.min.js"},"gitHead":"a2f143bc5dd464fc989e537915e81e29d099e44b","scripts":{"lint":"oxlint src","build":"rolldown -c rolldown.config.ts","check":"tsc --noEmit","format":"oxfmt src"},"_npmUser":{"name":"manototh","email":"manototh@gmail.com"},"_npmVersion":"11.12.0","description":"Documentation observability for Axiom","directories":{},"_nodeVersion":"22.13.0","_hasShrinkwrap":false,"devDependencies":{"oxfmt":"latest","oxlint":"latest","rolldown":"latest","typescript":"latest"},"_npmOperationalInternal":{"tmp":"tmp/do11y_0.0.4_1776545604527_0.11700567248848603","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@axiomhq/do11y","version":"0.0.5","keywords":["axiom","analytics","documentation","observability"],"license":"MIT","_id":"@axiomhq/do11y@0.0.5","maintainers":[{"name":"lukasmalkmus","email":"mail@lukasmalkmus.com"},{"name":"gabrielelpidio","email":"gabrieldeandradeleal@gmail.com"},{"name":"kevinehosford","email":"kev.e.hosford@gmail.com"},{"name":"bahlo","email":"hey@arne.me"},{"name":"mhr3","email":"michal.mhr@gmail.com"},{"name":"seiflotfy","email":"seif@axiom.co"},{"name":"flbn","email":"oliver@axiom.co"},{"name":"bdsqqq","email":"igorbedesqui@gmail.com"},{"name":"manototh","email":"manototh@gmail.com"},{"name":"islamshehata","email":"thesollyz@proton.me"}],"dist":{"shasum":"1a46add1b1417f77963ef60d7a53a11756c51607","tarball":"https://registry.npmjs.org/@axiomhq/do11y/-/do11y-0.0.5.tgz","fileCount":6,"integrity":"sha512-dkbxovJ/tWj6SxRG053tMbphjOYgg1dcPwkW58LPu+GiIcG+HGvy5BzNCHHCxUELIz20pMJvSE/ysEwDHsOgZA==","signatures":[{"sig":"MEYCIQD/wESWiV64E9eeuTVosePk3Tl13hdfi9UjrgwyIiDprgIhAMowYy8AOL63a7SX0aKIA7yUlKoopZD6vFwHLw6Lgzpz","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84870},"main":"./dist/do11y.min.js","type":"module","exports":{".":"./dist/do11y.min.js","./min":"./dist/do11y.min.js"},"gitHead":"7faa4d7b2bd66ea2c710fbcd9cb3c9dd66b0043f","scripts":{"lint":"oxlint src","build":"rolldown -c rolldown.config.ts","check":"tsc --noEmit","format":"oxfmt src"},"_npmUser":{"name":"manototh","email":"manototh@gmail.com"},"_npmVersion":"11.12.0","description":"Documentation observability for Axiom","directories":{},"_nodeVersion":"22.13.0","_hasShrinkwrap":false,"devDependencies":{"oxfmt":"latest","oxlint":"latest","rolldown":"latest","typescript":"latest"},"_npmOperationalInternal":{"tmp":"tmp/do11y_0.0.5_1776680913372_0.767830548298049","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@axiomhq/do11y","version":"0.0.6","keywords":["axiom","analytics","documentation","observability"],"license":"MIT","_id":"@axiomhq/do11y@0.0.6","maintainers":[{"name":"lukasmalkmus","email":"mail@lukasmalkmus.com"},{"name":"gabrielelpidio","email":"gabrieldeandradeleal@gmail.com"},{"name":"kevinehosford","email":"kev.e.hosford@gmail.com"},{"name":"bahlo","email":"hey@arne.me"},{"name":"mhr3","email":"michal.mhr@gmail.com"},{"name":"seiflotfy","email":"seif@axiom.co"},{"name":"flbn","email":"oliver@axiom.co"},{"name":"bdsqqq","email":"igorbedesqui@gmail.com"},{"name":"manototh","email":"manototh@gmail.com"},{"name":"islamshehata","email":"thesollyz@proton.me"}],"dist":{"shasum":"d74c289d2e666d67feef7805e8a93fa637d6b9c1","tarball":"https://registry.npmjs.org/@axiomhq/do11y/-/do11y-0.0.6.tgz","fileCount":7,"integrity":"sha512-aQPfcupv9wOWyPr7g1ZrQZ0v0XHan5KQkMKUPANlHD0boeSSpvlh7UQp1vRR5Cvo/R6hDLvnzHN4weLFdFCmBg==","signatures":[{"sig":"MEQCIDFoy4bEMYewiDEUFd+XUfiTzpiFBEUKQ5TU4RzP93sLAiBsrH3yH9oQsFxg+9TaErK5Zj9/c84iaVPKBtuVOr98Bg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":92515},"main":"./dist/do11y.min.js","type":"module","exports":{".":"./dist/do11y.min.js","./min":"./dist/do11y.min.js"},"gitHead":"8e10ec5e48891cdca3cd242afc22b3ea5d5b267f","scripts":{"lint":"oxlint src","build":"rolldown -c rolldown.config.ts","check":"tsc --noEmit","format":"oxfmt src"},"_npmUser":{"name":"manototh","email":"manototh@gmail.com"},"_npmVersion":"11.12.0","description":"Documentation observability for Axiom","directories":{},"_nodeVersion":"22.13.0","_hasShrinkwrap":false,"devDependencies":{"oxfmt":"latest","oxlint":"latest","rolldown":"latest","typescript":"latest"},"_npmOperationalInternal":{"tmp":"tmp/do11y_0.0.6_1777013041007_0.8325940097393447","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-04-17T20:09:02.246Z","modified":"2026-09-03T13:20:57.577Z","0.0.2":"2026-04-17T20:09:02.497Z","0.0.3":"2026-04-18T10:25:01.096Z","0.0.4":"2026-04-18T20:53:24.661Z","0.0.5":"2026-04-20T10:28:33.517Z","0.0.6":"2026-04-24T06:44:01.135Z"},"license":"MIT","keywords":["axiom","analytics","documentation","observability"],"description":"Documentation observability for Axiom","maintainers":[{"email":"mail@lukasmalkmus.com","name":"lukasmalkmus"},{"email":"kev.e.hosford@gmail.com","name":"kevinehosford"},{"email":"hey@arne.me","name":"bahlo"},{"email":"michal.mhr@gmail.com","name":"mhr3"},{"email":"seif@axiom.co","name":"seiflotfy"},{"email":"oliver@axiom.co","name":"flbn"},{"email":"gord@axiom.co","name":"gordallottaxiom"},{"email":"dchapman.noreply@gmail.com","name":"dominicchapman"},{"email":"thesollyz@proton.me","name":"islamshehata"},{"email":"njpatel@gmail.com","name":"njpatel"}],"readme":"# Axiom Do11y\n\nDo11y is a documentation observability solution from [Axiom](https://axiom.co). It turns documentation usage into machine data. It streams behavioral events like the ones below from your docs site to Axiom in real time:\n\n- Page views\n- Scroll depth\n- Link clicks\n- Search queries\n- Code-block copies\n- Section reading time\n- Tab switches\n- Table of contents (TOC) usage\n- Feedback widget usage\n- Expand/collapse interactions\n\nDo11y is built for humans and machines alike. It emits observability data designed to be easy to use for human users, while also being easy to query and analyze for machines.\n\nDo11y is agent-native: in an era where AI assistants and autonomous agents increasingly read and cite documentation alongside human users, Do11y detects AI platform referrers (ChatGPT, Perplexity, Claude, Gemini, and others) so you can understand how agents and humans engage with your content differently.\n\nThe runtime artifact is a single dependency-free JavaScript file. The source is TypeScript (`src/do11y.ts`). [rolldown](https://rolldown.rs) produces the built output.\n\n## Privacy\n\nDo11y collects anonymous usage data:\n\n- No cookies. Do11y uses `sessionStorage`, which the browser clears when it closes.\n- No personal identifiable information (PII).\n- No device fingerprinting.\n- No cross-site tracking.\n\nYou don't need a GDPR consent banner for using Do11y.\n\n## Supported frameworks\n\nDo11y supports the latest versions of the following frameworks:\n\n- Mintlify\n- Docusaurus\n- Nextra\n- MkDocs Material\n- VitePress\n\nFor other frameworks, use [manual setup](#manual-setup).\n\n## Prerequisites\n\n1. [Create an Axiom account](https://app.axiom.co/register).\n1. [Create a dataset in Axiom](https://axiom.co/docs/reference/datasets#create-dataset) to store observability data for your documentation site.\n1. [Create an API token in Axiom](https://axiom.co/docs/reference/tokens) with **ingest-only** permissions scoped to the dataset.\n\n## Quickstart\n\n### Mintlify\n\n1. Download the latest release from [GitHub](https://github.com/axiomhq/do11y/releases/latest) and extract the `do11y-<version>.zip` file.\n1. Copy `dist/do11y.min.js` and `examples/do11y-config.example.js` to the same folder in your documentation project (for example, `scripts/`). Alphabetical ordering ensures the config loads first.\n1. Rename `do11y-config.example.js` to `do11y-config.js`.\n1. In `do11y-config.js`, replace the placeholder values with your Axiom credentials.\n\n    ```js\n    window.Do11yConfig = {\n    axiomHost: 'AXIOM_DOMAIN',\n    axiomToken: 'API_TOKEN',\n    axiomDataset: 'DATASET_NAME',\n    framework: 'mintlify',\n    };\n    ```\n\n1. Optional: Set up the [automatic sync to your docs repo](#automatic-sync-to-your-docs-repo) to keep your copy of `do11y.min.js` up to date.\n\n### Docusaurus\n\nAdd the following to the `headTags` and `scripts` fields in `docusaurus.config.js` if you use JavaScript, or `docusaurus.config.ts` if you use TypeScript:\n\n```js\nheadTags: [\n    { tagName: 'meta', attributes: { name: 'axiom-do11y-domain', content: 'AXIOM_DOMAIN' } },\n    { tagName: 'meta', attributes: { name: 'axiom-do11y-token', content: 'API_TOKEN' } },\n    { tagName: 'meta', attributes: { name: 'axiom-do11y-dataset', content: 'DATASET_NAME' } },\n    { tagName: 'meta', attributes: { name: 'axiom-do11y-framework', content: 'docusaurus' } },\n],\nscripts: [{ src: 'https://cdn.jsdelivr.net/npm/@axiomhq/do11y@latest/dist/do11y.min.js', defer: true }],\n```\n\n### Nextra\n\nAdd the following to the `<Head>` component in `pages/_app.jsx` (or `_app.tsx`) if you use the Pages Router, or `app/layout.jsx` (or `app/layout.tsx`) if you use the App Router:\n\n```jsx\n<Head>\n    <meta name=\"axiom-do11y-domain\" content=\"AXIOM_DOMAIN\" />\n    <meta name=\"axiom-do11y-token\" content=\"API_TOKEN\" />\n    <meta name=\"axiom-do11y-dataset\" content=\"DATASET_NAME\" />\n    <meta name=\"axiom-do11y-framework\" content=\"nextra\" />\n    <script src=\"https://cdn.jsdelivr.net/npm/@axiomhq/do11y@latest/dist/do11y.min.js\" defer />\n</Head>\n```\n\n### VitePress\n\nAdd the following to the `head` field in `.vitepress/config.js` (or `.vitepress/config.ts`):\n\n```js\nhead: [\n    ['meta', { name: 'axiom-do11y-domain', content: 'AXIOM_DOMAIN' }],\n    ['meta', { name: 'axiom-do11y-token', content: 'API_TOKEN' }],\n    ['meta', { name: 'axiom-do11y-dataset', content: 'DATASET_NAME' }],\n    ['meta', { name: 'axiom-do11y-framework', content: 'vitepress' }],\n    ['script', { src: 'https://cdn.jsdelivr.net/npm/@axiomhq/do11y@latest/dist/do11y.min.js' }],\n],\n```\n\n### MkDocs Material\n\nAdd the following to `mkdocs.yml`:\n\n```yaml\ntheme:\n  name: material\n  custom_dir: overrides\nextra_javascript:\n  - https://cdn.jsdelivr.net/npm/@axiomhq/do11y@latest/dist/do11y.min.js\n```\n\nCreate `overrides/main.html` to inject the meta tags:\n\n```html\n{% extends \"base.html\" %}\n{% block extrahead %}\n  <meta name=\"axiom-do11y-domain\" content=\"AXIOM_DOMAIN\">\n  <meta name=\"axiom-do11y-token\" content=\"API_TOKEN\">\n  <meta name=\"axiom-do11y-dataset\" content=\"DATASET_NAME\">\n  <meta name=\"axiom-do11y-framework\" content=\"mkdocs-material\">\n{% endblock %}\n```\n\nSee the [MkDocs Material docs](https://squidfunk.github.io/mkdocs-material/customization/#extending-the-theme) for details on custom theme overrides.\n\n## Query data\n\nOnce you've installed Do11y and information about your documentation usage is flowing into Axiom, you can query the data in Axiom. See [QUERIES.md](QUERIES.md) for exampleAPL queries to analyze your documentation, including:\n\n- AI traffic detection and trends\n- Traffic sources and entry points\n- Page engagement and scroll completion\n- Where users get stuck (exit pages, low engagement)\n- Navigation patterns and user journeys\n- Link and CTA performance\n- Code block engagement\n\nFor more information, see [Query data with Axiom](https://axiom.co/docs/query-data/explore).\n\n## Integration dashboard\n\nAn integration dashboard provides a visual overview of your documentation usage. It shows important metrics like the number of page views, scroll depth, link clicks, code-block copies, section reading time, tab switches, TOC usage, feedback widget usage, and expand/collapse interactions. It's automatically created when you add Do11y to your docs site.\n\nTo access the integration dashboard:\n1. In Axiom, click **Dashboards**.\n1. In the **Integrations** section, click the integration dashboard **Documentation observability (Do11y) (DATASET_NAME)**.\n\nAlternatively, access the integration dashboard with the URL `https://app.axiom.co/ORG_ID/dashboards/do11y.DATASET_NAME`.\n\n## AI traffic detection\n\nDo11y classifies referrer domains to detect traffic from AI platforms such as ChatGPT, Perplexity, Claude, Gemini, Copilot, DeepSeek, and others. Each `page_view` event includes:\n\n| Field | Values | Description |\n|---|---|---|\n| `referrerCategory` | `ai`, `search-engine`, `social`, `community`, `code-host`, `direct`, `internal`, `other`, `unknown` | High-level traffic source category. |\n| `aiPlatform` | `ChatGPT`, `Perplexity`, `Claude`, `Gemini`, `Copilot`, `DeepSeek`, `Meta AI`, `Grok`, `Mistral`, `You.com`, `Phind`, or `null` | Specific AI platform when `referrerCategory` is `ai`. |\n\nThis detection is referrer-based: it checks whether the `document.referrer` hostname matches a known AI platform. Do11y uses no fingerprinting, user-agent parsing, or additional data collection.\n\n**Limitation:** Most AI platforms (especially ChatGPT mobile and API-sourced visits) don't pass referrer headers. These visits appear as `direct` traffic. Referrer-based detection typically captures 20-40% of AI traffic. Detecting the remaining \"dark AI\" traffic would require fingerprinting techniques that conflict with Do11y's privacy-first design.\n\nSee [QUERIES.md](QUERIES.md) for APL queries to analyze AI traffic, including per-platform breakdowns, trends, and engagement comparisons.\n\n## Known limitations\n\n### Custom themes\n\nThe selectors work on sites using the standard themes of each supported framework. Sites with heavily customized themes may render page elements differently. If you use a custom theme, check whether you need to set the selectors manually.\n\n### Framework selector drift\n\nCSS selectors reflect each framework's current DOM output and may break when frameworks release major updates that change class names or HTML structure. The test suites (`test-live-sites.ts`, `test-e2e-live.ts`, and `test-queries.ts`) exist specifically to catch this. Run them periodically to verify selectors still match.\n\n## Manual setup\n\n### Option 1: CDN (recommended)\n\nAdd the script to every page of your docs site. The simplest setup uses meta tags for the required settings:\n\n```html\n<meta name=\"axiom-do11y-domain\" content=\"AXIOM_DOMAIN\">\n<meta name=\"axiom-do11y-token\" content=\"API_TOKEN\">\n<meta name=\"axiom-do11y-dataset\" content=\"DATASET_NAME\">\n<meta name=\"axiom-do11y-framework\" content=\"FRAMEWORK\">\n<script src=\"https://cdn.jsdelivr.net/npm/@axiomhq/do11y@latest/dist/do11y.min.js\"></script>\n```\n\nReplace the meta tag values with your Axiom credentials and docs framework. To pin a specific version, replace `latest` with a version tag like `1.0.0`.\n\n#### Advanced configuration via CDN\n\nMeta tags only cover the essential settings. To configure any of the [advanced options](#configuration) such as scroll thresholds, tracking toggles, or custom selectors, set `window.Do11yConfig` in an inline script placed **before** the CDN script tag:\n\n```html\n<script>\nwindow.Do11yConfig = {\n  axiomHost: 'us-east-1.aws.edge.axiom.co',\n  axiomToken: 'xaat-your-ingest-token',\n  axiomDataset: 'do11y',\n  framework: 'vitepress',\n  scrollThresholds: [25, 50, 75, 100],\n  trackFeedback: false,\n  sectionVisibleThreshold: 5,\n  // Any option from the Configuration table below can be set here\n};\n</script>\n<script src=\"https://cdn.jsdelivr.net/npm/@axiomhq/do11y@latest/dist/do11y.min.js\"></script>\n```\n\nWhen both are present, meta tags take precedence over `window.Do11yConfig`, which takes precedence over the defaults.\n\n### Option 2: Self-host\n\nIf you can't use a CDN, self-host the script.\n\n1. Download the latest release from [GitHub](https://github.com/axiomhq/do11y/releases/latest) and extract the `do11y-<version>.zip` file.\n1. Copy `dist/do11y.min.js` and `examples/do11y-config.example.js` to your documentation project (for example, `scripts/`).\n1. Rename `do11y-config.example.js` to `do11y-config.js`.\n1. In `do11y-config.js`, replace the placeholder values with your Axiom credentials.\n\n    ```js\n    window.Do11yConfig = {\n    axiomHost: 'AXIOM_DOMAIN',\n    axiomToken: 'API_TOKEN',\n    axiomDataset: 'DATASET_NAME',\n    framework: 'FRAMEWORK',\n    };\n    ```\n\n1. Add both scripts to every page, with the config file loading first:\n\n    ```html\n    <script src=\"/path/to/do11y-config.js\"></script>\n    <script src=\"/path/to/do11y.min.js\"></script>\n    ```\n\n1. Optional: Set up the [automatic sync to your docs repo](#automatic-sync-to-your-docs-repo) to keep your copy of `do11y.min.js` up to date.\n\nDon't edit `do11y.min.js` directly. It's a build artifact and updating to a new release overwrites it.\n\n#### Automatic sync to your docs repo\n\nIf you self-host `do11y.min.js` in GitHub repo, the included GitHub Action (`examples/sync-do11y-to-docs.yml`) keeps your copy up to date automatically.\n\n1. Copy `examples/sync-do11y-to-docs.yml` to `.github/workflows/` in your docs repo. It runs every Monday and opens a PR whenever a new do11y release is available.\n1. Create an empty file at `do11y.version`. This file is used to track the version of `do11y.min.js`.\n1. Add the following repository variables in your docs repo under **Settings > Secrets and variables > Actions > Variables > New repository variable**:\n\n    | Variable | Example | Description |\n    |---|---|---|\n    | `DO11Y_JS_PATH` | `scripts/do11y.min.js` | Path to `do11y.min.js` in your docs repo. |\n    | `DO11Y_VER_PATH` | `scripts/do11y.version` | Path to a version tracking file in your docs repo. |\n\n1. Ensure the GitHub Action has permission to push to your docs repo. Go to **Settings > Actions > General > Workflow permissions**, and turn on **Allow GitHub Actions to create and approve pull requests**.\n\nYou don't need to add any secrets.\n\n## Configuration\n\nAll options can be set via `window.Do11yConfig` (inline script or a separate config file) or via meta tags.\n\n### Axiom connection\n\n| Option | Default | Description |\n|---|---|---|\n| `axiomHost` | `'AXIOM_DOMAIN'` | Base domain of the edge deployment where you want to store your data. For more information, see [Edge deployments](https://axiom.co/docs/reference/edge-deployments). |\n| `axiomDataset` | `'DATASET_NAME'` | Name of the Axiom dataset where you want to store your data. |\n| `axiomToken` | `'API_TOKEN'` | Ingest-only API token scoped to the dataset. |\n\n### Behavior\n\n| Option | Default | Description |\n|---|---|---|\n| `debug` | `false` | Log events to the browser console. |\n| `flushInterval` | `5000` | Milliseconds between batch flushes. |\n| `maxBatchSize` | `10` | Events queued before forcing a flush. |\n| `trackOutboundLinks` | `true` | Track clicks on external links. |\n| `trackInternalLinks` | `true` | Track clicks on internal links. |\n| `trackScrollDepth` | `true` | Track scroll depth thresholds. |\n| `scrollThresholds` | `[25, 50, 75, 90]` | Scroll percentages to record. |\n| `trackSectionVisibility` | `true` | Track which headings users actually read (via IntersectionObserver). |\n| `sectionVisibleThreshold` | `3` | Minimum seconds a section must be visible before recording. |\n| `trackTabSwitches` | `true` | Track code language/framework tab switches. |\n| `trackTocClicks` | `true` | Track on-page table of contents clicks. |\n| `trackExpandCollapse` | `true` | Track expand/collapse interactions (details, accordions). |\n| `trackFeedback` | `true` | Track \"Was this helpful?\" feedback widget clicks. |\n| `allowedDomains` | `['ALLOWED_DOMAINS']` | Restrict which domains may send data. Set to `null` to allow any. |\n| `respectDNT` | `true` | Honor the browser's Do Not Track setting. |\n| `maxRetries` | `2` | Retry count for failed requests. |\n| `retryDelay` | `1000` | Base delay between retries (doubles each attempt). |\n| `rateLimitMs` | `100` | Minimum gap between events of the same type. |\n\n### Documentation framework\n\nSet `framework` to auto-configure CSS selectors for your docs platform:\n\n| Value | Framework |\n|---|---|\n| `'mintlify'` | [Mintlify](https://mintlify.com) (default) |\n| `'docusaurus'` | [Docusaurus](https://docusaurus.io) |\n| `'nextra'` | [Nextra](https://nextra.site) |\n| `'mkdocs-material'` | [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) |\n| `'vitepress'` | [VitePress](https://vitepress.dev) |\n| `'custom'` | Provide your own selectors (see below) |\n\nWhen `framework` is set to a supported value, the script automatically uses the correct CSS selectors for search bars, copy buttons, code blocks, navigation, footers, and content areas. Optional: Set the framework via a meta tag:\n\n```html\n<meta name=\"axiom-do11y-framework\" content=\"docusaurus\">\n```\n\n### Custom selectors\n\nSet `framework: 'custom'` and provide any combination of these selectors. Any selector left `null` falls back to the Mintlify default.\n\n| Selector | What it targets |\n|---|---|\n| `searchSelector` | Search trigger elements (input, button). |\n| `copyButtonSelector` | \"Copy code\" buttons inside code blocks. |\n| `codeBlockSelector` | Code block containers (`<pre>`, wrappers). |\n| `navigationSelector` | Navigation and sidebar regions. |\n| `footerSelector` | Page footer. |\n| `contentSelector` | Main content area. |\n| `tabContainerSelector` | Tab groups for code language/framework switching. |\n| `tocSelector` | On-page table of contents container. |\n| `feedbackSelector` | \"Was this helpful?\" feedback widget container. |\n\n## Events collected\n\n| Event | Description | Key fields |\n|---|---|---|\n| `page_view` | Fires on every page load or SPA navigation. | `referrerDomain`, `referrerCategory`, `aiPlatform`, `isFirstPage`, `previousPath` |\n| `link_click` | Internal, external, anchor, or email link click. | `linkType`, `targetUrl`, `linkText`, `linkContext`, `linkSection`, `linkIndex` |\n| `scroll_depth` | User scrolls past a configured threshold. | `threshold`, `scrollPercent` |\n| `page_exit` | Fires on `beforeunload`. | `totalTimeSeconds`, `activeTimeSeconds`, `engagementRatio`, `maxScrollDepth`, `referrerCategory`, `aiPlatform` |\n| `search_opened` | User opens the search dialog (click or Cmd/Ctrl+K). | `trigger` |\n| `code_copied` | User clicks a code block's copy button. | `language`, `codeSection`, `codeBlockIndex` |\n| `section_visible` | A heading stayed visible in the viewport long enough for the user to read it. | `heading`, `headingLevel`, `visibleSeconds` |\n| `tab_switch` | User switches a code language/framework tab. | `tabLabel`, `tabGroup`, `isDefault` |\n| `toc_click` | User clicks an entry in the on-page table of contents. | `heading`, `headingLevel`, `tocPosition` |\n| `feedback` | User clicks a \"Was this helpful?\" button. | `rating` |\n| `expand_collapse` | User toggles a `<details>` element or accordion. | `summary`, `action`, `section` |\n\nEvery event also includes: `sessionId`, `sessionPageCount`, `path`, `hash`, `title`, `viewportCategory`, `browserFamily`, `deviceType`, `language`, and `timezoneOffset`.\n\n## JavaScript API\n\nDo11y exposes `window.AxiomDo11y` for debugging and integration:\n\n```javascript\nAxiomDo11y.getConfig()    // Current config (token redacted)\nAxiomDo11y.isEnabled()    // Whether tracking is active\nAxiomDo11y.flush()        // Force-send queued events\nAxiomDo11y.getQueueSize() // Number of queued events\nAxiomDo11y.version        // Script version\n```\n\nDo11y doesn't expose `cleanup()` and `debug()` on the global object. Exposing `cleanup()` would allow any third-party script on the page to silently stop tracking. Exposing `debug()` would allow any script to enable verbose console output that reveals the configured ingest endpoint and queued event data.\n\n## Tests\n\nThe `tests` folder contains multiple layers of testing. Each catches a different class of failure:\n\n| What broke | Which test catches it |\n|---|---|\n| Framework updated a CSS class name (selector drift) | `test-live-sites.ts` |\n| do11y broken on a specific framework's local dev server | `test-integrations.ts` |\n| Events not reaching Axiom from a real production site | `test-live-e2e.ts` |\n\n**`test-live-sites.ts`** checks that CSS selectors match real DOM elements in production. It requires no Axiom credentials and no event ingestion — its only job is to catch selector drift when a framework ships a DOM update that renames class names.\n\n**`test-integrations.ts`** runs against local scaffolded sites where the page content is fully under your control. Every interaction is guaranteed to fire: the guide page includes a `<details>` block, a TOC, a code block with a copy button, and a feedback widget. This is why it can assert hard minimums (`code_copied: 1`, `expand_collapse: 1`, `toc_click: 1`) that the live E2E test cannot. It also validates that do11y works correctly with each framework's dev server, SPA routing model, and build toolchain in a hermetic environment.\n\n**`test-live-e2e.ts`** is the only test that proves events reach Axiom from a real site. It catches issues that only surface in production: CDN caching, CSP headers, third-party script interference, or a site's own JavaScript conflicting with do11y.\n\n### Selector tests against live sites (`tests/test-live-sites.ts`)\n\nRuns headless Chromium via Puppeteer against real documentation sites to validate that selectors match elements in production.\n\n```bash\ncd tests\nnpm i\nnpx puppeteer browsers install chrome\nnpm run test-live-sites\n```\n\nThe test covers the following sites:\n\n| Framework | URL |\n|---|---|\n| Mintlify | https://www.mintlify.com/docs/components/expandables |\n| Docusaurus | https://docusaurus.io/docs/next/swizzling |\n| Nextra | https://nextra.site/docs/docs-theme/start |\n| MkDocs Material | https://squidfunk.github.io/mkdocs-material/reference/admonitions |\n| VitePress | https://vitepress.dev/guide/markdown |\n\n### E2E live-site tests (`tests/test-e2e-live.ts`)\n\nEnd-to-end tests that inject `do11y.js` into the same live public documentation sites as `test-live-sites.ts` via Puppeteer's `evaluateOnNewDocument`, drive a realistic user journey on each site, send events to Axiom, and then query Axiom to validate that the expected event types arrived. No local dev servers are required.\n\n```bash\ncd tests\nnpm i\nnpx puppeteer browsers install chrome\n```\n\nCopy `tests/.env.example` to `tests/.env` and add your credentials:\n\n```\nAXIOM_DOMAIN=us-east-1.aws.edge.axiom.co\nAXIOM_TOKEN=xaat-your-ingest-token\nAXIOM_DATASET=do11y\n```\n\nThe token requires both **ingest** and **query** permissions on the target dataset.\n\nRun the full suite:\n\n```bash\nnpm run test-e2e-live\n```\n\nRun a subset of frameworks:\n\n```bash\nFRAMEWORKS=mintlify,vitepress npm run test-e2e-live\n```\n\nSkip the build step on repeat runs (uses an existing `dist/do11y.js`):\n\n```bash\nSKIP_BUILD=1 npm run test-e2e-live\n```\n\nThe test covers the same sites as `test-live-sites.ts`:\n\n| Framework | Start URL | Second URL |\n|---|---|---|\n| Mintlify | https://www.mintlify.com/docs/components/expandables | https://www.mintlify.com/docs/components/accordions |\n| Docusaurus | https://docusaurus.io/docs/next/swizzling | https://docusaurus.io/docs/next/markdown-features |\n| Nextra | https://nextra.site/docs/docs-theme/start | https://nextra.site/docs/docs-theme/built-ins/layout |\n| MkDocs Material | https://squidfunk.github.io/mkdocs-material/reference/admonitions | https://squidfunk.github.io/mkdocs-material/reference/code-blocks/ |\n| VitePress | https://vitepress.dev/guide/getting-started | https://vitepress.dev/guide/markdown |\n\nThe test validates the following events per framework:\n\n| Event | Minimum expected | Notes |\n|---|---|---|\n| `page_view` | 2 | Start page + second page |\n| `scroll_depth` | 1 | |\n| `link_click` | 1 | |\n| `page_exit` | 1 | |\n| `expand_collapse` | 1 | 0 for Nextra (no documentation-level expandables on test page) |\n| `toc_click` | 1 | |\n| `search_opened` | 0 | Best-effort — not all frameworks render search the same way |\n| `code_copied` | 1 | |\n| `feedback` | 0 | 1 for Mintlify and MkDocs Material (confirmed widget on test pages) |\n| `section_visible` | 1 | `sectionVisibleThreshold: 1` + 2 s dwell on page load |\n\n### Query validation (`tests/test-queries.ts`)\n\nValidates that all APL queries in [QUERIES.md](QUERIES.md) are syntactically correct by executing them against the Axiom API.\n\n```bash\ncd tests\nnpm run test-queries\n```\n\n### Integration tests (`tests/test-integrations.ts`)\n\nEnd-to-end tests that install each supported framework, inject `do11y.js`, start a local dev server, drive user interactions via Puppeteer, and then query the Axiom API to verify that events arrived correctly.\n\n```bash\ncd tests\nnpm i\nnpx puppeteer browsers install chrome\n```\n\nCopy `tests/.env.example` to `tests/.env` and add your credentials:\n\n```\nAXIOM_DOMAIN=us-east-1.aws.edge.axiom.co\nAXIOM_TOKEN=xaat-your-ingest-token\nAXIOM_DATASET=do11y\n```\n\nThe token requires both **ingest** and **query** permissions on the target dataset.\n\nRun the full suite:\n\n```bash\nnpm run test-integrations\n```\n\nRun a subset of frameworks:\n\n```bash\nFRAMEWORKS=mintlify,vitepress npm run test-integrations\n```\n\nSkip dependency installation on repeat runs:\n\n```bash\nSKIP_INSTALL=1 npm run test-integrations\n```\n\nThe test covers the following frameworks:\n\n| Name | Type | Port | Notes |\n|---|---|---|---|\n| `mintlify` | npm (Mintlify CLI) | 4005 | Full framework install |\n| `docusaurus` | npm (Docusaurus 3) | 4001 | Full framework install |\n| `nextra` | npm (Next.js + Nextra 3) | 4002 | Full framework install |\n| `vitepress` | npm (VitePress 1.x) | 4003 | Full framework install |\n| `mkdocs-material` | pip (MkDocs Material) | 4004 | Requires Python. Skips if unavailable. |\n\nThe test validates the following events per framework:\n\n| Event | Minimum expected | Notes |\n|---|---|---|\n| `page_view` | 2 | Start page + guide page |\n| `scroll_depth` | 1 | |\n| `link_click` | 1 | |\n| `page_exit` | 1 | |\n| `expand_collapse` | 1 | |\n| `toc_click` | 1 | |\n| `search_opened` | 0 | |\n| `code_copied` | 1 | |\n| `feedback` | 0 | |\n| `section_visible` | 1 | `sectionVisibleThreshold: 1` + 2 s dwell on page load |\n\n## Create release\n\n1. Run all [tests](#tests).\n1. Bump the version in `package.json` and `src/do11y.ts`.\n1. Run the following commands to build the package and run the tests:\n\n    ```bash\n    npm run build\n    npm run check\n    npm run lint\n    ```\n\n1. Commit the changes and push to the `main` branch.\n1. Tag and release via the GitHub CLI:\n\n    ```bash\n    git tag v1.1.0\n    git push origin v1.1.0\n    gh release create v1.1.0\n    ```\n\n    Alternatively, use the GitHub UI to create a release at https://github.com/axiomhq/do11y/releases/new\n\n1. Publish the package to npm as `@axiomhq/do11y`. This requires access to the `@axiomhq` npm organization.\n\n    ```bash\n    npm login\n    npm publish --access public\n    npm logout\n    ```\n\n## License\n\n[MIT](LICENSE)\n","readmeFilename":"README.md"}