Skip to content

Ensuring data privacy

Material for MkDocs makes compliance with data privacy regulations very easy, as it offers a native cookie consent solution to seek explicit consent from users before setting up tracking. Additionally, external assets can be automatically downloaded for self-hosting.

Configuration

Sponsors only · insiders-2.10.0 · Default: none

Material for MkDocs ships a native and extensible cookie consent form which asks the user for his consent prior to sending any data via analytics. Add the following to mkdocs.yml:

extra:
  consent:
    title: Cookie consent
    description: >- # (1)!
      We use cookies to recognize your repeated visits and preferences, as well
      as to measure the effectiveness of our documentation and whether users
      find what they're searching for. With your consent, you're helping us to
      make our documentation better.
  1. You can add arbitrary HTML tags in the description, e.g. to link to your terms of service or other parts of the site.

Note that both, title and description, are required. If Google Analytics was configured via mkdocs.yml, the cookie consent will automatically include a setting for the user to disable it. Furthermore, custom cookies can be integrated by using the cookies field:

extra:
  consent:
    cookies:
      analytics: Custom name # (1)!
  1. The default name of the analytics cookie is Google Analytics.
extra:
  consent:
    cookies:
      analytics:
        name: Google Analytics
        checked: false
extra:
  consent:
    cookies:
      analytics: Google Analytics # (1)!
      custom: Custom cookie
  1. If you add a custom cookie to the cookies field, the analytics cookie must be added back explicitly, or analytics won't be triggered.

When a user first visits your site, a cookie consent form is rendered:

cookie consent enabled

In order to comply with GDPR, users must be able to change their cookie settings at any time. This can be done by creating a simple link as part of any document, e.g. your privacy policy:

