Hugo Contact Form Without a Backend (2026)
Add a working contact form to any Hugo site — no server, no plugins. Drop in a copy-paste shortcode with honeypot spam protection and email alerts.
The ShipMyForm team
· 4 min read
To add a contact form to a Hugo site without a backend, point a normal HTML
<form> at a hosted form endpoint and let it handle the submission. Hugo compiles
your site to static files — there's no server runtime to receive the POST yourself —
so a form backend is the piece that receives it, filters spam, and emails you. Here's
the whole form:
<form action="https://shipmyform.com/f/YOUR_FORM_ID" method="POST">
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<button type="submit">Send message</button>
</form>Replace YOUR_FORM_ID with the ID from your form backend and you have a working
contact form. That's the five-minute version. The rest of this guide covers where
this markup goes in a Hugo project, the thank-you page, spam, and doing it with
fetch if you want to stay on the page.
Don't want to hand-write the markup? Generate it — honeypot and Turnstile included — with the free contact form generator, then paste the result into the shortcode below.
1. Create the form endpoint
You need a URL to send submissions to. With ShipMyForm, create a form and copy
its endpoint — it looks like https://shipmyform.com/f/abc123. Every field's name
attribute (email, message) becomes a field you receive in your inbox.
2. Drop the form in with a shortcode
Here's the Hugo-specific catch: if you paste a raw <form> straight into a markdown
page, Hugo's renderer strips it — Goldmark ignores inline HTML unless you opt in.
You can flip that switch in your config:
# hugo.toml
[markup.goldmark.renderer]
unsafe = trueBut turning on unsafe for one form is a blunt instrument. The idiomatic Hugo move is
a shortcode — a reusable snippet you can drop into any markdown page. Create the
file:
<!-- layouts/shortcodes/contact-form.html -->
<form action="https://shipmyform.com/f/{{ .Site.Params.shipmyformID }}" method="POST">
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<button type="submit">Send message</button>
</form>Put your form ID in your site config once, so it isn't hard-coded in the template:
# hugo.toml
[params]
shipmyformID = "YOUR_FORM_ID"Now any content file can render the form with a single tag:
<!-- content/contact.md -->
---
title: "Contact"
---
Have a question? Send us a message.
{{< contact-form >}}The form renders inside your normal page layout, styled by your theme — no config switch, no raw HTML in your content.
If your contact page isn't markdown-driven, put the same <form> straight into a
layout like layouts/contact/single.html (or your theme's page template). Layout
templates are plain HTML, so no shortcode or unsafe setting is needed there.
3. Show a thank-you page
By default the browser follows the response to a thank-you page. Point it wherever you
like with a hidden _redirect field in the shortcode:
<!-- layouts/shortcodes/contact-form.html -->
<form action="https://shipmyform.com/f/{{ .Site.Params.shipmyformID }}" method="POST">
<input type="hidden" name="_redirect" value="{{ .Site.BaseURL }}thanks/" />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send message</button>
</form>Using {{ .Site.BaseURL }} keeps the redirect correct across local, staging, and
production builds. Create content/thanks.md with a confirmation message and you're
done — this works with zero JavaScript, which suits a static Hugo build.
4. Submit without leaving the page (optional)
If you'd rather show an inline success state, submit with fetch and ask for JSON
back. Hugo ships no client JavaScript by default, so add a small script alongside the
form in the shortcode:
<!-- layouts/shortcodes/contact-form.html -->
<form id="contact" action="https://shipmyform.com/f/{{ .Site.Params.shipmyformID }}" method="POST">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
<p id="status" hidden>Thanks — we'll be in touch.</p>
</form>
<script>
const form = document.getElementById("contact");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const res = await fetch(form.action, {
method: "POST",
body: new FormData(form),
headers: { Accept: "application/json" },
});
if (res.ok) {
form.reset();
document.getElementById("status").hidden = false;
}
});
</script>The Accept: application/json header is what makes the backend return { ok: true }
instead of redirecting. For a page with several forms, move this into
assets/js/ and pull it in with Hugo Pipes instead of inlining it.
5. Block spam
Static contact forms get scraped and hit by bots, but you can stop most of them without a CAPTCHA. The simplest, zero-friction defense is a honeypot — a hidden field real people never fill. If it's filled, the submission is a bot.
Add a honeypot field the backend knows to check:
<!-- visually hidden; keep it out of the tab order -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none" />ShipMyForm checks the honeypot and rate-limits submissions automatically, and you can turn on Cloudflare Turnstile if a form gets targeted — no reCAPTCHA puzzles for your visitors.
Everything here is safe to ship in a static build — there are no API keys in the page. The form endpoint is public by design and only accepts submissions from your allowed domains.
6. Get notified
Submissions land in an inbox, and you can turn on an email notification so every message hits you the moment it arrives. From there, route to Slack, Google Sheets, or a webhook without touching your Hugo templates.
Why not a Hugo "backend"?
Unlike some frameworks, Hugo has no server mode to fall back on — it's a build-time generator, full stop. There's no API route to add, no adapter to install, and nothing to deploy beyond static files. That makes a hosted form backend the natural fit: your site stays a folder of HTML, and the backend owns spam filtering, storage, and email. If you're weighing the trade-off, read what a form backend is and when you need one.
Next steps
- Harden it: stop contact form spam without reCAPTCHA
- Coming from Netlify? Netlify Forms alternatives
- Start free — 100 submissions a month, no credit card.
Frequently asked questions
- Can you have a contact form on a Hugo site?
- Yes. Hugo builds to static HTML, so you point a normal HTML form at a hosted form backend. The backend receives the submission, filters spam, and emails it to you — Hugo has no server-side runtime of its own, so this is the standard way to do it.
- Where do you put a form in Hugo?
- Either directly in a layout template, or — more reusably — in a shortcode you can drop into any markdown page with {{< contact-form >}}. Raw HTML in markdown is stripped unless you enable unsafe rendering, so a shortcode is the cleaner path.
- How do I stop spam on a Hugo contact form?
- Add a hidden honeypot field and let the backend score submissions, then optionally enable Cloudflare Turnstile. ShipMyForm checks the honeypot and rate-limits submissions automatically, with no reCAPTCHA puzzles for your visitors.
Related guides
What Is a Form Backend? (And When You Need One)
The plain-language definition, how form endpoints work, and when a hosted backend beats rolling your own.
How to Stop Contact Form Spam Without reCAPTCHA
Honeypots, rate limiting, and Turnstile — layered spam defenses that beat reCAPTCHA without the puzzles.
ShipMyForm vs Netlify Forms: A Netlify Forms Alternative
Netlify Forms is great if you are all-in on Netlify. Here is how ShipMyForm compares when you want a form backend that runs on any host.