Skip to content

mkdocs-zip-bundle-plugin

Turn code blocks into downloadable files — directly from your MkDocs docs. Tag any fenced code block with a bundle ID and filename and the plugin injects a download button. Group multiple blocks under one ID to produce a ZIP archive. Pair with mkdocs-placeholder-plugin and the downloaded files contain the reader's own values, not your defaults.


Live demo

Edit the fields below. Every code block on this page updates live. Click any download button — the file you get has your values in it, not the defaults.

Description / nameInput element
Application name
Host IP address
Application port
Configuration directory path
Timezone
User ID
Group ID

Single file download

Add data-zip-bundle and data-zip-filename to a code block. The plugin injects a download button — no ZIP, just the raw file.

```yaml { data-zip-bundle="compose-only" data-zip-filename="compose.yaml" }
services:
  myapp:
    ...
```

Result:

services:
  myapp:
    image: ghcr.io/example/myapp:latest
    container_name: myapp
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
    ports:
      - "8080:8080"
    volumes:
      - /opt/myapp:/config
    restart: unless-stopped

Multi-file ZIP bundle

Use the same data-zip-bundle ID on multiple blocks. The button appears after the last one and downloads all files as a single ZIP.

```yaml { data-zip-bundle="full-bundle" data-zip-filename="compose.yaml" }
...
```

```ini { data-zip-bundle="full-bundle" data-zip-filename="app.env" }
...
```

```bash { data-zip-bundle="full-bundle" data-zip-filename="setup.sh" }
...
```

Result:

services:
  myapp:
    image: ghcr.io/example/myapp:latest
    container_name: myapp
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
    ports:
      - "8080:8080"
    volumes:
      - /opt/myapp:/config
    restart: unless-stopped
APP_NAME=myapp
HOST=192.168.1.100
PORT=8080
CONFIG_PATH=/opt/myapp
TZ=America/New_York
PUID=1000
PGID=1000
#!/bin/sh
set -e

mkdir -p /opt/myapp
chown 1000:1000 /opt/myapp

podman compose up -d
echo "Done. Access at http://192.168.1.100:8080"

Nested directories in the ZIP

Use paths as filenames — the plugin creates the directory structure inside the ZIP automatically.

```yaml { data-zip-bundle="nested" data-zip-filename="config/app.yaml" }
...
```

```bash { data-zip-bundle="nested" data-zip-filename="scripts/setup.sh" }
...
```

Result:

server:
  host: 192.168.1.100
  port: 8080
  name: myapp
timezone: America/New_York
#!/bin/sh
mkdir -p /opt/myapp/config
cp config/app.yaml /opt/myapp/config/
podman compose up -d

Custom button label

Use data-zip-label to override the auto-generated button text.

```yaml { data-zip-bundle="labeled" data-zip-filename="compose.yaml" data-zip-label="Download my config" }
...
```

Result:

services:
  myapp:
    image: ghcr.io/example/myapp:latest
    ports:
      - "8080:8080"

Installation

pip install mkdocs-zip-bundle-plugin mkdocs-placeholder-plugin

Full setup

mkdocs.yml

markdown_extensions:
  - attr_list           # required — reads data-zip-* attributes
  - pymdownx.superfences

plugins:
  - search
  - zip-bundle:
      include_jszip: true
  - placeholder:
      placeholder_file: placeholder-plugin.yaml

placeholder-plugin.yaml

settings:
  normal_prefix: "@"
  normal_suffix: "@"
  auto_placeholder_tables: false  # place the table manually where you want it

placeholders:
  APP_NAME:
    default: myapp
    description: Application name
  PORT:
    default: "8080"
    description: Application port
  HOST_IP:
    default: 192.168.1.100
    description: Host IP address

Your page

Place the input table wherever you want it in your markdown:

<div class="auto-input-table" data-columns="description,input"></div>

Then tag your code blocks. The download button is injected automatically. At click time, the button reads the live rendered text — so whatever the reader typed into the inputs is what ends up in the downloaded file.

Configuration reference →