Introduction
General settings and categories
Each top-level key in the project configuration is either a general setting or a configuration category.
A general setting is a single value, such as baseURL or title. A configuration category groups related, nested settings, such as markup, menus, or params.
baseURL: https://example.org/
params:
subtitle: The Best Widgets on Earth
title: My New Hugo Site
baseURL = 'https://example.org/'
title = 'My New Hugo Site'
[params]
subtitle = 'The Best Widgets on Earth'
{
"baseURL": "https://example.org/",
"params": {
"subtitle": "The Best Widgets on Earth"
},
"title": "My New Hugo Site"
}
In this example, baseURL and title are general settings, and params is a configuration category.
Sensible defaults
Hugo offers many configuration settings, but its defaults are often sufficient. A new project requires only these settings:
baseURL: https://example.org/
locale: en-us
title: My New Hugo Site
baseURL = 'https://example.org/'
locale = 'en-us'
title = 'My New Hugo Site'
{
"baseURL": "https://example.org/",
"locale": "en-us",
"title": "My New Hugo Site"
}
Only define settings that deviate from the defaults. A smaller configuration file is easier to read, understand, and debug. Keep your configuration concise.
The best configuration file is a short configuration file.
Configuration file
Create a project configuration file in the root of your project directory, naming it hugo.toml, hugo.yaml, or hugo.json, with that order of precedence.
my-project/
└── hugo.tomlA simple example:
baseURL: https://example.org/
locale: en-us
params:
contact:
email: info@example.org
phone: +1 202-555-1212
subtitle: The Best Widgets on Earth
title: ABC Widgets, Inc.
baseURL = 'https://example.org/'
locale = 'en-us'
title = 'ABC Widgets, Inc.'
[params]
subtitle = 'The Best Widgets on Earth'
[params.contact]
email = 'info@example.org'
phone = '+1 202-555-1212'
{
"baseURL": "https://example.org/",
"locale": "en-us",
"params": {
"contact": {
"email": "info@example.org",
"phone": "+1 202-555-1212"
},
"subtitle": "The Best Widgets on Earth"
},
"title": "ABC Widgets, Inc."
}
To use a different configuration file when building your project, use the --config flag:
hugo build --config other.tomlCombine two or more configuration files, with left-to-right precedence:
hugo build --config a.toml,b.yaml,c.jsonHugo loads the files in the order listed, and each file recursively overwrites matching keys from the files before it. In other words, for a conflicting key, the value from the last-listed file wins.
Configuration directory
Instead of a single project configuration file, split your configuration by environment, configuration category, and language. Hugo loads the _default directory first, then the directory for the current environment, so for a conflicting key, the environment-specific value wins. For example:
my-project/
└── config/
├── _default/
│ ├── hugo.toml
│ ├── menus.en.toml
│ ├── menus.de.toml
│ └── params.toml
└── production/
└── params.tomlThe configuration categories are HTTPCache, build, caches, contentTypes, deployment, frontmatter, imaging, languages, markup, mediaTypes, menus, minify, module, outputFormats, outputs, page, pagination, params, privacy, related, roles, security, segments, server, services, sitemap, taxonomies, uglyURLs, and versions.
Omit or include a category
New in v0.162.0When splitting the configuration by configuration category, you may omit or include the category name in the component file. For example, these are equivalent:
params:
foo: bar
[params]
foo = 'bar'
{
"params": {
"foo": "bar"
}
}
foo: bar
foo = 'bar'
{
"foo": "bar"
}
This also applies to keys whose values are maps of slices, such as menus. For example, these are equivalent:
main:
- name: Home
pageRef: /
weight: 10
[[main]]
name = 'Home'
pageRef = '/'
weight = 10
{
"main": [
{
"name": "Home",
"pageRef": "/",
"weight": 10
}
]
}
menus:
main:
- name: Home
pageRef: /
weight: 10
[menus]
[[menus.main]]
name = 'Home'
pageRef = '/'
weight = 10
{
"menus": {
"main": [
{
"name": "Home",
"pageRef": "/",
"weight": 10
}
]
}
}
For pure slice-typed keys such as cascade and permalinks, including the category name is required. For example:
cascade:
- params:
color: red
target:
path: /articles/**
[[cascade]]
[cascade.params]
color = 'red'
[cascade.target]
path = '/articles/**'
{
"cascade": [
{
"params": {
"color": "red"
},
"target": {
"path": "/articles/**"
}
}
]
}
Hugo unwraps the category only when it is the sole key in the file and matches the file’s basename.
Recursive parsing
Hugo parses the config directory recursively, allowing you to organize the files into subdirectories. For example:
my-project/
└── config/
└── _default/
├── navigation/
│ ├── menus.de.toml
│ └── menus.en.toml
└── hugo.tomlExample
my-project/
└── config/
├── _default/
│ ├── hugo.toml
│ ├── menus.en.toml
│ ├── menus.de.toml
│ └── params.toml
├── production/
│ ├── hugo.toml
│ └── params.toml
└── staging/
├── hugo.toml
└── params.tomlConsidering the structure above, when running hugo build --environment staging, Hugo uses every setting from config/_default, then overwrites matching keys with settings from staging.
Let’s take an example to understand this better. Let’s say you are using Google Analytics for your website. This requires you to specify a Google tag ID in your project configuration:
services:
googleAnalytics:
ID: G-XXXXXXXXX
[services]
[services.googleAnalytics]
ID = 'G-XXXXXXXXX'
{
"services": {
"googleAnalytics": {
"ID": "G-XXXXXXXXX"
}
}
}
Now consider the following scenario:
- You don’t want to load the analytics code when running
hugo server. - You want to use different Google tag IDs for your production and staging environments. For example:
G-PPPPPPPPPfor productionG-SSSSSSSSSfor staging
To satisfy these requirements, configure your site as follows:
config/_default/hugo.toml- Exclude the
services.googleAnalyticssection. This prevents Hugo from loading the analytics code when you runhugo server. - By default, Hugo sets its
environmenttodevelopmentwhen runninghugo server. In the absence of aconfig/developmentdirectory, Hugo uses theconfig/_defaultdirectory.
- Exclude the
config/production/hugo.tomlInclude this section only:
services: googleAnalytics: ID: G-PPPPPPPPP[services] [services.googleAnalytics] ID = 'G-PPPPPPPPP'{ "services": { "googleAnalytics": { "ID": "G-PPPPPPPPP" } } }You do not need to include other parameters in this file. Include only those parameters that are specific to your production environment. Hugo overwrites the default configuration with these parameters.
By default, Hugo sets its
environmenttoproductionwhen runninghugo build. The analytics code uses theG-PPPPPPPPPtag ID.
config/staging/hugo.tomlInclude this section only:
services: googleAnalytics: ID: G-SSSSSSSSS[services] [services.googleAnalytics] ID = 'G-SSSSSSSSS'{ "services": { "googleAnalytics": { "ID": "G-SSSSSSSSS" } } }You do not need to include other parameters in this file. Include only those parameters that are specific to your staging environment. Hugo overwrites the default configuration with these parameters.
To build your staging site, run
hugo build --environment staging. The analytics code uses theG-SSSSSSSSStag ID.
Merge configuration settings
Hugo merges configuration settings from themes and modules, prioritizing the project’s own settings. This is distinct from combining configuration files with the --config flag, or splitting configuration across a configuration directory, both of which always overwrite keys of the same name. Given this simplified project structure with two themes:
project/
├── themes/
│ ├── theme-a/
│ │ └── hugo.toml
│ └── theme-b/
│ └── hugo.toml
└── hugo.tomland this project-level configuration:
baseURL: https://example.org/
locale: en-us
theme:
- theme-a
- theme-b
title: My New Hugo Site
baseURL = 'https://example.org/'
locale = 'en-us'
theme = ['theme-a', 'theme-b']
title = 'My New Hugo Site'
{
"baseURL": "https://example.org/",
"locale": "en-us",
"theme": [
"theme-a",
"theme-b"
],
"title": "My New Hugo Site"
}
Hugo merges settings in this order:
- Project configuration (
hugo.tomlin the project root) theme-aconfigurationtheme-bconfiguration
Merge strategy
The _merge setting within each configuration category controls which settings are merged and how they are merged.
You can set _merge at any level of nesting within a category, not only at its top level. When merging a nested table, Hugo uses the _merge value set on that table if present, or inherits the value from its nearest ancestor. For example, to change the merge strategy for a single Goldmark extension without affecting the rest of the markup category:
markup:
goldmark:
extensions:
typographer:
_merge: deep
[markup]
[markup.goldmark]
[markup.goldmark.extensions]
[markup.goldmark.extensions.typographer]
_merge = 'deep'
{
"markup": {
"goldmark": {
"extensions": {
"typographer": {
"_merge": "deep"
}
}
}
}
}
The value for _merge can be one of:
none- Do not merge.
shallow- Add values for new keys only.
deep- Add values for new keys, and merge values for existing keys.
You don’t need to be as verbose as the default setup below. A _merge value set higher up is inherited if not set lower down.
build:
_merge: none
caches:
_merge: none
cascade:
_merge: none
contenttypes:
_merge: none
deployment:
_merge: none
frontmatter:
_merge: none
httpcache:
_merge: none
imaging:
_merge: none
languages:
_merge: none
en:
_merge: none
menus:
_merge: shallow
params:
_merge: deep
markup:
_merge: none
mediatypes:
_merge: shallow
menus:
_merge: shallow
minify:
_merge: none
module:
_merge: none
outputformats:
_merge: shallow
outputs:
_merge: none
page:
_merge: none
pagination:
_merge: none
params:
_merge: deep
permalinks:
_merge: none
privacy:
_merge: none
related:
_merge: none
roles:
_merge: none
security:
_merge: none
segments:
_merge: none
server:
_merge: none
services:
_merge: none
sitemap:
_merge: none
taxonomies:
_merge: none
versions:
_merge: none
[build]
_merge = 'none'
[caches]
_merge = 'none'
[cascade]
_merge = 'none'
[contenttypes]
_merge = 'none'
[deployment]
_merge = 'none'
[frontmatter]
_merge = 'none'
[httpcache]
_merge = 'none'
[imaging]
_merge = 'none'
[languages]
_merge = 'none'
[languages.en]
_merge = 'none'
[languages.en.menus]
_merge = 'shallow'
[languages.en.params]
_merge = 'deep'
[markup]
_merge = 'none'
[mediatypes]
_merge = 'shallow'
[menus]
_merge = 'shallow'
[minify]
_merge = 'none'
[module]
_merge = 'none'
[outputformats]
_merge = 'shallow'
[outputs]
_merge = 'none'
[page]
_merge = 'none'
[pagination]
_merge = 'none'
[params]
_merge = 'deep'
[permalinks]
_merge = 'none'
[privacy]
_merge = 'none'
[related]
_merge = 'none'
[roles]
_merge = 'none'
[security]
_merge = 'none'
[segments]
_merge = 'none'
[server]
_merge = 'none'
[services]
_merge = 'none'
[sitemap]
_merge = 'none'
[taxonomies]
_merge = 'none'
[versions]
_merge = 'none'
{
"build": {
"_merge": "none"
},
"caches": {
"_merge": "none"
},
"cascade": {
"_merge": "none"
},
"contenttypes": {
"_merge": "none"
},
"deployment": {
"_merge": "none"
},
"frontmatter": {
"_merge": "none"
},
"httpcache": {
"_merge": "none"
},
"imaging": {
"_merge": "none"
},
"languages": {
"_merge": "none",
"en": {
"_merge": "none",
"menus": {
"_merge": "shallow"
},
"params": {
"_merge": "deep"
}
}
},
"markup": {
"_merge": "none"
},
"mediatypes": {
"_merge": "shallow"
},
"menus": {
"_merge": "shallow"
},
"minify": {
"_merge": "none"
},
"module": {
"_merge": "none"
},
"outputformats": {
"_merge": "shallow"
},
"outputs": {
"_merge": "none"
},
"page": {
"_merge": "none"
},
"pagination": {
"_merge": "none"
},
"params": {
"_merge": "deep"
},
"permalinks": {
"_merge": "none"
},
"privacy": {
"_merge": "none"
},
"related": {
"_merge": "none"
},
"roles": {
"_merge": "none"
},
"security": {
"_merge": "none"
},
"segments": {
"_merge": "none"
},
"server": {
"_merge": "none"
},
"services": {
"_merge": "none"
},
"sitemap": {
"_merge": "none"
},
"taxonomies": {
"_merge": "none"
},
"versions": {
"_merge": "none"
}
}
Root-level merge strategy
You can also set _merge at the root of your project configuration, outside of any configuration category, to change this behavior for the whole project:
_merge: none
baseURL: https://example.org/
locale: en-us
theme:
- theme-a
- theme-b
title: My New Hugo Site
_merge = 'none'
baseURL = 'https://example.org/'
locale = 'en-us'
theme = ['theme-a', 'theme-b']
title = 'My New Hugo Site'
{
"_merge": "none",
"baseURL": "https://example.org/",
"locale": "en-us",
"theme": [
"theme-a",
"theme-b"
],
"title": "My New Hugo Site"
}
A root-level _merge set to none disables merging of theme and module configuration entirely, regardless of any _merge value set on individual configuration categories. A root-level _merge set to shallow or deep instead changes the default merge strategy for configuration categories that do not specify their own _merge value.
Hugo can merge map configuration values from modules and themes into the project configuration, but cannot merge slice values. This applies to slice-typed configuration categories such as menus, as well as to map keys whose values are slices, such as the per-kind format lists in outputs.
Security implications
Most configuration categories default to a none merge strategy specifically to protect your project from third-party themes and modules.
Setting _merge to shallow or deep removes that protection, whether applied directly to a security-sensitive key such as markup or security, or set at the root of your configuration to change the default for every key. Only use a permissive _merge value for these keys if you trust every theme and module in your project.
Environment variables
You can also configure settings using operating system environment variables:
export HUGO_BASEURL=https://example.org/
export HUGO_ENABLEGITINFO=true
hugoThe above configures the baseURL and enableGitInfo settings and then builds your site.
An environment variable takes precedence over the values set in the configuration file. This means that if you set a configuration value with both an environment variable and in the configuration file, Hugo uses the value from the environment variable.
Environment variables simplify configuration for CI/CD platforms by allowing you to set values directly within their respective configuration and workflow files.
Environment variable names must be prefixed with HUGO_.
To set custom site parameters, prefix the name with HUGO_PARAMS_.
For snake_case variable names, the standard HUGO_ prefix won’t work. Hugo infers the delimiter from the first character following HUGO. This allows for variations like HUGOxPARAMSxAPI_KEY=abcdefgh using any permitted delimiter.
In addition to configuring standard settings, environment variables may be used to override default values for certain internal settings:
DART_SASS_BINARY- (
string) The absolute path to the Dart Sass executable. By default, Hugo searches for the executable in each of the paths in thePATHenvironment variable. HUGO_ENVIRONMENT- (
string) The build environment. Default isproductionwhen runninghugo buildanddevelopmentwhen runninghugo server. HUGO_FILE_LOG_FORMAT- (
string) A format string for the file path, line number, and column number displayed when reporting errors, or when calling thePositionmethod from a shortcode or Markdown render hook. Valid tokens are:file,:line, and:col. Default is:file::line::col. HUGO_MEMORYLIMIT- (
int) The maximum amount of system memory, in gigabytes, that Hugo can use while rendering your site. Default is 25% of total system memory. Note thatHUGO_MEMORYLIMITis a “best effort” setting. Don’t expect Hugo to build a million pages with only 1 GB of memory. You can get more information about how this behaves during the build by runninghugo build --logLevel infoand look for thedynacachelabel. HUGO_NUMWORKERMULTIPLIER- (
int) The number of workers used in parallel processing. Default is the number of logical CPUs.
Current configuration
Display the complete project configuration with:
hugo configDisplay a specific configuration setting with:
hugo config | grep [key]Display the configured file mounts with:
hugo config mounts