`module.mounts` overrides for theme and site behave differently

Hi! I couldn’t find anything explaining this in docs, only some source code examples that confirm that behavior (1 and 2). I am probably misunderstanding something, so please help me figure this out

In docs for module.mounts there is this note

Adding a new mount to a target root will cause the existing default mount for that root to be ignored. If you still need the default mount, you must explicitly add it along with the new mount.

I started with this config

$ hugo env
hugo v0.151.2+extended+withdeploy darwin/arm64 BuildDate=2025-10-16T16:52:34Z VendorInfo=brew
GOOS="darwin"
GOARCH="arm64"
GOVERSION="go1.25.3"
github.com/sass/libsass="3.6.6"
github.com/webmproject/libwebp="v1.3.2"
$ hugo config mounts
{
   "path": "project",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "",
   "dir": "/blog/site",
   "mounts": [
      {
         "source": "content",
         "target": "content"
      },
      {
         "source": "data",
         "target": "data"
      },
      {
         "source": "layouts",
         "target": "layouts"
      },
      {
         "source": "i18n",
         "target": "i18n"
      },
      {
         "source": "archetypes",
         "target": "archetypes"
      },
      {
         "source": "assets",
         "target": "assets"
      },
      {
         "source": "static",
         "target": "static"
      }
   ]
}
{
   "path": "../../theme",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "project",
   "dir": "/blog/theme/",
   "mounts": [
      {
         "source": "archetypes",
         "target": "archetypes"
      },
      {
         "source": "assets",
         "target": "assets"
      },
      {
         "source": "content",
         "target": "content"
      },
      {
         "source": "data",
         "target": "data"
      },
      {
         "source": "i18n",
         "target": "i18n"
      },
      {
         "source": "layouts",
         "target": "layouts"
      },
      {
         "source": "static",
         "target": "static"
      }
   ]
}

If I update the hugo.toml for the site with custom mount, it behaves exactly as documentation is outlined

+[module]
+  [[module.mounts]]
+    includeFiles = ['[0-9][0-9][0-9][0-9]-*.md']
+    source = '../adrs'
+    target = 'content/adrs'
{
   "path": "project",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "",
   "dir": "/blog/site",
   "mounts": [
      {
         "source": "../adrs",
         "target": "content/adrs"
      },
      {
         "source": "data",
         "target": "data"
      },
      {
         "source": "layouts",
         "target": "layouts"
      },
      {
         "source": "i18n",
         "target": "i18n"
      },
      {
         "source": "archetypes",
         "target": "archetypes"
      },
      {
         "source": "assets",
         "target": "assets"
      },
      {
         "source": "static",
         "target": "static"
      }
   ]
}
{
   "path": "../../theme",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "project",
   "dir": "/blog/theme/",
   "mounts": [
      ...skipped
   ]
}

But if I do something similar in hugo.toml for a theme, I get mounts fully replaced with the config, not just the ones that I am overriding.

{
   "path": "project",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "",
   "dir": "/blog/site",
   "mounts": [
     ...skipped
   ]
}
{
   "path": "../../theme",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "project",
   "dir": "/blog/theme/",
   "mounts": [
      {
         "source": "../adrs",
         "target": "content/adrs"
      }
   ]
}

I know I can fix this by manually adding all required mounts to a theme’s hugo.toml, but I wanted to understand better why it behaves differently and if this is a bug or works exactly as it should?

Are you doing something like this?

cd themes/my-theme/
hugo config mounts

Hi! No, I am calling hugo config mounts from the site directory. The site and the theme were generated by hugo new site site and hugo new theme theme, and I didn’t change much yet

site/hugo.toml at the start

baseURL = 'http://example.com'
languageCode = 'en-us'
title = 'Blog'

[caches]
  [caches.images]
    dir = ':cacheDir/images'

[module]
  replacements = 'github.com/imomaliev/blog/theme -> ../../theme'

  [[module.imports]]
    path = 'github.com/imomaliev/blog/theme'

theme/hugo.toml at the start

baseURL = 'https://example.org/'
languageCode = 'en-US'
title = 'My New Hugo Site'

[menus]
  [[menus.main]]
    name = 'Home'
    pageRef = '/'
    weight = 10

  [[menus.main]]
    name = 'Posts'
    pageRef = '/posts'
    weight = 20

  [[menus.main]]
    name = 'ADRs'
    pageRef = '/adrs'
    weight = 30

  [[menus.main]]
    name = 'Tags'
    pageRef = '/tags'
    weight = 40

[module]
  [module.hugoVersion]
    extended = false
    min = '0.146.0'

Here is my file tree

$ git ls-files | tree -L 2 --fromfile
.
├── README.md
├── adrs
│   ├── 0001-documenting-architectural-decisions.md
│   └── TEMPLATE.md
├── site
│   ├── archetypes
│   ├── content
│   └── hugo.toml
└── theme
    ├── archetypes
    ├── assets
    ├── content
    ├── hugo.toml
    ├── layouts
    └── static

Here’s an example site:

git clone --single-branch -b hugo-forum-topic-56104 https://github.com/jmooring/hugo-testing hugo-forum-topic-56104
cd hugo-forum-topic-56104
hugo config mounts

The result is correct. What’s different?

This is exactly the thing that I am trying to highlight

