link-notes

Automatically attaches a note (arbitrary markdown) to outbound links that match a rule, so that:

  • hovering the link shows the note as a tooltip, and
  • the note is rendered once at the bottom of the page (the footer),

without you having to hand-add a footnote to every post. It is a first-party Netdocs plugin — there is no equivalent MkDocs plugin — and is fully opt-in and data-driven.

Formerly `affiliate-links`

This plugin was renamed from affiliate-links to the neutral link-notes (attaching a disclosure to affiliate links is just one use-case). The old name still works as an alias, and the legacy programs / disclosure config keys are still accepted — no changes are required to existing configs.

A common use-case: affiliate disclosures

Attaching an affiliate-disclosure note to eBay Partner Network / tagged Amazon links means the disclosure appears on hover and once in the page footer, satisfying the once-per-page disclosure requirement automatically:

{
  "name": "link-notes",
  "options": {
    "rules": [
      {
        "name": "ebay",
        "label": "Affiliate links",
        "domains": [ "ebay.us" ],
        "note": "This is an eBay Partner Network affiliate link. It costs you nothing extra, and purchases made through it help support this site."
      }
    ]
  }
}

A link such as [HBA card](https://ebay.us/abc123) becomes, in the rendered page, a link with a small footnote marker; hovering it reveals the note, and the same text appears in the page's footnote list.

Just write a normal link

You do not need a snippet or macro. Prefer an ordinary Markdown link — [used APC PDU](https://ebay.us/bSAxHF) — and let this plugin add the note. That keeps posts readable and avoids per-link markup such as --8<-- "ebay.html" text="..." url="...", which is noisier and easy to get wrong.

How it works

The plugin runs as a Markdown preprocessor (order 30, after snippets, table-reader and macros, so links those plugins generate are also covered). For every configured rule it scans the page for links that match by domain and/or regular expression and appends a footnote reference carrying the note text. Because the tooltip and the footer note both come from the same footnote, enabling the Material content.footnote.tooltips feature gives you the hover behavior for free.

Matching by domain and query parameter

Some links only qualify when they carry a specific query parameter — for example a raw amazon.com URL is only an affiliate link when it has a tag= parameter, while amzn.to short links always are. A domain entry can therefore be either a plain string or an object with its own query_contains marker:

{
  "name": "amazon",
  "label": "Affiliate links",
  "domains": [
    "amzn.to",
    { "domain": "amazon.com", "query_contains": "tag=" }
  ],
  "note": "This is an Amazon affiliate link. As an Amazon Associate I earn from qualifying purchases at no additional cost to you; it helps support this site."
}

A rule-level query_contains may also be set; it applies to every plain-string domain that doesn't override it. Subdomains of a configured domain match automatically.

Matching by regular expression

For anything a domain rule can't express, add a patterns list. Each entry is a regular expression matched case-insensitively against the full URL; a link matches the rule if any pattern (or any domain rule) matches:

{
  "name": "sponsored",
  "label": "Sponsored",
  "patterns": [
    "https?://[^/]*/go/",
    "utm_source=sponsor"
  ],
  "note": "This is a sponsored link."
}

Invalid patterns are logged and skipped rather than aborting the build. A rule needs at least one domains entry or one patterns entry; a rule with neither is dropped with a warning.

What is left untouched

To keep output valid, the plugin does not inject a footnote reference when:

  • the link already carries a footnote (e.g. a hand-authored [^ebay]), so it coexists with existing content during migration;
  • the link is inside a fenced code block;
  • the link is inside a pipe-table cell (Markdig can't reliably parse footnote references there); or
  • the link is glued directly to an adjacent link ([a](x)[b](y)), where a wedged footnote would render ambiguously.

In the table-cell and adjacent-link cases the footer note is still guaranteed: the rule emits a standalone !!! info "<label>" admonition at the bottom of the page instead of a footnote.

Options

Option Type Default Description
rules array The link rules to detect (see below). Legacy alias: programs.

Each rule object:

Field Type Required Description
name string yes Rule id; used to build the footnote label (linknote-<name>).
note string yes Markdown shown as the tooltip and footer note. Legacy alias: disclosure.
domains array no* Hosts that identify the link. Each entry is a domain string or { "domain": "...", "query_contains": "..." }. Subdomains match automatically.
patterns array no* Regular expressions matched (case-insensitively) against the full URL.
query_contains string no Default substring a matching URL must contain (per-domain values override this).
label string no Title for the standalone fallback admonition (table-only links). Default Links.

* A rule must provide at least one of domains or patterns.

Tip

Enable content.footnote.tooltips in your theme features so the notes appear as hover tooltips as well as in the footer.