Audit Hugo Content States with hugo list
You need to know what is a draft, a future post or something quietly expired before you publish, and hugo list answers all three as plain CSV. This is a read-only audit: all content, drafts, future-dated pages and expired pages, each in a form you can pipe anywhere. The examples use Hugo 0.123.7, installed here from the Ubuntu hugo package. Allow about ten minutes if the site is already on disk. You need a shell and read access to the project; nothing here publishes, deletes or edits content.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the installed command
Run the version check from the project directory, or use an absolute project path with --source later:
$ hugo version
hugo v0.123.7+extended linux/amd64 BuildDate=2026-03-17T19:51:14Z VendorInfo=ubuntu:0.123.7-1ubuntu0.3+esm2
Your build date or vendor suffix can differ. What matters here is the Hugo version, because the available list subcommands are version-specific.
Checkpoint
Ask Hugo which list commands this installation actually provides:
$ hugo list --help
Available Commands:
all List all posts
drafts List all drafts
expired List all posts already expired
future List all posts dated in the future
This installed release has no published subcommand. Newer upstream documentation lists one, so do not copy that command into a script without first checking the Hugo binary your deployment actually uses.
2. List every content state
Run all first when you need a full inventory. It includes drafts, future content and expired pages, not just what would normally go live:
$ hugo list all
path,slug,title,date,expiryDate,publishDate,draft,permalink
content/posts/roadmap.md,,Roadmap,2026-09-01T10:00:00Z,0001-01-01T00:00:00Z,2026-09-01T10:00:00Z,false,https://example.org/posts/roadmap/
content/posts/preview.md,,Preview,2026-09-02T10:00:00Z,0001-01-01T00:00:00Z,2026-09-02T10:00:00Z,true,https://example.org/posts/preview/
The exact rows depend on your site. The first line is a CSV header. Fields cover the source path, title, date values, the draft flag and the resolved permalink. An empty slug is not automatically an error: Hugo can use a content path or title when no slug is explicitly set.
Hugo prints this to standard output and does not write a generated site. A successful command normally returns status 0. Capture the status immediately if a script needs to tell an empty report apart from a failed command:
hugo list all
status=$?
printf 'hugo list all returned status %s\n' "$status"
test "$status" -eq 0
3. Find drafts before a release
Use drafts when you want only content whose front matter marks it as a draft:
$ hugo list drafts
path,slug,title,date,expiryDate,publishDate,draft,permalink
content/posts/preview.md,,Preview,2026-09-02T10:00:00Z,0001-01-01T00:00:00Z,2026-09-02T10:00:00Z,true,https://example.org/posts/preview/
Use the path from the report to inspect the front matter. Do not flip draft: true merely because a row turned up in an audit; a draft may be deliberate and might still need review, links or images before it goes anywhere.
If the command prints only the header, that is still a useful result: Hugo found no draft pages in the content tree it loaded. It is not proof that another checkout or mounted content directory has none.
4. Check scheduled and expired pages
Run future to find pages whose publish date is later than Hugo's current clock:
$ hugo list future
path,slug,title,date,expiryDate,publishDate,draft,permalink
content/posts/launch.md,,Launch,2030-09-02T10:00:00Z,0001-01-01T00:00:00Z,2030-09-02T10:00:00Z,false,https://example.org/posts/launch/
Run expired to find content whose expiry date has already passed:
$ hugo list expired
path,slug,title,date,expiryDate,publishDate,draft,permalink
content/posts/old-offer.md,,Old offer,2026-01-01T10:00:00Z,2026-02-01T00:00:00Z,2026-01-01T00:00:00Z,false,https://example.org/posts/old-offer/
Both classifications depend on the clock and the front matter dates. A page can show up in more than one audit if its metadata puts it in more than one state. Treat the rows as review queues, not automatic instructions to publish or remove files.
5. Point Hugo at the right site
When your shell is not in the project root, pass the source directory explicitly:
$ hugo --source /path/to/site list all
Replace /path/to/site with the directory holding the Hugo configuration and content. The local manual documents --source as the filesystem path everything else is read relative to. If the site uses a non-default configuration file, add --config /path/to/hugo.yaml; if the configuration lives in its own directory, use --configDir /path/to/config instead.
Verify the selected tree rather than trusting a plausible-looking report:
$ test -d /path/to/site/content && echo 'content directory found'
$ hugo --source /path/to/site list drafts
Skip sudo for a normal audit. If the command reports a permission failure, fix the project or mount permissions through your normal administration process, then rerun it; elevated privileges can let an audit read files the publishing account cannot, which hides a real deployment problem.
6. Keep the CSV safe in scripts
The output is comma-separated and can contain commas inside titles or other fields. Do not split every line with shell word splitting and assume each comma marks a safe boundary. For a one-off human review, redirect it to a new report name:
$ hugo list all > hugo-content-audit.csv
$ test -s hugo-content-audit.csv && head -n 3 hugo-content-audit.csv
Redirection replaces an existing file before Hugo even runs, so pick a new name or protect an existing report first. For machine processing, reach for a CSV-aware reader in whatever language or tool consumes the report, and keep the original Hugo output until the audit has been checked.
For a reproducible comparison, record the Hugo version and the project path alongside the report. The result reflects the content tree, front matter and clock seen by that one invocation; it is not a permanent database of publication history.
Done means
- Confirmed the version.
hugo versionidentifies the binary used for the audit. - Checked available subcommands.
hugo list --helpconfirms what this version actually provides. - Ran every audit that matters.
all,drafts,futureandexpiredwere run against the intended project. - Reviewed the CSV. The header and representative rows were checked before any follow-up edit.
- Changed nothing. No draft, date, expiry value, content file or generated site was touched.