Ship new releases without breaking your docs. This track teaches you to maintain multiple documentation versions, letting users access the exact docs that match their installed version.

Tip

Duration: ~45 min | Prerequisite: Experience with Bengal content organization

1

Versioned Documentation

Serve multiple versions of your documentation from a single site

Versioned Documentation

Bengal supports versioned documentation, allowing you to maintain multiple versions of your docs (e.g., v1, v2, v3) and let users switch between them.

Why Version Your Docs?

  • API libraries: Users on older versions need docs that match their installed version
  • Breaking changes: Major releases often have different APIs or workflows
  • Enterprise support: Some users stay on LTS versions for years
  • Migration guides: Link between versions to help users upgrade

Two Modes

Bengal supports two versioning modes:

Folder Mode (Default)

Store versions in_versions/directories:

content/
├── docs/              # Latest version (v3)
│   └── guide.md
└── _versions/
    ├── v2/
    │   └── docs/
    │       └── guide.md
    └── v1/
        └── docs/
            └── guide.md

Best for: Most projects, simple setup, easy to understand.

Set up Folder Mode

Git Mode

Build from Git branches or tags:

versioning:
  mode: git
  git:
    branches:
      - name: main
        latest: true
      - pattern: "release/*"
        strip_prefix: "release/"

Best for: Projects that already use release branches, CI/CD pipelines.

Set up Git Mode

Quick Start (Folder Mode)

1. Enable Versioning

# bengal.yaml
versioning:
  enabled: true
  versions:
    - id: v3
      latest: true
    - id: v2
    - id: v1
  sections:
    - docs

2. Create a Version Snapshot

# Snapshot current docs as v2 before making breaking changes
bengal version create v2

This copies docs/_versions/v2/docs/.

3. Build

bengal build

Your site now has:

  • /docs/guide/→ v3 (latest)
  • /docs/v2/guide/→ v2
  • /docs/v1/guide/→ v1

URL Structure

Version URL Pattern Example
Latest /docs/page/ /docs/installation/
Older /docs/{version}/page/ /docs/v2/installation/

The latest version has no version prefix in URLs—cleaner for most users.

Features

Version Selector

A dropdown appears in the documentation sidebar when versioning is enabled, letting users switch between versions. Bengal's version selector includes smart fallback: if a page doesn't exist in the target version, it navigates to the nearest equivalent (section index or version root) instead of showing a 404.

The default theme includes the version selector automatically. To customize, overridepartials/version-selector.htmlin your theme.

Version Banners

Older versions automatically display a warning banner linking to the latest docs. Customize the banner per version:

versions:
  - id: v2
    banner:
      type: warning  # or: info, danger
      message: "You're viewing docs for v2. See the latest version."

Banner types: info (blue), warning (yellow), danger(red for deprecated versions).

Link to specific versions from any page:

See the [[v2:docs/migration|v2 Migration Guide]] for upgrade steps.

Cross-Version Linking

Version Directives

Mark when features were added, deprecated, or changed:

:::{since} v2.0
This feature was added in version 2.0.
:::

:::{deprecated} v3.0
Use `new_function()` instead.
:::

:::{changed} v2.5
Default timeout changed from 30s to 60s.
:::

Version Directives

SEO Optimization

Bengal automatically handles SEO for versioned docs:

  • Canonical URLs: Older versions point to latest version (prevents duplicate content penalties)
  • Sitemap priorities: Latest version pages get priority 0.8; older versions get 0.3
  • No-index option: Mark deprecated versions asnoindexto remove from search entirely

CLI Commands

# List all versions
bengal version list

# List versions as JSON (for scripting)
bengal version list --format json

# Show version details
bengal version info v2

# Create a new version snapshot
bengal version create v2 --label "2.0 LTS"

# Preview what create would do
bengal version create v2 --dry-run

# Compare versions
bengal version diff v2 v3

# Compare git branches directly
bengal version diff main release/0.1.6 --git

Next Steps

2

Folder Mode Setup

Set up versioned documentation using folder-based versioning

Folder Mode Setup

Folder mode is the simplest way to version your documentation. Each version lives in its own directory.

Directory Structure

your-site/
├── bengal.yaml
├── content/
│   ├── docs/                    # Latest version (v3)
│   │   ├── _index.md
│   │   ├── installation.md
│   │   └── guide/
│   │       └── getting-started.md
│   └── _versions/
│       ├── v2/
│       │   └── docs/            # v2 content
│       │       ├── _index.md
│       │       ├── installation.md
│       │       └── guide/
│       │           └── getting-started.md
│       └── v1/
│           └── docs/            # v1 content
│               └── ...

