At a glance
- Output
- Static
- Server code
- None
- Where it goes
layouts/partials/contact.html- Endpoint
- In site params
- In Markdown
- A shortcode
The integration
Three small files. The endpoint lives in your config, so the same partial works in every environment, and the shortcode lets writers drop the form into any Markdown page.
{{/* layouts/partials/contact.html */}}
<form action="{{ site.Params.formEndpoint }}" method="POST">
<label>Name <input name="name" autocomplete="name" required></label>
<label>Email <input type="email" name="email" autocomplete="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<input type="text" name="_mtf_honeypot" tabindex="-1" autocomplete="off"
aria-hidden="true" style="position:absolute;left:-9999px">
<button type="submit">Send</button>
</form>Step by step
- 1
Create the endpoint
Create a form in MakeTheForm to get your endpoint URL and inbox.
- 2
Store it in your site params
Add formEndpoint under [params] in hugo.toml. The endpoint is an address, not a secret, so it is fine in a public repository.
- 3
Add the partial and shortcode
Create layouts/partials/contact.html with the form, and layouts/shortcodes/contact.html that renders it. Keep the hidden _mtf_honeypot input.
- 4
Build, deploy and test
Deploy as usual to Netlify, Cloudflare Pages, GitHub Pages or any host, verify your recipient address once, and submit the live form.
Common pitfalls
Hard-coding the endpoint in every template
Pasting the URL into several layouts means several places to change it later, and a staging build that posts to the production form.
Fix: Read it from site.Params.formEndpoint, and override it per environment in config.
Putting raw HTML in Markdown
Hugo’s default Markdown renderer drops raw HTML unless unsafe rendering is turned on, so a form pasted into content silently disappears.
Fix: Use the shortcode. It renders the partial and needs no unsafe setting.
Troubleshooting
| Symptom | What’s happening | Fix |
|---|---|---|
| form missing | The form was pasted into a Markdown file and Hugo removed it. | Use the contact shortcode instead of raw HTML in content. |
| empty action | The partial rendered action="" because the param is missing. | Check formEndpoint is under [params] in hugo.toml, spelled the same as in the partial. |
| 403 | Allowed domains is on and the site’s address is not listed. | Add your production and preview domains, or leave the form in public mode. |
Frequently asked questions
How do I add a contact form to a Hugo site?
Put a normal HTML form in a partial, set its action to a hosted form endpoint, and render it with a shortcode or from a layout. Hugo stays static, and the endpoint handles delivery and spam.
Can Hugo send email?
No. Hugo runs only when you build the site, so it cannot react to a submission. The form has to post to something that runs at request time, such as a form endpoint.
Does this work with Hugo themes?
Yes. Override the theme’s contact partial with your own in layouts/partials, or add the shortcode to the contact page. Theme files are never edited.
Other platforms
Keep going
Last reviewed by the MakeTheForm team.