Feature Request: Content hook for post-processing HTML from external renderers (e.g., Asciidoctor)

Hi Hugo Team,

I would like to propose a new feature: a content hook that allows for post-processing the final HTML of a content page after it has been rendered by its respective engine, especially external ones like Asciidoctor.

Background and Motivation

I am currently working on adding support for Asciidoc to a community module, hugo-admonitions. This module provides beautifully styled admonitions (callouts like Note, Warning, etc.).

  • For Markdown: Our current implementation works perfectly. We use a render-blockquote-alert.html render hook to intercept a specific Markdown syntax and convert it into a custom HTML structure (e.g., <div class="admonition note">...</div>). Our CSS is written to style this specific, clean structure.

  • The Asciidoc Challenge: When adding support for Asciidoc (.adoc files), we’ve hit a limitation. Hugo passes the content to the external asciidoctor binary, which generates its own default HTML for admonitions (a <table>-based structure). This process completely bypasses Hugo’s render hook system.

This leaves us with two architectural choices:

  1. Adapt the CSS to the HTML: Our current, working solution is to add a significant amount of specific CSS to our stylesheet to target Asciidoctor’s unique table-based output.

    • Downside: This couples our module tightly to the specific HTML output of Asciidoctor, which could change. It also leads to CSS bloat, as we have to maintain two sets of structural styles for the same visual component.
  2. Transform the HTML to the CSS: A much cleaner and more robust architectural solution would be to intercept the HTML generated by Asciidoctor and transform it to match the semantic HTML structure our module already uses for Markdown. This would allow us to reuse our existing CSS, making the module more maintainable and efficient.

Unfortunately, after investigation, it appears Hugo does not currently provide a hook to post-process the .Content after it has been rendered by an external tool.

Proposed Solution

I propose the creation of a new, final-stage content hook, perhaps named post-render-content.html, located at layouts/_default/_markup/post-render-content.html.

This hook would function as follows:

  • It would receive the fully rendered .Content variable as its context.
  • It would run after the content has been processed by its renderer (Goldmark, Asciidoctor, etc.).
  • It would allow theme and module authors to perform final transformations (e.g., via replaceRE) before the content is rendered into the final page template.

A hypothetical implementation would look like this:

{{/* layouts/_default/_markup/post-render-content.html */}}

{{ $processedContent := .Content |
    replaceRE `<div class="admonitionblock (\w+)">\s*<table>(.+?)</table>\s*</div>` `<div class="admonition $1">$2</div>` |
    replaceRE `(?s)<td class="icon"><div class="title">(.+?)</div></td>` `<div class="admonition-header">$1</div>` |
    replaceRE `(?s)<td class="content">(.+?)</td>` `<div class="admonition-content">$1</div>`
    /* Note: Regex is simplified for demonstration */
}}

{{ return $processedContent | safeHTML }}

Alternatives Considered

  1. Renderer-Specific CSS: This is the workaround we are currently using. It is functional but not ideal for the reasons mentioned above (fragile and not DRY).
  2. Custom Asciidoctor Templates: One could configure Asciidoctor itself to use custom templates to generate the desired HTML. This places a very high configuration burden on the end-user and defeats the purpose of creating a simple, “plug-and-play” Hugo module.

Adding a native post-processing hook would provide a powerful, clean, and “Hugo-native” way to normalize output from various content formats. It would greatly empower module authors and theme developers to create more consistent and robust components.

Thank you for your time and for considering this request.

2 Likes

Thanks a lot for this detailed analysis and the proposal. I am struggling quite hard with Asciidoc content in Hugo (which is a hard requirement for a project I am working on) as such render hooks are not working. Aside from the mentioned admonitions plugin I’d also like to use hugo-kroki which relies on the same mechanism and isn’t working with Asciidoc source files.

It would be great if source files in different formats than Markdown would receive some love and a better support by Hugo. Thanks for your consideration!

The Kroki shortcode should work fine.

I have failed in doing so. To give you a chance to reproduce, please find my test page here:

The permalink for of the current asciidoc page is here:

Your content contains a fenced code block, not a shortcode as I suggested.

This works fine:

git clone --single-branch -b hugo-forum-topic-55177 https://github.com/jmooring/hugo-testing hugo-forum-topic-55177
cd hugo-forum-topic-55177
hugo server

You can get the source code used in example above here.

Also note that github.com/hugomods/kroki already includes a kroki shortcode.

cc: @razon

@KKKZOZ

Handling content transformations like this is best done with an Asciidoctor converter template, which is similar to a Markdown render hook. I’ve spent a lot of time on this, even creating a working implementation for the exact scenario you described.

However, after several attempts, we found that using converter templates introduced an unacceptable security risk: arbitrary code execution. Developing post-rendering content hooks[1] as you describe is a complex task, and in my view the investment far exceeds the return.

Regarding your specific use case:

  1. I’m not particularly concerned about “CSS bloat” because modern compression algorithms like gzip, Brotli, and Zstandard (zstd) achieve very high compression ratios.

  2. I’m also not particularly concerned that the AsciiDoc specification/implementation for HTML generation will change.

  3. A significant challenge arises if a site wants to use your module: they’d need to use your specific content render hook. If their chosen theme also requires different transformations through its own content render hook, you’ll run into conflicts that could be very difficult for average Hugo users to resolve. We already face this problem when theme developers create link and image render hooks that aren’t compatible with multilingual, single-host sites.


  1. The implementation would need a different hook for each content format, and possibly a fallback for all content formats. A site may include multiple content formats (e.g., Markdown, AsciiDoc, and HTML). Also note that the hooks would have to fire before determination of .Content, .Summary, .ContentWithoutSummary, .WordCount, etc. ↩︎

1 Like

Hi @jmooring,

Thanks so much for the detailed and thoughtful reply. :folded_hands:

The points about security risks and potential hook conflicts are very clear and convincing. I hadn’t considered the issue of different modules or themes competing for the same hook, and now I completely understand why that makes this a difficult problem.

Thanks again for your time and for all the great work you do on Hugo!

2 Likes

Ah, apologies for confusing the terms. I didn’t know Hugo shortcodes work with asciidoc.

Indeed, the two ways of using shortcodes seem to work quite well!

Commit: use two types of shortcodes · mxmehl/hugo-asciidoc-test@c1aff39 · GitHub

Regarding the plugin this thread was actually about, let me answer in a separate post.

I don’t fully grasp the implications, sorry. Does that mean the hook your plugin requires is not possible to work with Asciidoc files due to the security considerations?

I have written about this elsewhere, so I will be short: It’s on the short list to allow plugins like this via WASM/Wasi plugins, which would essentially do whatever you want. But we have some other building blocks to get in place first.

2 Likes