Step 1: Enable Versioning

Add to yourbengal.yaml:

versioning:
  enabled: true
  versions:
    - id: v3
      label: "3.0"
      latest: true
    - id: v2
      label: "2.0"
    - id: v1
      label: "1.0"
      deprecated: true
  sections:
    - docs  # Which content sections are versioned (default)

If you omit sections, it defaults to ["docs"].

Step 2: Create Version Directories

# Before making breaking changes, snapshot current docs
bengal version create v2

# This creates _versions/v2/docs/ with a copy of docs/

Option B: Create Manually

mkdir -p content/_versions/v2/docs
cp -r content/docs/* content/_versions/v2/docs/

Step 3: Build and Verify

bengal build

Check the output:

public/
├── docs/
│   ├── index.html              # /docs/ (latest)
│   └── installation/
│       └── index.html          # /docs/installation/
├── docs/v2/
│   ├── index.html              # /docs/v2/
│   └── installation/
│       └── index.html          # /docs/v2/installation/
└── docs/v1/
    └── ...

Configuration Options

Full Configuration Reference

versioning:
  enabled: true

  # Version definitions
  versions:
    - id: v3                    # Required: unique identifier
      label: "3.0 (Current)"    # Optional: display label
      latest: true              # One version must be latest

    - id: v2
      label: "2.0 LTS"
      banner:                   # Optional: warning banner
        type: warning           # info, warning, or danger
        message: "This is an older version. See v3 for latest."
        show_latest_link: true

    - id: v1
      label: "1.0"
      deprecated: true          # Marks version as deprecated
      end_of_life: "2024-12-31" # Optional: EOL date

  # Aliases for version IDs
  aliases:
    latest: v3
    stable: v3
    lts: v2

  # Which sections are versioned (default: ["docs"])
  sections:
    - docs
    # - api    # Add more if needed

  # Shared content across all versions (default: ["_shared"])
  shared:
    - _shared  # Content in _shared/ appears in all versions

Shared Content

Content that should appear in all versions goes in_shared/:

content/
├── docs/
├── _versions/
└── _shared/
    └── docs/
        └── changelog.md    # Appears in all versions

Workflow: Creating a New Version

When you're about to release a breaking change:

1. Snapshot Current Docs

# Create v2 from current docs
bengal version create v2 --label "2.0"

2. Update Configuration

versions:
  - id: v3          # NEW: will be the latest
    latest: true
  - id: v2          # Previous latest
  - id: v1

3. Make Breaking Changes

Editcontent/docs/ freely—v2 is preserved in _versions/v2/docs/.

4. Build and Deploy

bengal build

Tips

Keep Versions in Sync

For pages that exist in all versions (like changelog), use shared content:

_shared/docs/changelog.md

Mark Deprecated Versions

versions:
  - id: v1
    deprecated: true
    banner:
      type: danger
      message: "v1 is no longer supported. Please upgrade to v3."

Version-Specific Banners

Each version can have its own banner:

versions:
  - id: v2
    banner:
      type: info
      message: "v2 is in LTS. Security updates until 2025."

Troubleshooting

Version Not Appearing

  1. Check that the version is listed inbengal.yaml
  2. Verify the directory exists:_versions/{id}/docs/
  3. Ensure content exists in the version directory

Wrong Version Showing as Latest

Only one version should havelatest: true:

versions:
  - id: v3
    latest: true    # ✓ Only this one
  - id: v2
    # latest: true  # ✗ Remove this

URLs Incorrect

Checksectionsin your config matches your content structure:

sections:
  - docs    # Must match your content/docs/ directory

Next Steps

3

Git Mode Setup

Build versioned documentation from Git branches and tags

Git Mode Setup

Git mode builds documentation from Git branches or tags instead of folder copies. This is ideal for projects that already use release branches.

When to Use Git Mode

Use Git Mode if:

  • You already have release branches (e.g.,release/1.0, release/2.0)
  • You want versions tied to Git history
  • You prefer not to duplicate content in folders
  • Your CI/CD pipeline builds from branches

Use Folder Mode if:

  • You want simple, visible version directories
  • You don't use release branches
  • You're new to versioning

How It Works

┌─────────────────────────────────────────────────────────┐
│                     Your Repo                           │
├─────────────────────────────────────────────────────────┤
│  main ──────────────────────────────► v3.0 (latest)     │
│    │                                                    │
│    └─ release/2.0 ──────────────────► v2.0              │
│         │                                               │
│         └─ release/1.0 ─────────────► v1.0              │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    Bengal Build                         │
├─────────────────────────────────────────────────────────┤
│  1. Discover branches matching patterns                 │
│  2. Create worktrees for each version                   │
│  3. Build each version in parallel                      │
│  4. Merge outputs into single site                      │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    Output Site                          │
├─────────────────────────────────────────────────────────┤
│  /docs/           ← from main (latest)                  │
│  /docs/2.0/       ← from release/2.0                    │
│  /docs/1.0/       ← from release/1.0                    │
└─────────────────────────────────────────────────────────┘

Bengal uses Git worktrees to check out each branch without affecting your working directory.

Configuration

Configuration works in bothbengal.yaml and bengal.tomlformats.

Basic Setup

# bengal.yaml (or bengal.toml)
versioning:
  enabled: true
  mode: git

  git:
    branches:
      # Explicit branch
      - name: main
        latest: true

      # Pattern matching
      - pattern: "release/*"
        strip_prefix: "release/"

    default_branch: main
    parallel_builds: 4

Pattern Matching

Match multiple branches with glob patterns:

git:
  branches:
    # Match release/1.0, release/2.0, etc.
    - pattern: "release/*"
      strip_prefix: "release/"    # release/2.0 → version "2.0"

    # Match v1.0.0, v2.0.0, etc.
    - pattern: "v*"
      # No strip_prefix → version "v1.0.0"

Using Tags

Build from Git tags instead of (or in addition to) branches:

git:
  branches:
    - name: main
      latest: true

  tags:
    - pattern: "v*"
      strip_prefix: "v"    # v1.0.0 → version "1.0.0"

Full Configuration Reference

versioning:
  enabled: true
  mode: git

  git:
    # Branch patterns
    branches:
      - name: main              # Explicit branch name
        latest: true            # This is the latest version

      - pattern: "release/*"    # Glob pattern
        version_from: branch    # How to extract version ID (see below)
        strip_prefix: "release/"
        latest: false

# version_from options:
#   branch - Use branch name (default)
#   tag    - Use tag name
#   <regex> - Custom regex pattern

    # Tag patterns (optional)
    tags:
      - pattern: "v*"
        strip_prefix: "v"

    # Settings
    default_branch: main        # Fallback if no latest specified (default: "main")
    cache_worktrees: true       # Keep worktrees for faster rebuilds (default: true)
    parallel_builds: 4          # Number of concurrent builds (default: 4)

  # Standard versioning options still apply
  sections:
    - docs

  aliases:
    latest: main
    stable: "2.0"

Building

Build All Versions

# Discover and build all matching branches/tags
bengal build --all-versions

Output:

🔍 Discovering versions from git...
Found 3 versions to build
  • main
  • 2.0
  • 1.0

📦 Building version main...
✅ Built 45 pages

📦 Building version 2.0...
✅ Built 42 pages

📦 Building version 1.0...
✅ Built 38 pages

✅ Built 3 versions

Build Specific Version

# Build only version 2.0
bengal build --version 2.0

Regular Build (Current Branch Only)

# Build current branch as unversioned site
bengal build

Comparing Versions

Compare content between Git refs:

# Compare branches
bengal version diff main release/2.0 --git

# Output as markdown (for release notes)
bengal version diff main release/2.0 --git --output markdown

# Output as JSON (for automation)
bengal version diff main release/2.0 --git --output json

Example output:

📊 Version Diff: release/2.0 → main

Version diff: release/2.0 → main
  Added: 3 pages
  Removed: 1 pages
  Modified: 12 pages
  Unchanged: 28 pages

✨ Added pages:
  + docs/new-feature.md
  + docs/api/webhooks.md
  + docs/guide/advanced.md

🗑️ Removed pages:
  - docs/deprecated-guide.md

📝 Modified pages:
  ~ docs/installation.md (45.2% changed)
  ~ docs/configuration.md (12.8% changed)

CI/CD Integration

GitHub Actions Example

# .github/workflows/docs.yml
name: Build Docs

on:
  push:
    branches:
      - main
      - 'release/*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Need full history for branches

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.14'

      - name: Install Bengal
        run: pip install bengal

      - name: Build All Versions
        run: bengal build --all-versions

      - name: Deploy
        # Pin to SHA for supply chain security (v3.9.3)
        uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

GitLab CI Example

# .gitlab-ci.yml
build-docs:
  image: python:3.14
  script:
    - pip install bengal
    - bengal build --all-versions
  artifacts:
    paths:
      - public/

Worktrees

Bengal uses Git worktrees to check out multiple branches simultaneously:

.bengal/
└── worktrees/
    ├── 2.0/          # Checked out from release/2.0
    └── 1.0/          # Checked out from release/1.0

Caching Worktrees

By default, worktrees are cached for faster rebuilds:

git:
  cache_worktrees: true    # Keep worktrees between builds

Set to falseto always create fresh worktrees (slower but cleaner).

Manual Cleanup

# Remove cached worktrees
rm -rf .bengal/worktrees
git worktree prune

Troubleshooting

Branch Not Found

❌ Version 2.0 not found

Check that the branch exists:

git branch -a | grep release

Verify your pattern matches:

branches:
  - pattern: "release/*"    # Must match exactly

Worktree Errors

Failed to create worktree

Clean up stale worktrees:

git worktree prune
rm -rf .bengal/worktrees

Permission Errors

error: unable to create file: Permission denied

Ensure write access to the.bengal/directory:

chmod -R u+w .bengal/

If running in CI, ensure the checkout step has write permissions.

Slow Builds

Reduce parallel builds if memory is limited:

git:
  parallel_builds: 2    # Reduce from default 4

Next Steps

4

Cross-Version Links

Link to specific documentation versions from any page

Bengal extends its[[link]]syntax to support cross-version links—links that point to a specific version of a page.

Syntax

[[version:path]]
[[version:path|Custom Text]]
[[version:path#anchor]]

Examples

See the [[v2:docs/installation]] for the old setup process.

Renders as a link to /docs/v2/installation/with the page title as link text.

With Custom Text

Check the [[v2:docs/migration|v2 Migration Guide]] before upgrading.

Renders as a link to /docs/v2/migration/with text "v2 Migration Guide"

With Anchor

The [[v2:docs/api#authentication|v2 auth section]] explains the old flow.

Links to /docs/v2/api/#authentication.

Using Aliases

You can use version aliases instead of IDs:

[[latest:docs/guide]]    <!-- Links to latest version -->
[[stable:docs/api]]      <!-- Links to stable alias -->
[[lts:docs/security]]    <!-- Links to LTS alias -->

Use Cases

Migration Guides

When writing upgrade documentation, link to the old version for reference:

# Upgrading from v2 to v3

## Breaking Changes

### Authentication

In v3, we switched from API keys to OAuth. See [[v2:docs/auth|v2 authentication docs]]
for the old approach.

**Before (v2):**
```python
client = Client(api_key="...")
```

**After (v3):**
```python
client = Client(oauth_token="...")
```

Changelogs

Reference specific version documentation in your changelog:

## v3.0.0

### Breaking Changes

- Removed `legacy_mode` option ([[v2:docs/config#legacy-mode|see v2 docs]])
- Changed default timeout from 30s to 60s

### New Features

- Added webhook support ([[latest:docs/webhooks|see docs]])

Deprecation Notices

Point users to the new approach while documenting the old:

:::{deprecated} v3.0
The `old_function()` is deprecated. See [[latest:docs/api#new-function]]
for the replacement.
:::

Behavior

Version Not Found

If the specified version doesn't exist, Bengal renders a broken reference indicator:

[[v99:docs/guide]]

Renders as:

<span class="broken-ref" data-ref="v99:docs/guide"
      title="Version not found: v99">[docs/guide]</span>

The broken-ref class allows styling via CSS, and the titleattribute shows the error on hover.

Page Not Found in Version

If the version exists but the page doesn't, Bengal still generates the link (it might be a valid page in that version's build).

Links to the latest version use clean URLs without version prefix:

[[latest:docs/guide]]  →  /docs/guide/  (not /docs/v3/guide/)

Configuration

Cross-version links work automatically when versioning is enabled. No additional configuration needed.

# bengal.yaml
versioning:
  enabled: true
  versions:
    - id: v3
      latest: true
    - id: v2
    - id: v1
  aliases:
    latest: v3
    stable: v3
    lts: v2

You can mix cross-version links with regular links:

## Related Documentation

- [[docs/installation]] — Current installation guide
- [[v2:docs/installation]] — v2 installation guide
- [[v1:docs/installation]] — v1 installation guide (deprecated)

Tips

Use for Upgrade Paths

Create a clear upgrade path by linking between versions:

# v2 → v3 Migration

1. Review [[v2:docs/breaking-changes|v2 breaking changes]]
2. Update your config ([[v3:docs/config|v3 config reference]])
3. Test your integration

Don't Overuse

Cross-version links are powerful but can clutter content. Use them primarily in:

  • Migration guides
  • Changelogs
  • Deprecation notices
  • Version comparison pages

For regular content, users expect links to stay within their current version.

Next Steps

5

Version Directives

Mark content as added, deprecated, or changed in specific versions

Version Directives

Bengal provides three directives for marking version-specific content:

Directive Purpose Rendered As
:::{since} Feature added in version X New in X
:::{deprecated} Feature deprecated in version X Deprecated since X
:::{changed} Behavior changed in version X Changed in X

Each directive renders with an SVG icon and themed styling using CSS classes.

Since Directive

Marks content that was added in a specific version.

Syntax

:::{since} v2.0
This feature was added in version 2.0.
:::

Rendered Output

New in v2.0

This feature was added in version 2.0.

Use Cases

Document new features:

## Webhooks

:::{since} v2.5
Webhooks allow you to receive real-time notifications when events occur.
:::

To configure webhooks:
1. Go to Settings → Webhooks
2. Add your endpoint URL
3. Select events to subscribe to

Mark new parameters:

### Parameters

| Name | Type | Description |
|------|------|-------------|
| `timeout` | `int` | Request timeout in seconds |
| `retries` | `int` | Number of retries :::{since} v2.1 ::: |

Deprecated Directive

Marks content that is deprecated and will be removed in a future version.

Syntax

:::{deprecated} v3.0
Use `new_function()` instead. This will be removed in v4.0.
:::

Rendered Output

Deprecated since v3.0

Usenew_function()instead. This will be removed in v4.0.

Use Cases

Deprecate a function:

## old_function()

:::{deprecated} v3.0
This function is deprecated. Use [[docs/api#new-function|new_function()]] instead.
:::

```python
# Don't use this
result = old_function(data)

# Use this instead
result = new_function(data)
```

Deprecate a configuration option:

### legacy_mode

:::{deprecated} v2.5
The `legacy_mode` option is deprecated and will be removed in v3.0.
Migrate to the new configuration format.
:::

```yaml
# Deprecated
legacy_mode: true

# New approach
compatibility:
  version: 2
```

Changed Directive

Marks content where behavior changed in a specific version.

Syntax

:::{changed} v2.5
Default timeout changed from 30 seconds to 60 seconds.
:::

Rendered Output

Changed in v2.5

Default timeout changed from 30 seconds to 60 seconds.

Use Cases

Document behavior changes:

## Configuration

### timeout

The request timeout in seconds.

:::{changed} v2.5
Default changed from 30 to 60 seconds for better reliability with slow networks.
:::

**Default:** 60 seconds

Document API changes:

## Response Format

:::{changed} v3.0
The response now includes a `metadata` field with additional information.
:::

```json
{
  "data": [...],
  "metadata": {    // New in v3.0
    "total": 100,
    "page": 1
  }
}
```

Inline Usage

All directives can be used inline for brief annotations:

| Option | Default | Description |
|--------|---------|-------------|
| `timeout` | 60s | Request timeout :::{changed} v2.5 was 30s ::: |
| `retries` | 3 | Retry count :::{since} v2.0 ::: |
| `legacy` | false | Legacy mode :::{deprecated} v3.0 ::: |

Without Version Number

Thedeprecated and changeddirectives support omitting the version number for generic notices:

:::{deprecated}
This feature is deprecated.
:::

:::{changed}
This behavior has changed.
:::

Note

Thesincedirective requires a version number. Without one, no output is rendered.

Styling

Each directive renders with an inline SVG icon and themed styling. The directives use CSS classes you can customize:

/* Since directive - success/green theme */
.version-since {
  --version-color: var(--color-success);
  background-color: var(--color-success-bg);
}

