Hugo contact form
Hugo builds the page and the browser posts it, so there is no module to install and nothing for Hugo to do at submit time. The form is a partial, the two values it needs are site params, and a shortcode makes it usable from a content file.
Create the form in the console first. It gives you a form ID for the URL and the name of a honeypot field for your markup. Both are rendered into the page, and neither is a secret.
The partial
{{ $formId := .Site.Params.ezform.formid }}
{{ $honeypot := .Site.Params.ezform.honeypot }}
<form action="https://api.ezform.io/f/{{ $formId }}" method="POST">
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
<div style="position:absolute;left:-9999px" aria-hidden="true">
<label for="{{ $honeypot }}">Company website</label>
<input id="{{ $honeypot }}" name="{{ $honeypot }}" type="text" tabindex="-1" autocomplete="off">
</div>
<button type="submit">Send</button>
</form>
[params.ezform]
formid = "YOUR_FORM_ID"
honeypot = "YOUR_HONEYPOT_FIELD"
Keep the param keys lowercase. Hugo lowercases the keys under params, so a formID written in
camelCase is read back as .Site.Params.ezform.formid, and the mismatch shows up as an empty
string in the URL rather than as an error.
Call it from a template with {{ partial "ezform-form.html" . }}. The trailing dot matters: without the context, .Site inside the partial is nothing and both values come out empty.
Using it from a content file
Pasting the form’s HTML into a Markdown page looks like it works and does not. Goldmark’s unsafe setting is off by default, so raw HTML in content is dropped from the output, and what you get is a page with no form on it and no error anywhere.
Wrap the partial in a shortcode instead, which is rendered whatever that setting says:
{{ partial "ezform-form.html" . }}
---
title: Contact
---
Tell us what you need and we will get back to you.
{{< contact-form >}}
Allow your origins
Scheme and host, no path, one entry each:
http://localhost:1313forhugo server, if you test against the real endpoint rather than filling the console in by hand.- Your production host, which should match
baseURLinhugo.toml. The apex andwww.are two different origins. - Any preview host your deploy setup gives you.
A submission from an origin that is not listed is discarded and never stored, which is what keeps someone who read your form ID out of your inbox. It is also the first thing to check when a form appears to do nothing: see Responses and failure.
What happens when someone submits
The browser posts the form and navigates. Configure a redirect path on the form in the console and ezform answers 303 to that page, so a refresh cannot repost the submission. Without one, ezform renders its own short confirmation page.
A Hugo site ships no JavaScript for this, and that is the point: the form keeps working with JavaScript turned off, which is not true of a form backend that requires a script tag.
Where to go next
- Quickstart for the two values you need from the console
- Spam and origins for why the honeypot is hidden off-screen rather than with
display: none - Submitting a form for the field limits and the encodings accepted