Content Mounts
Content mounts let you combine multiple filesystem directories into a single site. Each mount attaches a directory at a specific URL prefix in the content tree – no symlinks, no copy scripts, no build-time workarounds.
When to Use Mounts
- Shared documentation: Mount a company-wide docs library under
/docs/shared/ - Vendored content: Mount third-party documentation at
/docs/vendor/ - Split editorial and technical content: Keep blog posts in one directory and API docs in another
- Multi-team sites: Each team maintains their own content directory, merged into one site
Configuration
Add content.mounts to your config.yaml:
# config.yaml content: directory: ./content # primary content (always present) mounts: # Merge shared content into root namespace - source: ./shared-content mount: / # Vendor docs appear under /docs/vendor/ - source: /opt/docs/vendor mount: /docs/vendor # API reference with high priority (overrides primary on collision) - source: ./api-docs mount: /docs/api/reference priority: high default_template: api-page.html.jinja
The primary content.directory always loads first. Mounts are processed in declaration order after it.
Mount Levels
Mounts can attach at any level of the content tree:
| Level | Mount Path | Effect |
|---|---|---|
| 0 (root) | / | Pages merge directly into the top-level URL space |
| 1 (section) | /docs | Pages appear under /docs/page-name |
| 2 (subsection) | /docs/api/reference | Pages nested two levels deep |
Root Mounts
A mount at / merges its pages into the same URL space as your primary content directory. A page at getting-started/default.md in the mount source becomes /getting-started – the same as if it were in your primary content directory.
mounts: - source: ./shared-content mount: /
Section and Subsection Mounts
Mounts at deeper levels prefix all page URLs with the mount path. A page at widgets/default.md in a mount at /products becomes /products/widgets.
mounts: - source: ./product-pages mount: /products
Mount Options
Each mount supports these fields:
| Field | Required | Default | Description |
|---|---|---|---|
source | yes | – | Filesystem path or plugin:<name> identifier |
mount | yes | – | URL prefix where content appears (must start with /) |
priority | no | low | Collision resolution: low or high |
default_template | no | – | Fallback template for pages without template: in frontmatter |
Plugin Mounts
Mounts can also source content from WASM plugins using the plugin:<name> syntax. The plugin’s on_content_load hook is called at startup, and the returned pages are merged into the content tree under the mount prefix.
content: mounts: - source: plugin:accent-notion mount: /kb default_template: kb-article.html.jinja
The plugin must declare on_content_load = true in its plugin.toml:
[hooks]
on_content_load = true
[config]
api_url = "https://api.notion.com/v1"
api_key = ""
refresh_interval_seconds = 120
[network]
allowed_hosts = ["api.notion.com"]
How Plugin Mounts Work
- At startup, Accent calls the plugin’s
on_content_loadWASM function - The plugin fetches content from its external source (API, database, etc.)
- The plugin returns pages as JSON with relative URLs and markdown content
- Accent applies the mount prefix to each URL and merges pages into the content tree
- Plugin pages participate in taxonomy, collections, navigation, and static builds
Plugin JSON Protocol
The plugin receives:
{ "config": { "api_url": "...", "api_key": "..." }, "mount": "/kb", "host": { "production": false, "site_name": "My Site", "site_url": "https://example.com", "language": "en" } }
And returns:
{ "pages": [ { "url": "/getting-started", "markdown": "# Getting Started\n\nWelcome...", "frontmatter": { "title": "Getting Started", "tags": ["docs"], "template": "kb-article.html.jinja" } } ], "error": null }
Plugins return relative URLs (like /getting-started). Accent prepends the mount path, so the page appears at /kb/getting-started. This makes plugins mount-agnostic – the same plugin can be mounted at different paths in different sites.
Plugin Failures
Plugin failures during on_content_load are non-fatal. If a plugin cannot reach its API or returns an error, the server still starts with all other content sources. A warning is logged for each failure.
Collision Resolution
When a mounted page has the same URL as a page in your primary content directory (or another mount), Accent resolves the conflict based on priority:
priority: low(default) – The primary content page wins. The mount page is skipped and a warning is logged.priority: high– The mount page wins and replaces the primary content page. A warning is still logged.
mounts: # These API docs override anything at /docs/api/ in primary content - source: ./generated-api-docs mount: /docs/api priority: high
Collisions always produce a log warning so you can diagnose unexpected shadowing:
WARN Mount '/docs/vendor' page at '/docs/vendor/setup' collides with existing page from primary content, keeping existing (mount priority: low)
Default Templates
Mounts can specify a default_template that applies to pages without an explicit template: field in their frontmatter. This is useful when mounting content that wasn’t authored for your theme:
mounts: - source: ./api-docs mount: /docs/api default_template: api-page.html.jinja
Pages that explicitly set template: in their frontmatter are unaffected – default_template only fills in the gap when no template is specified.
Hierarchy and Navigation
Mounted pages integrate naturally into Accent’s page hierarchy:
- Breadcrumbs work across mount boundaries. A page at
/docs/vendor/getting-startedshows breadcrumbs likeHome > Docs > Vendor > Getting Started. - Parent/child relationships are resolved by URL structure. The parent of
/docs/vendor/setupis/docs/vendor(if an index page exists there), or/docs(the next ancestor with a page). - Sibling navigation (previous/next) works within the mount’s URL subtree.
For breadcrumbs to show the mount root as a level, include an index page (e.g., default.md) in the mount source directory’s root.
Hot Reload
In development mode, Accent watches all mounted directories for changes. When you edit a file in a mount source directory, the page cache is invalidated and your browser refreshes automatically (if browser reload is enabled).
No server restart is needed – save a file in any mounted directory and refresh.
Static Build
accent build includes all mounted content in the static output. Mounted pages appear in:
- The generated HTML files under their mount prefix
- The sitemap (
/sitemap.xml) - RSS feeds (
/feed.xml) if they match the feed criteria - Taxonomy pages (
/tags/) based on their frontmatter tags
Reserved Routes
Mounts cannot claim Accent’s built-in route prefixes. The following paths are protected:
/healthz, /readyz, /health, /_dev, /_admin, /_search, /feed.xml, /sitemap.xml, /robots.txt, /.well-known, /api, /media, /content-media, /theme, /tags
Attempting to mount at any of these paths produces a configuration error at startup.
Root mounts (mount: /) are allowed – they merge into the root namespace without claiming a reserved prefix.
Example: Documentation Site with Shared Library
# config.yaml content: directory: ./content mounts: # Company-wide shared docs - source: ../shared-docs-library/content mount: /docs/shared # Auto-generated API reference - source: ./target/api-docs mount: /docs/api/reference priority: high default_template: api-reference.html.jinja
Directory structure:
my-site/
config.yaml
content/
01.home/default.md -> /home
02.docs/default.md -> /docs
02.docs/01.guides/default.md -> /docs/guides
target/api-docs/
users/default.md -> /docs/api/reference/users
products/default.md -> /docs/api/reference/products
../shared-docs-library/content/
style-guide/default.md -> /docs/shared/style-guide
brand-guidelines/default.md -> /docs/shared/brand-guidelines
Limitations
- Config reload does not pick up mount changes. Sending SIGHUP or calling
POST /_admin/reloadre-scans content within existing mounts (new and changed pages appear), but adding, removing, or changing mount source paths inconfig.yamlrequires a server restart. This matches the behavior ofcontent.directoryitself.
Backward Compatibility
Sites without content.mounts work identically to before. The content.directory field is unchanged and continues to be the only required content configuration. Mounts are fully opt-in.