[Change cookie settings](#__consent){ .md-button }

Built-in privacy plugin

Sponsors only · insiders-4.9.0 · Plugin · Experimental

The built-in privacy plugin automatically identifies external assets as part of the build process and downloads all assets for very simple self-hosting. Add the following lines to mkdocs.yml:

plugins:
  - privacy # (1)!
  1. Note that the privacy plugin should be located at the end of the list of plugins, as it will scan the resulting HTML for resources to download and replace. If a plugin after the privacy plugin adds further external assets, these assets will not be downloaded.

If you need to be able to build your documentation with and without Insiders, please refer to the built-in plugins section to learn how shared configurations help to achieve this.

The following configuration options are available:

enabled

Default: true – This option specifies whether the plugin is enabled when building your project. If you want to switch the plugin off, e.g. for local builds, use an environment variable:

plugins:
  - privacy:
      enabled: !ENV [PRIVACY, false]
externals

Default: bundle – This option specifies what the plugin should do when encountering external assets. There are two options: while report will issue warning messages during the build, bundle will automatically download all external files and adjust all references:

plugins:
  - privacy:
      externals: bundle

If you've removed all external assets from your project via customization, it's still a good idea to enable the plugin and set the mode to report, as the plugin will make sure that there are no hidden external links in any Markdown files that were unintentionally added.

Using report in strict mode will make the build fail when external assets are detected.

externals_directory

Default: assets/externals – This option specifies where the downloaded external assets will be stored. It's normally not necessary to change this option:

plugins:
  - privacy:
      externals_directory: assets/externals
externals_exclude

Default: none – This option allows to exclude certain external assets from processing by the privacy plugin, so they will not be downloaded and bundled during the build:

plugins:
  - privacy:
      externals_exclude: # (1)!
        - cdn.jsdelivr.net/npm/mathjax@3/* 
        - giscus.app/*
  1. MathJax loads web fonts for typesetting of mathematical content through relative URLs, and thus cannot be automatically bundled by the privacy plugin. MathJax can be self-hosted.

    Giscus, which we recommend to use as a comment system, uses a technique called code-splitting to load only the code that is necessary, which is implemented via relative URLs. Giscus can be self-hosted as well.

Excluding specific external assets can be necessary if they contain dynamically created or relative URLs, which can't be resolved by the privacy plugin due to technical limitations.

Why can't Material for MkDocs bundle all assets by design?

The primary reason why Material for MkDocs can't just bundle all of its own assets is the integration with Google Fonts, which offers over a thousand different fonts that can be used to render your documentation. Most of the fonts include several weights and are split up into different character sets to keep the download size small, so the browser only downloads what is really needed. For Roboto, our default regular font, this results in 42 *.woff2 files in total.

If Material for MkDocs would bundle all font files, the download size would be in the hundreds of megabytes, slowing down automated builds. Furthermore, authors might add external assets like third-party scripts or stylesheets that would need to be remembered to be defined as further local assets.

This is the very reason the built-in privacy plugin exists — it automates the process of downloading all external assets manually to ensure compliance with GDPR. Note that there are some technical limitations.

How it works

The built-in privacy plugin scans the resulting HTML for links to external resources, including external scripts, style sheets, images and web fonts, and downloads them to bundle them with your documentation site. Every URL refering to an external resource, no matter if part of a template or Markdown file, is then replaced with the URL to the local copy. An example:

<script src="https://example.com/script.js"></script>

The external script is downloaded, and the link is replaced with:

<script src="assets/externals/example.com/script.js"></script>

Style sheets are scanned for external url(...) references, e.g. images and web fonts, which are then also downloaded and bundled with your documentation site. This means that Google Fonts can be configured in mkdocs.yml as usual, as the built-in privacy plugin automatically downloads and bundles all dependent resources.

As a third measure, preconnect hints used for DNS pre-fetching which might also leak the visitors IP address to a third party are automatically removed during the build process.

Expand to inspect example

For the official documentation, the built-in privacy plugin downloads the following resources:

.
└─ assets/externals/
   ├─ unpkg.com/tablesort@5.3.0/dist/tablesort.min.js
   ├─ fonts.googleapis.com/css
   ├─ fonts.gstatic.com/s/
   │  ├─ roboto/v29/
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc-CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc0CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc1CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc2CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc3CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc5CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TjASc6CsQ.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic-CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic0CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic1CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic2CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic3CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic5CsTKlA.woff2
   │  │  ├─ KFOjCnqEu92Fr1Mu51TzBic6CsQ.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xEIzIFKw.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xFIzIFKw.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xGIzIFKw.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xHIzIFKw.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xIIzI.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xLIzIFKw.woff2
   │  │  ├─ KFOkCnqEu92Fr1Mu51xMIzIFKw.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fABc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fBBc4.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fBxc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fCBc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fChc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmSU5fCxc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfBBc4.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfBxc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfCBc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2
   │  │  ├─ KFOlCnqEu92Fr1MmWUlfCxc4EsA.woff2
   │  │  ├─ KFOmCnqEu92Fr1Mu4WxKOzY.woff2
   │  │  ├─ KFOmCnqEu92Fr1Mu4mxK.woff2
   │  │  ├─ KFOmCnqEu92Fr1Mu5mxKOzY.woff2
   │  │  ├─ KFOmCnqEu92Fr1Mu72xKOzY.woff2
   │  │  ├─ KFOmCnqEu92Fr1Mu7GxKOzY.woff2
   │  │  ├─ KFOmCnqEu92Fr1Mu7WxKOzY.woff2
   │  │  └─ KFOmCnqEu92Fr1Mu7mxKOzY.woff2
   │  └─ robotomono/v13/
   │     ├─ L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSV0mf0h.woff2
   │     ├─ L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSZ0mf0h.woff2
   │     ├─ L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSd0mf0h.woff2
   │     ├─ L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2
   │     ├─ L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSt0mf0h.woff2
   │     ├─ L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSx0mf0h.woff2
   │     ├─ L0xdDF4xlVMF-BfR8bXMIjhOsXG-q2oeuFoqFrlnAIe2Imhk1T8rbociImtElOUlYIw.woff2
   │     ├─ L0xdDF4xlVMF-BfR8bXMIjhOsXG-q2oeuFoqFrlnAIe2Imhk1T8rbociImtEleUlYIw.woff2
   │     ├─ L0xdDF4xlVMF-BfR8bXMIjhOsXG-q2oeuFoqFrlnAIe2Imhk1T8rbociImtEluUlYIw.woff2
   │     ├─ L0xdDF4xlVMF-BfR8bXMIjhOsXG-q2oeuFoqFrlnAIe2Imhk1T8rbociImtEm-Ul.woff2
   │     ├─ L0xdDF4xlVMF-BfR8bXMIjhOsXG-q2oeuFoqFrlnAIe2Imhk1T8rbociImtEmOUlYIw.woff2
   │     └─ L0xdDF4xlVMF-BfR8bXMIjhOsXG-q2oeuFoqFrlnAIe2Imhk1T8rbociImtEn-UlYIw.woff2
   └─ polyfill.io/v3/polyfill.min.js

Caching recommended

All downloaded files are written to the .cache directory, significantly reducing the duration of subsequent builds as only replacements need to be carried out. You might want to:

  1. Ignore the .cache directory in your project, by adding it to .gitignore.
  2. When building your site for publishing, use a build cache to save the .cache directory in between builds. Taking the example from the publishing guide, add the following lines:

    name: ci
      on:
        push:
          branches:
            - master
            - main
      jobs:
        deploy:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v2
            - uses: actions/setup-python@v2
              with:
                python-version: 3.x
            - uses: actions/cache@v2
              with:
                key: ${{ github.ref }}
                path: .cache
            - run: pip install mkdocs-material
            - run: mkdocs gh-deploy --force
    

Limitations

Note that dynamically created URLs as part of scripts are not detected, and thus cannot be automatically downloaded. The built-in privacy plugin does not execute scripts – it can only detect fully qualified URLs to download and replace.

In short, don't do this:

const cdn = "https://polyfill.io"
const url = `${cdn}/v3/polyfill.min.js`

Instead, always use fully qualified URLs:

const url ="https://polyfill.io/v3/polyfill.min.js"

Customization

Custom cookies

If you've customized the cookie consent and added a custom cookie, the user will be prompted to accept your custom cookie. Use additional JavaScript to check whether the user accepted it:

var consent = __md_get("__consent")
if (consent && consent.custom) {
  /* The user accepted the cookie */
}
extra_javascript:
  - javascripts/consent.js
Back to top