Build a Hugo npm manifest safely with hugo mod npm pack
You will collect Node dependencies declared by a Hugo project and its imported modules into a project-level package.json. On the installed Hugo 0.123.7, hugo mod npm pack reads package.hugo.json files and writes the resulting manifest in the project root. Allow about 15 minutes for a small project, plus time to inspect the generated JSON before running npm.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide targets Hugo 0.123.7 from the Ubuntu package on this machine. The command is marked experimental, and newer Hugo releases have changed the output layout, so do not apply the exact file expectations below to a different version without checking its help and documentation.
1. Check Hugo and the project root
Run the command from the Hugo project root. It should be a module-aware project with a Hugo configuration file and, normally, a go.mod file. Confirm the binary and version before relying on its output:
$ command -v hugo
/usr/bin/hugo
$ hugo version
hugo v0.123.7+extended linux/amd64 BuildDate=2026-03-17T19:51:14Z VendorInfo=ubuntu:0.123.7-1ubuntu0.3+esm2
$ test -f hugo.yaml -o -f hugo.toml -o -f hugo.json && echo 'Hugo configuration found'
Hugo configuration found
If the project is not a Hugo module yet, initialise it with the module workflow you use for that project before trying to consolidate dependencies. Do not run the pack command from a parent directory: relative paths and the files it writes are resolved from the project it discovers.
Checkpoint: you know the Hugo version, the project directory, and which user owns the files. This operation normally needs no elevated privileges. Do not use sudo merely because the project has JavaScript dependencies.
2. Save the current manifest
On Hugo 0.123.7, the generated output is package.json in the project root. The pack command can replace that file, so make a recoverable copy before running it if one already exists:
$ if test -f package.json; then cp --preserve=all package.json package.json.before-hugo-pack; fi
$ if test -f package.json; then sha256sum package.json; else echo 'no existing package.json'; fi
no existing package.json
Use a destination outside the project if the existing manifest is valuable. The example uses a neighbouring backup so the later restore command is obvious. Treat the backup as temporary working state, not as a substitute for version control.
Warning: do not edit the old manifest while this command is running, and do not assume its scripts, name, version or private flag will survive. The installed command constructs output from the Hugo package template and dependency data. Inspect the diff before accepting the result.
3. Declare project-specific dependencies in package.hugo.json
Create package.hugo.json in the project root when the project itself needs Node packages. Keep the dependency sections in ordinary npm format:
{
"dependencies": {
"alpinejs": "^3.14.0"
},
"devDependencies": {
"vite": "^5.4.0"
}
}
Imported modules can provide their own package.hugo.json files. Hugo merges the dependency sets it finds in the dependency tree, selecting the version closest to the project when the same package is declared more than once. That is why the generated file is a project artefact rather than a hand-maintained copy of every theme's dependency list.
If both package.hugo.json and package.json are present in a module root, current Hugo documentation says the Hugo-specific file takes precedence. The local 0.123.7 command also uses package.hugo.json as its project template. Keep the source file small and deliberate; it is the input that can be regenerated.
4. Run the pack command
Run this as the unprivileged project user:
$ hugo mod npm pack
The command may be quiet when there are no dependencies to collect. On a project with the example template, Hugo 0.123.7 writes a root package.json containing dependency sections and a comments object identifying where each entry came from:
{
"comments": {
"dependencies": {
"alpinejs": "project"
},
"devDependencies": {
"vite": "project"
}
},
"dependencies": {
"alpinejs": "^3.14.0"
},
"devDependencies": {
"vite": "^5.4.0"
}
}
The exact ordering and comments depend on the project and module graph. Do not compare the sample byte-for-byte. Check that the packages you expect are present and that their version ranges are acceptable.
5. Inspect the generated JSON before npm
Validate the file as JSON and review the change. npm is not part of the Hugo command, so this checkpoint does not install anything or change a lockfile:
$ node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8")); console.log("valid JSON")'
valid JSON
$ git diff -- package.json package.hugo.json
$ node -e 'const p=require("./package.json"); console.log(Object.keys(p.dependencies || {}).sort().join("\n"))'
alpinejs
If Node is unavailable, use another JSON parser already approved for your environment. A successful parse does not prove that a package name or version exists in the registry. It only proves that the generated manifest is syntactically readable.
For Hugo releases from 0.159.0 onwards, the official documentation describes a different workspace-oriented output under packages/hugoautogen/package.json, with the root manifest updated with a workspace entry. That is not the behaviour verified here. Check hugo mod npm pack --help and the matching release documentation before upgrading a build script.
6. Install only after the review
Once the manifest is correct, use your project's normal package-manager command, usually npm install. This changes the lockfile and creates or updates node_modules, so review the resulting diff and run it in the project's usual build environment. Do not use elevated privileges: a root-owned node_modules tree creates a later permissions problem.
If the generated manifest is wrong, restore the previous file before running npm:
$ if test -f package.json.before-hugo-pack; then mv -- package.json.before-hugo-pack package.json; fi
$ test -f package.json && node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8"))'
If the pack command created a new manifest and there was no old one, remove only that generated file after checking its path with pwd and readlink -f package.json. Do not use a broad recursive removal command for cleanup.
Common failure points
- A missing or empty output usually means you are not in the intended Hugo module, or there are no usable package declarations. Check
pwd,go.mod, the Hugo config and the module imports. - A dependency version you did not expect can come from a nearer module declaration. Inspect the generated
commentsobject and the module graph before editing the output by hand. - A failed npm install is separate from a successful Hugo pack. Keep the generated manifest, capture the npm error, and correct the source declaration rather than silently pinning a different package.
- Do not treat the experimental command as a stable interchange format. Commit source declarations and review generated files so an upgrade can be reversed.
Done means
- You confirmed the installed Hugo version and ran the command from the intended project root.
- Existing
package.jsoncontent has a recoverable backup or is tracked in version control. - Project dependencies are declared in
package.hugo.json, not hidden in an unreviewed generated file. - The generated JSON parses and contains the expected dependency ranges.
- You reviewed the diff before running npm, and you know how to restore the previous manifest.