org.nvim

Hugo export (ox-hugo)

Stability: experimental (org-extensions-stability)

hugo exports Org to Markdown for the Hugo static site generator, like the Emacs package ox-hugo: a post gets a TOML or YAML front matter and goes to <base_dir>/content/<section>/<name>.md.

require("org").setup({
  extensions = { hugo = { base_dir = "~/blog" } },  -- or #+hugo_base_dir
})

It adds H to the export dispatcher (<prefix>e, Emacs C-c C-e H):

H Hthe post subtree at the cursor, or the file when it has no post subtrees (org-hugo-export-wim-to-md); action hugo_export_wim
H hthe file as one post (org-hugo-export-to-md; the subtree at the cursor with the dispatcher's s); action hugo_export_file
H Olike H H, then open the file
H olike H h, then open the file
H Aevery post subtree (or the file); action hugo_export_all
H tto a temporary Markdown buffer; action hugo_export_buffer

:Org hugo [wim|all|file|buffer] [open] [visible] does the same.

Two ways to write posts

One post per file: the file needs #+title; #+hugo_base_dir (relative to the file), #+hugo_section (default section, "posts"), #+date, #+description, #+filetags and the other keywords below set its front matter. The file name (or #+export_file_name) names the post.

#+title: My first post
#+hugo_base_dir: ..
#+hugo_section: blog
#+date: 2026-10-05
#+filetags: emacs @tech

One post per subtree: every headline with an EXPORT_FILE_NAME property is a post, its title the headline. EXPORT_HUGO_* properties set its front matter, and a post inherits from its parents (and #+PROPERTY:) the ones ox-hugo inherits: the section, bundle, base dir, front matter format, tags, categories, menu, custom front matter, draft, weight, date, author and the other EXPORT_HUGO_* settings (not the title or file name). The keywords of the file are the defaults.

#+hugo_base_dir: ~/blog
* Blog                                                         :emacs:
:PROPERTIES:
:EXPORT_HUGO_SECTION: blog
:END:
** DONE First post                                            :@tech:
CLOSED: [2026-10-05 Mon 10:30]
:PROPERTIES:
:EXPORT_FILE_NAME: first-post
:END:
Hello!

EXPORT_HUGO_SECTION_FRAG values of the post and its parents are added to the section as directories, and EXPORT_HUGO_BUNDLE (joined the same way) makes the post part of a page bundle: content/<section>/<bundle>/, with EXPORT_FILE_NAME: index (leaf bundle) or _index (branch bundle). Commented posts and posts with an exclude tag (noexport) are skipped.

Front matter

title#+title or the headline, markup removed, ---, -- and ... as dashes and an ellipsis
datethe post's CLOSED time, else #+date / EXPORT_DATE (an Org date, or an RFC 3339 date kept as written)
publishDateSCHEDULED, else HUGO_PUBLISHDATE
expiryDateHUGO_EXPIRYDATE
lastmodHUGO_LASTMOD; with auto_set_lastmod (or HUGO_AUTO_SET_LASTMOD) the time of the export, unless the date is less than suppress_lastmod_period seconds old
drafttrue for a post in a TODO state (TODO, DRAFT, ...), false when done; else HUGO_DRAFT (t, true, yes / nil, false, no)
tagsHUGO_TAGS, else the Org tags (inherited ones and #+filetags too) without @: a_b -> "a-b", a___b -> "a_b" (prefer_hyphen_in_tags), a__b -> "a b" (allow_spaces_in_tags)
categoriesHUGO_CATEGORIES, else the Org tags starting with @
descriptionDESCRIPTION, or a #+begin_description block
author#+author / EXPORT_AUTHOR (comma separated) as a list
aliasesHUGO_ALIASES; one without "/" goes under the section
weightHUGO_WEIGHT: a number, auto (from the post's place among its sibling posts), or :tags 10 :categories auto for taxonomy weights (:page is the page weight)
menuHUGO_MENU: :menu main :weight 10 :parent docs ... (identifier and weight are set when missing); HUGO_MENU_OVERRIDE changes some of them
resourcesHUGO_RESOURCES: :src "*.png" :title "..." :key value; other keys go under params, and the files are copied into the bundle
customHUGO_CUSTOM_FRONT_MATTER: :key value pairs, lists '(a b) and nested maps '((k . v)); PROP+: lines add to it
othersHUGO_SLUG, HUGO_URL, HUGO_TYPE, HUGO_LAYOUT, HUGO_LINKTITLE, HUGO_MARKUP, HUGO_OUTPUTS, HUGO_SERIES, HUGO_IMAGES, HUGO_VIDEOS, KEYWORDS, HUGO_AUDIO, HUGO_HEADLESS, HUGO_ISCJKLANGUAGE, HUGO_LOCALE with HUGO_WITH_LOCALE, creator with creator:t

HUGO_FRONT_MATTER_KEY_REPLACE: old>new renames a key (old>nil drops it). A toml or yaml source block with :front_matter_extra t is added to the front matter as it is. Each setting is a #+hugo_... keyword in the per-file flow and an EXPORT_HUGO_... property in a post subtree. Strings are quoted and escaped for TOML (like tomelr, a string with a newline or a double quote becomes a multi-line string) or YAML.

The body

Hugo (Goldmark) Markdown, like ox-hugo's: headings one level below the title (HUGO_LEVEL_OFFSET) with {#anchor} ids (the CUSTOM_ID or a slug of the title, like org-hugo-slug: letters and digits of any script kept, other characters, punctuation such as ’ — « » too, made hyphens), fenced code blocks with { linenos=true, hl_lines=[...] } for -n and :hl_lines (the highlight shortcode with an empty HUGO_CODE_FENCE), [^fn:1] footnotes, pipe tables, _italic_, ~~strike~~, #+begin_details / #+begin_summary as <details>, other special blocks as HTML elements or <div>s, or as paired shortcodes listed in HUGO_PAIRED_SHORTCODES (%name for Markdown contents, arguments from #+attr_shortcode), #+hugo: more as the summary splitter, #+hugo: lines and @@hugo:...@@ written as they are, and equations escaped for MathJax.

A standalone image becomes a {{< figure >}} shortcode with its caption and #+attr_html (:alt :title :class :width :link ...); an image in a sentence stays Markdown. A linked local file (with one of copy_extensions) is copied to the site: kept where it is under static/, into the bundle of a bundle post, else to static/<static_subdir>/. Links to headings of other posts of the file, and to other Org files ([[file:other.org::#id]]), become {{< relref >}}; info: links go to the manuals on the web.

Options

base_dirthe Hugo site (nil; #+hugo_base_dir)
content_folder("content"; #+hugo_base_content_folder)
section("posts"; #+hugo_section)
front_matter_format"toml" or "yaml" ("toml")
footertext at the end of every post ("")
preserve_fillingkeep the line breaks of paragraphs (true)
delete_trailing_ws(true)
use_code_for_kbd~code~ as <kbd> (false)
allow_spaces_in_tags(true)
prefer_hyphen_in_tags(true)
auto_set_lastmod(false)
suppress_lastmod_periodseconds (0)
with_tocfalse, true or a depth (false; toc:)
with_section_numbersfalse, true, a level or "onlytoc" (false)
static_subdir("ox-hugo")
copy_extensionsextensions of files copied to the site
date_format("%Y-%m-%dT%T%z")
paired_shortcodes("")
link_desc_insert_type"Figure 1" instead of "1" in links (false)
container_elementHTML element around top-level sections ("")
special_block_type_propertiesraw, trim-pre, trim-post per block type
headline_anchor{#anchor} after headings (true)
auto_exportexport the post at the cursor (or the file) when a buffer with a Hugo base dir is written, like org-hugo-auto-export-mode (false)

They are ox-hugo's org-hugo-* variables without the prefix. :checkhealth org checks base_dir and whether hugo is installed.

Not ported

  • Blackfriday mode (org-hugo-goldmark nil), deprecated in ox-hugo.
  • Dates, lastmod and notes from LOGBOOK drawers (the drawer is left out of the body).
  • Coderef anchors in code blocks ((ref:name) links export as text), #+toc: headlines :target, ordered lists with custom counters written as HTML, inlined SVGs (:inlined t) and tag groups.
  • Pandoc citations (HUGO_PANDOC_CITATIONS); Org citations work as in the other back-ends.
  • Asynchronous export: the dispatcher's a toggle is ignored.

On ox-hugo's own test site, 347 of its 406 per-subtree posts come out byte for byte the same (most of the others run code blocks or use the features above).