/* Deprecated directive - warning/orange theme */
.version-deprecated {
  --version-color: var(--color-warning);
  background-color: var(--color-warning-bg);
}

/* Changed directive - info/blue theme */
.version-changed {
  --version-color: var(--color-info);
  background-color: var(--color-info-bg);
}

Badges include Lucide-style SVG icons (.version-badge-icon) that inherit the theme color via currentColor. Full directive containers (when content is provided) feature a luminescent glow animation and neumorphic styling.

Combining Directives

You can combine directives for comprehensive version history:

## process_data()

:::{since} v1.0
Core data processing function.
:::

:::{changed} v2.0
Added support for streaming data.
:::

:::{changed} v2.5
Default batch size increased from 100 to 1000.
:::

Processes data with automatic batching and retry logic.

Best Practices

Be Specific

Vague:

:::{changed} v2.0
This was changed.
:::

Specific:

:::{changed} v2.0
Return type changed from `list` to `iterator` for memory efficiency.
:::

Include Migration Path

For deprecations, always explain what to use instead:

:::{deprecated} v3.0
Use `new_api()` instead. See the [[docs/migration|migration guide]] for details.
:::

Don't Overuse

Version directives are most valuable for:

  • Breaking changes
  • New major features
  • Deprecations

Don't annotate every minor change—it creates noise.

Next Steps

✓ Track Complete