$ git clone --single-branch -b hugo-forum-topic-56104 https://github.com/jmooring/hugo-testing hugo-forum-topic-56104
$ cd hugo-forum-topic-56104
$ hugo config mounts
{
   "path": "project",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "",
   "dir": "/tmp/hugo-forum-topic-56104",
   "mounts": [
      {
         "source": "content",
         "target": "content"
      },
      {
         "source": "content-a",
         "target": "content"
      },
      {
         "source": "data",
         "target": "data"
      },
      {
         "source": "layouts",
         "target": "layouts"
      },
      {
         "source": "i18n",
         "target": "i18n"
      },
      {
         "source": "archetypes",
         "target": "archetypes"
      },
      {
         "source": "assets",
         "target": "assets"
      },
      {
         "source": "static",
         "target": "static"
      }
   ]
}
{
   "path": "my-theme",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "project",
   "dir": "/tmp/hugo-forum-topic-56104/themes/my-theme/",
   "mounts": [
      {
         "source": "content",
         "target": "content"
      },
      {
         "source": "content-b",
         "target": "content"
      }
   ]
}

default mounts for the theme are gone for some reason, meaning only the explicit mounts form themes/my-theme/hugo.toml are present. Which means if I want to use layout from my-theme, I would have to manually add this mount as well. But for a site, overriding mounts does not remove other default ones.

I expect that the output would look like this instead:

$ hugo config mounts
{
   "path": "project",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "",
   "dir": "/tmp/hugo-forum-topic-56104",
   "mounts": [
      {
         "source": "content",
         "target": "content"
      },
      {
         "source": "content-a",
         "target": "content"
      },
      {
         "source": "data",
         "target": "data"
      },
      {
         "source": "layouts",
         "target": "layouts"
      },
      {
         "source": "i18n",
         "target": "i18n"
      },
      {
         "source": "archetypes",
         "target": "archetypes"
      },
      {
         "source": "assets",
         "target": "assets"
      },
      {
         "source": "static",
         "target": "static"
      }
   ]
}
{
   "path": "my-theme",
   "version": "",
   "time": "0001-01-01T00:00:00Z",
   "owner": "project",
   "dir": "/tmp/hugo-forum-topic-56104/themes/my-theme/",
   "mounts": [
      {
         "source": "content",
         "target": "content"
      },
      {
         "source": "content-b",
         "target": "content"
      }
      {
         "source": "data",
         "target": "data"
      },
      {
         "source": "layouts",
         "target": "layouts"
      },
      {
         "source": "i18n",
         "target": "i18n"
      },
      {
         "source": "archetypes",
         "target": "archetypes"
      },
      {
         "source": "assets",
         "target": "assets"
      },
      {
         "source": "static",
         "target": "static"
      }
   ]
}

I’m sorry, I still don’t understand. Are you commenting on the output of the hugo config mounts command, or the content of the published site?

Sorry, English is not my main language, and this may be the reason it is difficult to understand what I am trying to say.

I am commenting on the output of the hugo config mounts, which, if I understand correctly, implies that Hugo sees only content mounts that were explicitly added to my-theme/hugo.toml, and stops seeing default ones like archetypes, layouts etc.

This behavior is different from adding custom mounts to the site’s hugo.toml. Adding an additional mount will override only the default target but will leave the rest untouched. In your example, you’ve added a mount for countent-a and had to explicitly add an additional mount for content.

You’ve done a similar thing for my-theme. But in doing so, all other default mounts are now stopped from being automatically discovered by hugo config

OK, I think I can reproduce this with the published site by adding some assets to both the project and the theme. Pull changes on the test repo above, then run hugo.

Expected console warnings:

WARN  /project-asset.txt
WARN  /theme-asset.txt

Actual console warnings:

WARN  /project-asset.txt
1 Like

Yes, this is a much better approach to highlighting the question at hand, thank you)) I should have done this from the start

Current documentation:

Adding a new mount to a target root will cause the existing default mount for that root to be ignored. If you still need the default mount, you must explicitly add it along with the new mount.

What it should be:

Adding a new mount to a target root will cause the existing default mount for that root to be ignored. If you still need the default mount, you must explicitly add it along with the new mount.

Adding a new mount to a module’s target root will cause the existing default mounts for all roots to be ignored. If you still need a default mount, you must explicitly add it along with the new mount.

But the real issue is… why do you have to do that?

1 Like

Great, I can submit a PR if that’s ok with you)

I am probably doing something wierd)) but I was trying to use the same external folder adrs in my example to host an archetype for a theme and content for site itself here is the repo GitHub - imomaliev/blog: Blog

I am currently using symlink from adrs/TEMPLATE.md to theme/archetypes, but thought the module.mounts system would be a better option and discovered the behavior from this topic

1) Do not use symlinks in a Hugo project… ever. Use module mounts instead.
2) Within a module’s config, you can only mount directories relative to the theme. This is to prevent bad actors.

Please create an issue instead, and link to this forum topic… my wording above needs some tweaking.

1 Like

Yes, I figured using symlinks for this was not a good idea and this is why I was switching to module mounts)

can you please elaborate on this or point me to a documentation about this topic? I am mounting it relative to a theme (meaning relative to site/themes/theme).

  [[module.mounts]]
    source = '../../../adrs/TEMPLATE.md'
    target = 'archetypes/adr.md'

Or you mean something different?

https://gohugo.io/configuration/module/#source

source
(string) The source directory of the mount. For the main project, this can be either project-relative or absolute. For other modules it must be project-relative.

We don’t want a theme/module author to be able to do this:

[[module.mounts]]
source = "/etc/passwd"
target = "assets/passwd"
1 Like

Issue Update documentation for module.mounts usage in modules · Issue #3238 · gohugoio/hugoDocs · GitHub

1 Like

Oh, yeah, totally understand that. Good thing I am not doing that) Thanks!

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.