Hugo Mount Configuration: How to Mount Content from Different Sources

Hugo handles content from different sources by merging overlay mounts that map arbitrary directories or files from your project or imported modules onto component roots like content, assets, static, and layouts, processed through collection, normalization, and filesystem creation stages.

Hugo's mount configuration system creates a unified filesystem by overlaying directories from multiple sources onto logical component paths. According to the gohugoio/hugo source code, this mechanism processes module imports through modules/collect.go, validates mount definitions in modules/config.go, and constructs the final overlay filesystem in hugofs/rootmapping_fs.go that presents a single coherent view of content scattered across your project, themes, and external modules.

The Three Stages of Mount Processing

Hugo processes mount configurations through a three-stage pipeline implemented in modules/collect.go. This ensures that every module import contributes its files safely to the unified filesystem.

Stage 1: Collecting Module Configuration with applyMounts

The mount collection process begins in applyMounts (lines 416-459 of modules/collect.go). For each module import, Hugo determines which directories should be mounted:

  • If import.NoMounts is true, the module contributes no files to the filesystem.
  • If the import defines explicit module.mounts, those definitions take precedence.
  • If no mounts are declared, Hugo creates default mounts for every component folder that exists in the module (such as content, assets, static, or layouts).

This fallback mechanism ensures that traditional themes work immediately without explicit mount configuration while allowing advanced users to define precise mappings.

Stage 2: Normalizing and Validating Mounts with normalizeMounts

Once collected, raw mount definitions pass through normalizeMounts (lines 711-749 of modules/collect.go) for validation and transformation:

  • Path cleaning: Source and target paths are cleaned and made absolute relative to the module's root directory.
  • Source restrictions: For non-project modules, sources must be local paths; absolute sources are permitted only in the main project.
  • Special handling: The function manages node_modules imports and prevents mounting of the public folder or hugo_stats.json.
  • Filter creation: The files parameter converts into an inclusion/exclusion filter using glob patterns (e.g., "! drafts/*" excludes files).
  • Language migration: The deprecated lang field migrates to the newer sites.matrix representation during initialization.

This stage ensures that only safe, well-formed mounts reach the filesystem layer.

Stage 3: Creating the Overlay Filesystem with RootMappingFs

Validated mounts feed into the RootMappingFs structure defined in hugofs/rootmapping_fs.go. When Hugo requests content from a component root, the Mounts method (lines 36-64) resolves the physical sources:

  1. Prefix matching: The method looks up every RootMapping whose target path is a prefix of the requested component path.
  2. Filesystem construction: For each match, Hugo creates a BasePathFs wrapping the underlying filesystem. If the mount includes a files filter, it wraps this with a filename filter filesystem.
  3. Directory decoration: The filesystem passes through decorateDirs to attach metadata.
  4. Stacking: The resulting FileMetaInfo objects stack according to mount weight, with later mounts winning precedence in case of path conflicts.

This overlay approach allows content from your project, a theme, and node_modules to coexist under a single logical content directory without physical copying.

Configuring Mounts in Your Project

Hugo exposes mount configuration through the module.mounts section of your site configuration, supporting both TOML and YAML formats.

Basic Mount Configuration

Define mounts by specifying the source directory and the target component root:

[module]
  [[module.mounts]]
    source = "content"
    target = "content/blog"

  [[module.mounts]]
    source = "node_modules/bootstrap/dist/js"
    target = "assets/js/bootstrap"

In this example, the project's content directory mounts to content/blog, effectively relocating all posts under the /blog/ URL path. The second mount makes Bootstrap's JavaScript files available under assets/js/bootstrap for Hugo's asset pipeline.

Advanced Mount Options with File Filtering

Mounts support granular control through the files parameter, which accepts glob patterns for inclusion or exclusion:

[[module.mounts]]
  source = "content"
  target = "content"
  files  = ["! drafts/*", "! **/private.md"]

The ! prefix excludes matching files. This configuration mounts the content directory but omits any files in drafts/ subdirectories and any file named private.md. According to modules/config.go, these patterns convert into an InclusionFilter applied during the BasePathFs creation stage.

Summary

Hugo's mount configuration system creates a unified filesystem by overlaying directories from multiple sources onto component roots. Key implementation details include:

  • Three-stage processing: Mounts collect via applyMounts in modules/collect.go, normalize through normalizeMounts, and resolve through RootMappingFs.Mounts in hugofs/rootmapping_fs.go.
  • Automatic defaults: Modules without explicit mounts receive default mappings for existing component folders (content, assets, static, layouts).
  • File filtering: The files parameter supports glob patterns for selective inclusion or exclusion of content.
  • Overlay precedence: Later mounts in the configuration stack take precedence, allowing project files to override theme content.

Frequently Asked Questions

What is the difference between source and target in Hugo mounts?

The source parameter specifies the physical directory path relative to the module's root, while the target defines the logical location within Hugo's unified filesystem where that content appears. For example, mounting source = "content" to target = "content/blog" makes files physically located in the project's content/ folder available under the /blog/ URL path.

Can I mount content from node_modules or external directories?

Yes, Hugo explicitly supports mounting from node_modules and other external directories. In normalizeMounts within modules/collect.go, special handling exists for node_modules imports. You can mount assets from installed packages by defining a mount with source = "node_modules/some-package/dist" and target = "assets/js/lib". However, absolute source paths are restricted to the main project; imported modules must use relative paths.

How does Hugo handle file conflicts when multiple mounts target the same path?

Hugo resolves conflicts through mount weight and stacking order. When RootMappingFs.Mounts in hugofs/rootmapping_fs.go collects matching filesystems, it stacks them according to their configuration sequence. Later mounts win precedence, meaning if both a theme and your project mount to content/posts, the project's version takes priority. The decorateDirs function attaches metadata that helps Hugo determine which physical source provides each file.

What happens if a module has no explicit mounts defined?

If a module lacks explicit module.mounts configuration, Hugo automatically creates default mounts during the applyMounts stage in modules/collect.go. The collector inspects the module's directory structure and generates implicit mounts for every standard component folder that exists: content, assets, static, layouts, data, and i18n. This ensures backward compatibility with traditional Hugo themes while still utilizing the overlay filesystem architecture.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →