Bengal provides multiple ways to create links between pages, headings, and external resources. Choose the method that best fits your use case.
Tip
Try it out: This guide includes a test target anchor that demonstrates arbitrary reference targets. Scroll down to see it in action!
Quick Reference
| Method | Use Case | Syntax |
|---|---|---|
| Markdown Links | Standard links, external URLs | [text](url) |
| Cross-References | Internal page links with auto-title | [[path]] |
| External References | Link to external docs (Python, NumPy, etc.) | [[ext:project:target]] |
| Template Functions | Dynamic links in templates | {{ ref('path') }} |
| Anchor Links | Link to headings | [[#heading]]or#heading |
| Target Directives | Arbitrary anchors mid-page | :::{target} id |
| Target References | Link to target directives | [[!target-id]] |
Standard Markdown Links
Use standard Markdown link syntax for external URLs and simple internal links.
External Links
[Visit GitHub](https://github.com)
[Email us](mailto:hello@example.com)
Internal Links
Absolute paths (from site root):
[Get Started](/docs/get-started/)
[API Reference](/docs/reference/api/)
Relative paths (from current page):
<!-- From docs/get-started/installation.md -->
[Quickstart](./quickstart.md)
[Configuration](../reference/config.md)
[Home](../../_index.md)
Path Resolution Rules:
.mdextension is optional — Bengal resolves bothpage.mdandpage- Relative paths resolve from the current page's directory
./refers to current directory,../goes up one level- Paths are normalized automatically (trailing slashes, etc.)
Link with Title
[Link text](https://example.com "Optional title")
Cross-References ([[link]]Syntax)
Cross-references provide intelligent linking with automatic title resolution and O(1) lookup performance. Use them for internal page links.
Basic Cross-Reference
[[docs/getting-started]]
This automatically:
- Resolves to the page at
docs/getting-started.md - Uses the page's title as link text
- Handles path normalization automatically
Custom Link Text
[[docs/getting-started|Get Started Guide]]
The|separator lets you specify custom link text while still using intelligent path resolution.
Link by Custom ID
If a page has a customidin frontmatter:
---
id: install-guide
title: Installation Guide
---
Link to it by ID:
[[id:install-guide]]
[[id:install-guide|Installation]]
Benefits of ID-based links:
- Stable even if page path changes
- Shorter syntax
- Works across site restructures
Link to Headings
Link to any heading in your site:
[[#installation]]
[[#configuration]]
This finds the heading text (case-insensitive) and links to it. If multiple pages have the same heading, it uses the first match.
Link to heading in specific page:
[[docs/getting-started#installation]]
Path Resolution
Cross-references support multiple path formats:
[[docs/getting-started]] # Path without extension
[[docs/getting-started.md]] # Path with extension (also works)
[[getting-started]] # Slug (if unique)
[[id:install-guide]] # Custom ID
Resolution order:
- Custom ID (
id:xxx) - Path lookup (
docs/page) - Slug lookup (
page-name)
Broken References
If a cross-reference can't be resolved, Bengal shows a broken reference indicator:
[[nonexistent-page]]
Renders as:[nonexistent-page]with a visual indicator for debugging.
Anchor Links
Link to headings within the same page or across pages.
Same-Page Anchors
[Installation](#installation)
[Configuration](#configuration)
Headings automatically get anchor IDs based on their text. For example:
## Installation
This heading gets ID: `installation`
Custom Anchor IDs
Use{#custom-id}syntax for custom anchor IDs on headings:
## Installation {#install-guide}
Link to it: [[#install-guide]]
Arbitrary Reference Targets
Create anchor targets anywhere in content (not just on headings) using the{target}directive. This is similar to RST's.. _label:syntax.
Syntax:
:::{target} my-anchor-id
:::
Example: Target Directive Usage
:::{target} important-caveat
:::
:::{warning}
This caveat is critical for production use.
:::
See [[!important-caveat|the caveat]] for details.
Reference Syntax:
Use[[!target-id]]to explicitly reference target directives:
[[!test-target]] # Link with auto-generated text
[[!test-target|Custom Text]] # Link with custom text
Why!instead of#?
The!prefix distinguishes target directive references from heading anchor references:
[[#heading]]- References heading anchors (auto-generated or custom{#id})[[!target-id]]- References target directives (explicit:::{target})
This eliminates collisions and makes your intent explicit.
Use cases:
- Anchor before a note/warning that users should link to
- Stable anchor that survives heading text changes
- Anchor in middle of content (not tied to heading)
- Migration from Sphinx (
.. _label:) or RST reference targets
Anchor ID requirements:
- Must start with a letter (a-z, A-Z)
- May contain letters, numbers, hyphens, underscores
- Case-sensitive in output, case-insensitive for resolution
Note: The target renders as an invisible anchor element. Any content inside the directive is ignored (targets are point anchors, not containers).
Try it: This page has a test target below. Jump to it: Test Target
This is a test target anchor. You can link to it using[[!test-target]]from anywhere in your site.
Cross-Page Anchors
[Installation](/docs/getting-started/#installation)
[[docs/getting-started#installation]]
Both syntaxes work. Cross-references are preferred for internal links.
Template Functions
Use template functions in Kida templates for dynamic link generation.
ref(path, text=None)
Generate a cross-reference link:
{{ ref('docs/getting-started') }}
{{ ref('docs/getting-started', 'Get Started') }}
{{ ref('id:install-guide') }}
Returns: Safe HTML link (<a href="...">...</a>)
Use cases:
- Dynamic navigation menus
- Related pages sections
- Breadcrumbs
doc(path)
Get a page object for custom link generation:
{% let page = doc('docs/getting-started') %}
{% if page %}
<a href="{{ page.url }}">{{ page.title }}</a>
<p>{{ page.description }}</p>
{% end %}
Returns:Pageobject orNone
Use cases:
- Custom link formatting
- Accessing page metadata
- Conditional rendering
anchor(heading, page_path=None)
Link to a heading:
{{ anchor('Installation') }}
{{ anchor('Configuration', 'docs/getting-started') }}
Parameters:
heading: Heading text to find (case-insensitive)page_path: Optional page path to restrict search
Returns: Safe HTML link with anchor fragment
Use cases:
- Table of contents
- "Jump to" links
- Cross-page heading references
relref(path)
Get relative URL without generating a link:
<a href="{{ relref('docs/api') }}" class="btn">API Docs</a>
{% let api_url = relref('docs/api') %}
{% if api_url %}
<link rel="preload" href="{{ api_url }}" as="document">
{% end %}
Returns: URL string or empty string if not found
Use cases:
- Custom link HTML
- Meta tags
- Preload/prefetch links
Path Resolution
Understanding how Bengal resolves paths helps you write reliable links.
Path Formats
Bengal accepts multiple path formats:
[[docs/getting-started]] # Recommended: path without extension
[[docs/getting-started.md]] # Also works: path with extension
[[getting-started]] # Slug (if unique across site)
[[id:install-guide]] # Custom ID
Resolution Order
When resolving a cross-reference:
- Custom ID (
id:xxx) — Checked first, fastest lookup - Path lookup (
docs/page) — Most explicit, recommended - Slug lookup (
page-name) — Fallback, may be ambiguous
Relative Path Resolution
Relative paths resolve from the current page's directory:
<!-- File: docs/getting-started/installation.md -->
[[./quickstart]] # docs/getting-started/quickstart.md
[[../reference/config]] # docs/reference/config.md
[[../../_index]] # _index.md (site root)
Rules:
.mdextension is optional./refers to current directory../goes up one level- Paths are normalized (trailing slashes, etc.)
Best Practices
When to Use Each Method
Use Markdown Links ([text](url)) when:
- Linking to external URLs
- You need full control over link text
- Simple relative paths within a section
Use Cross-References ([[path]]) when:
- Linking to internal pages
- You want automatic title resolution
- You want stable links that survive path changes (with IDs)
- You're writing documentation
Use Template Functions (ref(),doc(), etc.) when:
- Generating links dynamically in templates
- Building navigation menus
- Creating related pages sections
- Conditional link rendering
Use Anchor Links (#heading) when:
- Linking to specific sections
- Creating table of contents
- Cross-referencing specific content
Use Target Directives (:::{target}) when:
- Creating anchors not tied to headings
- Need stable anchors that survive content restructuring
- Migrating from RST/Sphinx (
.. _label:) - Anchoring before notes/warnings for direct linking
Reference with[[!target-id]]:
- Explicit syntax avoids collisions with heading anchors
- Makes intent clear (target directive vs heading)
- Required for target directive references
Link Stability
Most Stable (survives path changes):
- Custom IDs:
[[id:my-page]] - Template functions with IDs:
{{ ref('id:my-page') }}
Moderately Stable (survives minor changes):
- Path-based cross-references:
[[docs/page]] - Absolute markdown links:
[text](/docs/page/)
Least Stable (breaks on path changes):
- Relative markdown links:
[text](../page.md) - Slug-based references (if slugs change)
Performance
All linking methods use O(1) lookups from pre-built indexes:
- Cross-references: Built during discovery phase
- Template functions: Use same index
- Markdown links: Resolved during rendering
No performance difference between methods — choose based on use case.
Examples
Navigation Menu
<nav>
{% for item in site.menus.main %}
<a href="{{ item.url }}">{{ item.name }}</a>
{% endfor %}
</nav>
Related Pages Section
## Related Pages
- [[docs/tutorials/getting-started|Getting Started]]
- [[docs/reference/api|API Reference]]
- [[docs/tutorials/examples|Examples]]
Dynamic Related Pages
{% let related = site.pages | where('tags', page.tags, 'in') | limit(5) %}
{% if related %}
<h2>Related Pages</h2>
<ul>
{% for page in related %}
<li>{{ ref(page.path) }}</li>
{% endfor %}
</ul>
{% end %}
Table of Contents
## Table of Contents
- [[#introduction|Introduction]]
- [[#installation|Installation]]
- [[#configuration|Configuration]]
- [[#usage|Usage]]
Cross-Reference in Content
For detailed setup instructions, see [[docs/getting-started/installation|the installation guide]].
Once installed, configure your site as described in [[docs/reference/config#site-settings|Site Settings]].
Troubleshooting
Broken Links
If a link doesn't resolve:
- Check path spelling — Paths are case-sensitive
- Verify page exists — Use
bengal validateto check - Check path format — Use path without
.mdextension - Try absolute path — Use
/docs/pageinstead of relative
Link Validation
Bengal validates links during build:
bengal validate
This checks:
- Cross-references resolve
- Markdown links point to existing pages
- Anchor links target valid headings
Debugging Broken References
Broken cross-references show visual indicators:
<span class="broken-ref" data-ref="nonexistent-page"
title="Page not found: nonexistent-page">
[nonexistent-page]
</span>
Use browser dev tools to inspect broken references and see what path was attempted.
See Also
- External References — Link to Python stdlib, NumPy, other Bengal sites
- Content Authoring — Markdown and MyST syntax
- Template Functions — All template helpers
- Content Validation — Link validation and health checks
- Graph Analysis — Analyze site connectivity