# Plugins

> Turn Docsy's optional scripts on or off, and configure them, from site configuration.

---

Site [llms.txt](/fr/llms.txt)

---

Docsy loads some of its optional JavaScript features as **plugins**: entries
under `params.docsy.plugins` in your site configuration.

## Configure Docsy's plugins

| Plugin            | What it does (Default / Loads on)                                                                  | Learn more                     |
| ----------------- | -------------------------------------------------------------------------------------------------- | ------------------------------ |
| `click-to-copy`   | Adds a copy button to code blocks (On, but off under Prism, which has its own / Every page)        | [Copy to clipboard][]          |
| `tabpane-persist` | Remembers the selected tab across pages (On / Every page ([why](#page-flags-in-included-content))) | [`tabpane`][]                  |
| `markmap`         | Renders `markmap` code blocks as mind maps (Off / Pages with a `markmap` code block)               | [Activating MarkMap support][] |
| `mermaid`         | Renders `mermaid` code blocks as diagrams (On / Pages with a `mermaid` code block)                 | [Diagrams with Mermaid][]      |

To turn a plugin off, set its `enable` field to `false`:

<!-- markdownlint-disable no-shortcut-ref-link -->
<!-- prettier-ignore-start -->





<ul class="nav nav-tabs" id="tabs-0" role="tablist">
  <li class="nav-item">
      <button class="nav-link disabled"
          id="tabs-00-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-00-00" role="tab"
          aria-controls="tabs-00-00" aria-selected="false">
        Configuration file:
      </button>
    </li><li class="nav-item">
      <button class="nav-link active"
          id="tabs-00-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-00-01" role="tab"
          data-td-tp-persist="toml" aria-controls="tabs-00-01" aria-selected="true">
        hugo.toml
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-00-02-tab" data-bs-toggle="tab" data-bs-target="#tabs-00-02" role="tab"
          data-td-tp-persist="yaml" aria-controls="tabs-00-02" aria-selected="false">
        hugo.yaml
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-00-03-tab" data-bs-toggle="tab" data-bs-target="#tabs-00-03" role="tab"
          data-td-tp-persist="json" aria-controls="tabs-00-03" aria-selected="false">
        hugo.json
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-0-content">
    <div class="tab-pane fade"
        id="tabs-00-00" role="tabpanel" aria-labelled-by="tabs-00-00-tab" tabindex="0">
        <pre tabindex="0"><code></code></pre>
    </div>
    <div class="tab-pane fade show active"
        id="tabs-00-01" role="tabpanel" aria-labelled-by="tabs-00-01-tab" tabindex="0">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">params</span><span class="p">.</span><span class="nx">docsy</span><span class="p">.</span><span class="nx">plugins</span><span class="p">.</span><span class="nx">click-to-copy</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">enable</span> <span class="p">=</span> <span class="kc">false</span></span></span></code></pre></div>
    </div>
    <div class="tab-pane fade"
        id="tabs-00-02" role="tabpanel" aria-labelled-by="tabs-00-02-tab" tabindex="0">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">docsy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">plugins</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">click-to-copy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enable</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span></span></span></code></pre></div>
    </div>
    <div class="tab-pane fade"
        id="tabs-00-03" role="tabpanel" aria-labelled-by="tabs-00-03-tab" tabindex="0">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;params&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;docsy&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;plugins&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;click-to-copy&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;enable&#34;</span><span class="p">:</span> <span class="kc">false</span> <span class="p">}</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
    </div>
</div>

<!-- prettier-ignore-end -->
<!-- markdownlint-enable no-shortcut-ref-link -->

## Configuration reference

Docsy's own plugins are declared in the theme's [`hugo.yaml`][theme-defaults];
your entries merge over them by name and field ([Configuration § Theme
defaults][config-merge]). The schema defines each entry's keys, required fields,
types, defaults, and syntactic patterns:

```yaml
type: map
entries:
  'plugins':
    type: map # nonempty
    key: # plugin name
      type: string # coerced, lowercased
      pattern: ^[a-z0-9_-]+$
      reservedSuffix: _docsy-shim
    value:
      type: map
      entries:
        'enable': { type: bool, required: true } # coerced
        'version':
          type: string # coerced
          pattern: ^[0-9A-Za-z.+-]+$
        'options': { type: string } # plugin-defined format; see the plugin's guide
        '_defer': { type: bool, default: false } # coerced; plugin-author field: adds `defer` to the script tag
```


- Fields are optional unless marked `required: true`.
- `{}` for a theme plugin keeps every inherited field, including `enable`.
- Language-specific `params` apply, so an entry can differ per language.
- `enable` is off for `false`, `"false"`, and `0`, and on for any other value.
  The string forms exist for [environment overrides][config-env].
- `version` selects the version of a plugin's dependency, not of the plugin
  script or of Docsy. To override a theme plugin's pin, see [Mermaid
  version][mermaid-version] or [MarkMap version][markmap-version].
- `options` holds a plugin's own settings, as a string whose format and
  validation are the plugin's; Docsy passes the value to the plugin unchanged.
  For the shape a theme plugin takes, see its guide ([Mermaid
  settings][mermaid-settings]).
- `_defer` is the plugin author's field (the `_` prefix marks such fields),
  declared with the plugin
  ([Loading strategy (experimental)](#loading-strategy)); leave it alone on a
  plugin you didn't write.

### Warnings

Every registry shape warning carries the id `docsy-config` (to silence one, see
[Configuration § Configuration warnings][config-warnings]):

- An unknown field is ignored and the rest of the entry applies.
- An unknown key directly under `params.docsy` is ignored and the rest of the
  map applies.
- A name the schema's pattern rejects or that ends in its reserved suffix, a
  scalar entry, or an entry missing a required field drops the whole entry.
- A `params.docsy` or `params.docsy.plugins` that is not a map empties the
  registry, Docsy's own plugins and their deprecated aliases included.
  `plugins: {}` keeps them; a valueless `plugins:` is null and drops them.
- An empty registry after configuration merging warns; a registry with all
  entries disabled is valid.
- An enabled name with no script file
  ([Plugin files (experimental)](#plugin-files)) is a different fault: it warns
  `docsy-plugin-missing` (a disabled entry is never looked up).

`version` validation applies to entries not already dropped by the shape guards,
including disabled entries. An exact `X.Y.Z` passes without a version warning;
another value matching the schema's pattern, such as `latest`, warns under
_`NAME`_`-floating-version`, where _`NAME`_ is the entry's name. A value outside
the pattern, such as a range or operator (`^11`, `>=11`, `*`) or an empty or
malformed string, fails the build and skips the entry before its companion runs.

For why Docsy pins versions, see [Pinned script-dependency versions][ug-pins].

## Add a custom script

<span class="badge text-bg-info rounded-pill text-small">EXPERIMENTAL</span>

This section is [experimental][];
[configuring Docsy's plugins](#configure-docsys-plugins) is supported.

For a script that should load at the end of every page, register it as a plugin;
for markup in `<head>`, inline snippets, or third-party tags, use the [head and
body hooks][] instead.

1. Save the script as `assets/js/plugins/`_`NAME`_`.js`, with _`NAME`_ in
   lowercase.
2. Register it under `params.docsy.plugins`
   ([configuration reference](#configuration-reference)):

<!-- markdownlint-disable no-shortcut-ref-link -->
<!-- prettier-ignore-start -->





<ul class="nav nav-tabs" id="tabs-3" role="tablist">
  <li class="nav-item">
      <button class="nav-link disabled"
          id="tabs-03-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-00" role="tab"
          aria-controls="tabs-03-00" aria-selected="false">
        Configuration file:
      </button>
    </li><li class="nav-item">
      <button class="nav-link active"
          id="tabs-03-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-01" role="tab"
          data-td-tp-persist="toml" aria-controls="tabs-03-01" aria-selected="true">
        hugo.toml
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-03-02-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-02" role="tab"
          data-td-tp-persist="yaml" aria-controls="tabs-03-02" aria-selected="false">
        hugo.yaml
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-03-03-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-03" role="tab"
          data-td-tp-persist="json" aria-controls="tabs-03-03" aria-selected="false">
        hugo.json
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-3-content">
    <div class="tab-pane fade"
        id="tabs-03-00" role="tabpanel" aria-labelled-by="tabs-03-00-tab" tabindex="3">
        <pre tabindex="0"><code></code></pre>
    </div>
    <div class="tab-pane fade show active"
        id="tabs-03-01" role="tabpanel" aria-labelled-by="tabs-03-01-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">params</span><span class="p">.</span><span class="nx">docsy</span><span class="p">.</span><span class="nx">plugins</span><span class="p">.</span><span class="nx">NAME</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">enable</span> <span class="p">=</span> <span class="kc">true</span></span></span></code></pre></div>
    </div>
    <div class="tab-pane fade"
        id="tabs-03-02" role="tabpanel" aria-labelled-by="tabs-03-02-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">docsy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">plugins</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NAME</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
    </div>
    <div class="tab-pane fade"
        id="tabs-03-03" role="tabpanel" aria-labelled-by="tabs-03-03-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;params&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;docsy&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;plugins&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;NAME&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;enable&#34;</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
    </div>
</div>

<!-- prettier-ignore-end -->
<!-- markdownlint-enable no-shortcut-ref-link -->

### Plugin files

A project file shadows the theme's of the same name, which is how you replace
one of Docsy's plugins, its companions, or its shim.

| File                                                           | Contract                                                                                                                   |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `assets/js/plugins/`_`NAME`_`.js`                              | Required. Built on its own with [`js.Build`][].                                                                            |
| `layouts/_partials/scripts/plugins/`_`NAME`_`.html`            | Optional companion partial for vendored libraries, markup, or configuration; receives `(dict "Page" PAGE "Plugin" ENTRY)`. |
| `assets/scss/plugins/`_`NAME`_`.scss`                          | Optional companion stylesheet, through the Sass pipeline.                                                                  |
| `layouts/_partials/scripts/plugins/`_`NAME`_`_docsy-shim.html` | Optional shim partial; [adjust a plugin per page](#adjust-a-plugin-per-page).                                              |

Companions emit before the script ([why][design-ordering]). Script and
stylesheet tags carry [subresource integrity][SRI] in every environment. Entry
keys reach templates lowercase ([Configuration § Key spelling][config-keys]).

### Loading strategy

A plugin's script runs synchronously by default. For a script that scans the
document once when it runs, set `_defer: true`. The script then runs after
parsing and sees markup emitted after its tag, including the [body-end
hook][head and body hooks], as `click-to-copy` does. Declare `_defer` where you
register the plugin, or set it in its [shim](#adjust-a-plugin-per-page).

### Adjust a plugin per page

A **shim** adjusts a plugin's registry entry for each page before the plugin
loads. Add one for your own plugin, or for one of Docsy's. Three of Docsy's
plugins ship a shim, `mermaid`, `markmap`, and `click-to-copy`: your file
replaces that plugin's shim and everything it does ([shim contract][impl-shim]),
so start from a copy of the theme's file, in [`scripts/plugins/`][theme-shims].

Create `layouts/_partials/scripts/plugins/`_`NAME`_`_docsy-shim.html`, with the
plugin's registry name as _`NAME`_ ([shim contract][impl-shim]):

```go-html-template
{{ $entry := .Plugin -}}
{{ if not (.Page.Store.Get "hasMyFeature") -}}
  {{ $entry = merge $entry (dict "enable" false) -}}
{{ end -}}
{{ return $entry -}}
```

That shim loads the plugin only on pages that use it: a render hook of yours
sets the flag with `.Page.Store.Set` where the feature's markup appears. Before
relying on a flag, read
[Page flags in included content](#page-flags-in-included-content).

### Dependency versions

For a custom plugin with a configurable dependency, set `version` on its
registry entry ([configuration reference](#configuration-reference)) and read
`.Plugin.version` in the companion partial. Use that value to select the
dependency's code, for example in a build-time fetch URL. Declaring `version`
does not fetch code automatically. Omit the field if the plugin has no
dependency version to configure.

The entry's `version` is not passed to the plugin script. For a working example,
see the `markmap` companion in [`scripts/plugins/`][theme-shims].

### Plugin settings

A plugin reads its settings from its entry's `options`
([configuration reference](#configuration-reference)); the shim receives the
value as the site wrote it and may decode it before the companion runs. Choose
the string's format and document it with the plugin. It is a string because Hugo
lowercases map keys ([why][design-registry]); Docsy's plugins take a **JSON
object**, decoded at build time with `transform.Unmarshal`, or in the browser
with `JSON.parse` after the companion emits it. For the pattern, see the
`mermaid` shim and companion in [`scripts/plugins/`][theme-shims] ([shim
contract][impl-shim]).

### Security

- Pin third-party dependencies on the entry's `version`, never `latest`.
- Vendor build-time fetches and serve them with SRI.
- Use no loader that pulls unpinned secondary code, which SRI on the loader
  can't cover.
- Load remote code only on pages that use it:
  [gate the plugin with a shim](#adjust-a-plugin-per-page).

## Page flags in included content

Some plugins load only on pages that need them: Docsy's `markmap` render hook
sets a page flag whenever a page has a `markmap` code block, and the plugin
ships where the flag is set. A flag counts only when it lands on the page whose
output the plugin is emitted into.

- A **render hook** runs in the context of the page being rendered, so a
  `markmap` block in content pulled in through [`.RenderShortcodes`][] flags the
  page that includes it.
- A **shortcode** runs in the context of the page whose file contains it, so a
  shortcode in included content would flag the _included_ page, and the
  including page would never see the flag.
- Content pulled in through `.Content` flags the included page in both cases.

That is why Docsy ships `tabpane-persist` ungated, on every page: tabpanes come
from a shortcode. For MarkMap's authoring paths and the remedy, see [When a
MarkMap doesn't render][].

<!-- prettier-ignore-start -->
[`.RenderShortcodes`]: https://gohugo.io/methods/page/rendershortcodes/
[`tabpane`]: /docs/content/shortcodes/#tabpane
[Activating MarkMap support]: /docs/content/diagrams-and-formulae/#activating-markmap-support
[When a MarkMap doesn't render]: /docs/content/diagrams-and-formulae/#when-a-markmap-doesnt-render
[Copy to clipboard]: /docs/content/lookandfeel/#copy-to-clipboard
[head and body hooks]: /docs/content/lookandfeel/#add-code-to-head-or-before-body-end
[`js.Build`]: https://gohugo.io/functions/js/build/
[config-env]: /docs/content/configuration/#environment-variables
[ug-pins]: /docs/content/diagrams-and-formulae/#script-dep-versions
[config-keys]: /docs/content/configuration/#key-spelling
[config-merge]: /docs/content/configuration/#theme-defaults-and-your-overrides
[config-warnings]: /docs/content/configuration/#configuration-warnings
[design-ordering]: /project/design/script-loading/#ordering-decisions
[design-registry]: /project/design/script-loading/#registry-shape
[experimental]: /project/about/changelog/#experimental
[markmap-version]: /docs/content/diagrams-and-formulae/#markmap-version
[mermaid-settings]: /docs/content/diagrams-and-formulae/#mermaid-settings
[mermaid-version]: /docs/content/diagrams-and-formulae/#mermaid-version
[Diagrams with Mermaid]: /docs/content/diagrams-and-formulae/#diagrams-with-mermaid
[impl-shim]: /project/implementation/script-loading/#shims
[theme-shims]: https://github.com/docsy/docsy/tree/main/theme/layouts/_partials/scripts/plugins
[theme-defaults]: https://github.com/docsy/docsy/blob/main/theme/hugo.yaml
[SRI]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
<!-- prettier-ignore-end -->
