OpenAPI Reference Documentation in Hugo: A Better Alternative to Swagger

I’ve recently been working on an overhaul of API documentation, and as part of the overhaul I am building the strict reference sections entirely from an OpenAPI definition. While OpenAPI-based reference documentation is commonly built with Swagger UI, I quickly realized that using Swagger UI was not the best choice for me. The documentation site I’m working on is built with the Hugo Framework, a static site generator.

Why Not Swagger?

Swagger UI is a standalone webpage. It’s a great product, and I’m a huge fan. However, it’s not designed to be modular or extremely configurable when it comes to the output.

The Better Choice

I think the better choice for those using Hugo or other static site generators is to use the build process of these generators to parse an OpenAPI specification file, and generate their own .html from this process. I provide a working code example at the end of this post.

The Result

The end result of using this shortcode is an .html file that contains exactly the properties that you want to display in your reference documentation. You can customize it for your needs, which avoids extra bloat and allows lightning-fast performance for your documentation website.

The result contains everything you need, and nothing that you don’t


I checked the asset sizes and page load performance numbers for my shortcode vs. Swagger UI for Twitter’s and Github’s OpenAPI definitions.

Asset Size Reduction

There was a 2,250x reduction in total size of served assets when I processed Twitter’s OpenAPI definition with my shortcode vs Swagger UI.

Page Performance Improvements

Improvements of 45% and 67% were observed in Lighthouse Performance scores for the shortcode-generated .html when compared to Swagger UI for Twitter and Github's OpenAPI definitions.


If you want to dive deeper into how this is possible with Hugo, check out my in-depth blog post at my personal website.


If you find this useful, please give it a clap! Thanks for reading.


Here’s the shortcode, copied and pasted for your convenience. It was built using Hugo v0.80.0, but should work in prior versions with perhaps slight modifications:

<!-- openapi-ref-docs.htmlhugo OpenAPI reference documentation generatorParams:
url: the public URL of the OpenAPI .json specification
Generates reference documentation based on the provided OpenAPI specification. Use with: {{/* {{< openapi-ref-docs url="your-.json-file-url" >}} */}} Example: {{/* {{< openapi-ref-docs url="" >}} */}}If your OpenAPI spec is not hosted at an accessible url endpoint, you can
store a copy of your OpenAPI specification in the root-level site /data folder.
Then delete the first and last lines of the code block below, and replace the
$openapi variable declaration at the beginning with: {{/*
{{ $openapi := .Site.Data.fileName }}*/}}where fileName is the file's name with `.json` removed from the end

{{ with $.Params.url }}
{{ $openapi := getJSON $.Params.url }}
<div class="$openapi-spec-content">
<h2>{{ $ }} version {{ $ }}</h2>
<div class="description">
<p>{{ $ }}</p>
<div style="display: flex; flex-direction: column; align-items: stretch; width: 80%;">
{{ range $path, $pathMethods := $openapi.paths }}
{{ range $pathMethod, $pathDetails := $pathMethods }}
<div style="display: flex; flex-direction: column; width: 100%; border: 1px solid black;">
<div style="display: flex; flex-direction: row; width: 100%; max-height: 400px; overflow: auto;">
<div style="width: 50%; padding-right: 15px;">
<h4><span style="text-transform: uppercase;">{{ $pathMethod }}</span>&nbsp;&nbsp;<span style="color: darkslategray;">{{ $path }}</span></h4>
<h5>{{ $pathDetails.summary }}</h5>
<p><a href="{{ $pathDetails.externalDocs.url }}">{{ $pathDetails.externalDocs.description}}</a></p>
<p>Description: {{ $pathDetails.description | markdownify }}</p>
<div style="width: 50%; display: flex; flex-direction: column;">
{{ range $responseCode, $responseDetails := $pathDetails.responses }}
{{ if isset $responseDetails "content" }}
{{ range $exampleName, $example := (index $responseDetails.content "application/json").examples }}
{{ if isset $example "$ref" }}
{{ $refPath := index $example "$ref" }}
{{ range $index, $component := (split $refPath "/") }}
{{ if (index $openapi.components.examples $component) }}
<div style="font-weight: bold; max-height: calc(80%); overflow: auto;">
<span>Example for Response Code: {{ $responseCode }}</span>
<pre><code>{{(index $openapi.components.examples $component).value | jsonify (dict "indent" " ") }}</code></pre>
{{ end }}
{{ end }}
{{ else }}
<code style="flex-wrap: nowrap; overflow-x: scroll;">Example Response: {{ $example }}</code>
{{ end }}
{{ end }}
{{ else }}
<span>No Example for Response Code: {{ $responseCode }}</span>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store