Contact form for Hugo websites
Hugo builds blazing-fast static sites — but ships zero backend. Drop a partial or shortcode in your theme, pull the access key from site params, and you have a working contact form without spinning up a Cloud Run service or wiring Netlify Functions. Pure HTML, zero JavaScript, full spam protection.
What your Hugo contact form actually looks like.
Drop-in form backend with spam filtering, signed webhooks, and a real submissions dashboard. The same code in this preview is what you copy into your Hugo project — no SDK, no plugin, no PHP.
- ✓1,000 submissions per month, free forever
- ✓Honeypot + AI spam classifier on every plan
- ✓Signed webhooks to Slack, Discord, your server
Ship a Hugo contact form without a backend.
No SDK, no PHP, no plugin. Your form posts standard FormData to one URL — submissions land in your inbox.
Get your free access key
Verify your email and your access key is generated instantly. Free for 1,000 submissions per month, forever.
By signing up, you agree to our terms and privacy policy.
Drop in the Hugo code
Copy the Hugo snippet on the right and paste it into your project. Replace YOUR_ACCESS_KEY with the key from step 1.
Submissions land in your inbox
Hits your dashboard and email in seconds. Forward to Slack, Discord, Sheets, Notion, or any signed webhook URL.
Try it now — no signup, no key.
This is a styled HTML preview of what your Hugo form will look like. Submitting opens a confirmation, no real request is sent.
Your Hugo form posts FormData to /api/submit. Splitforms validates the access key, runs the spam classifier, and forwards the parsed submission to your inbox plus the dashboard.
- →14ms median round-trip from the edge.
- →Honeypot + classifier, no CAPTCHA.
- →Per-domain key locking out of the box.
{
"access_key": "sk_live_4f9a_••••",
"name": "Maya Iyer",
"email": "maya@studio71.co",
"message": "…"
}How to ship this without regrets.
Five rules that make the difference between a form that works in the demo and a form that survives launch traffic.
- 01
Define the access key in `hugo.toml` under `[params]` — don't commit it to a public repo. For private repos, this is fine. For public repos, override via env var: `hugo --param splitformsKey=$SPLITFORMS_KEY`.
- 02
Build the form as a shortcode (`{{< contact-form >}}`) rather than a partial. Shortcodes are usable from Markdown content; partials only work in templates.
- 03
Add a /thanks page (`content/thanks/_index.md`) with your normal layout — splitforms's redirect target needs to exist and serve a real page.
- 04
Lock the access key to your production domain in the splitforms dashboard. Hugo's `--baseURL` flag during local dev means the form posts from `localhost:1313` — add that to allowed domains for testing or use a separate dev key.
- 05
If your theme uses Hugo Pipes for asset processing, add `<noscript>` styling to the form so it looks reasonable when JS is disabled — splitforms's pure-HTML pattern works without JS, but your theme might style buttons via JS-loaded CSS.
What bites people who skip the docs.
Worth a 60-second skim before you ship to production. Each one has caused a Hugo support ticket at least once.
Site.Params lookup is case-sensitive in some Hugo versions
If you set splitformsKey in hugo.toml under [params] but reference it as {{ .Site.Params.SplitformsKey }} in the template, recent Hugo versions still resolve it — but Hugo 0.110 and earlier don't. Use the exact case from your config file.
Markdown content stripping eats inline form HTML
If you put a <form> directly in a Markdown content file, Goldmark's HTML sanitizer strips it. Either set markup.goldmark.renderer.unsafe = true in your config, or wrap the form in a shortcode (recommended): {{< contact-form >}}.
absURL filter on the redirect URL adds a trailing slash you don't want
{{ "thanks/" | absURL }} produces https://yoursite.com/thanks/. Hugo's URL filter normalizes trailing slashes per your uglyURLs config — if your /thanks page lives at /thanks.html (uglyURLs=true), the redirect 404s. Hardcode the URL or set relURL consistently.
Hugo modules / theme overrides require partial in your project, not the theme
If you copy contact-form.html into a vendored theme's layouts/partials/, your changes get overwritten on the next theme update. Always put custom partials in your project's own layouts/partials/ — Hugo's lookup chain prefers project over theme automatically.
Hugo's `safeHTML` is needed when rendering the access key as raw output
If your splitforms key contains an underscore (it does — keys are sk_live_...), Hugo's HTML escaping is fine for input values, but if you echo it into JS contexts you may need safeJS. Sticking to <input value="…"> works as-is.
Hugo's --minify collapses whitespace inside the form's textarea default
If you build with hugo --minify, the HTML minifier strips whitespace and newlines aggressively — including the contents between <textarea>…</textarea> tags. A textarea pre-filled with \n\nWrite your message here… collapses to a single line, breaking the visual hint. The minifier respects <pre> but not <textarea> by default. Either avoid pre-filled textarea content (use a placeholder attribute instead — that's what placeholders are for) or disable minification of HTML via minify.tdewolff.html.keepWhitespace = true in your config.
How Hugo handles forms without splitforms.
The shape of the problem before splitforms enters the picture — and the gap it fills for Hugo specifically.
Hugo is a static site generator — there's no runtime, no /server/api, no way to handle a form POST without an external service. The native paths are: (a) a Cloudflare Worker / Lambda / Cloud Run service handling POST /contact and emailing you (~4 hours of setup, ongoing operation), (b) Netlify Forms (Hugo-on-Netlify only, 100/mo free), or (c) a third-party form backend like Formspree, Basin, or Web3Forms. Hugo's templating shines for the form's HTML — {{ partial }}, {{ .Site.Params }} for the access key, {{< shortcode >}} for Markdown reuse — but the delivery layer is always external. Splitforms is the simplest external option that doesn't require a Cloud Run service.
Two ways to ship splitforms on Hugo.
Pick the pattern that matches your constraints — JS budget, key-exposure tolerance, server-side opacity. Both produce the same result.
Pattern A — partial in `layouts/partials/contact-form.html`
Reusable partial called from any template with {{ partial "contact-form.html" . }}. Pulls the access key from Site.Params.splitformsKey. Project-level partials override theme partials automatically.
Pattern B — shortcode usable from Markdown content
Save as layouts/shortcodes/contact-form.html. Now writers can drop {{< contact-form >}} (with optional redirect="…" arg) directly into any .md file. Eliminates the need to switch to template editing for one-off forms.
Shipping Hugo + splitforms to production.
Host-specific gotchas, env-var conventions, and the boring-but-load-bearing details for putting this on the public internet.
Hugo deploys to any static host: Vercel, Netlify, Cloudflare Pages, GitHub Pages, S3, Surge, plain Apache/nginx. The form posts cross-origin so the host is irrelevant. Set splitformsKey in hugo.toml under [params] for private repos; for public repos, build with hugo --param splitformsKey=$SPLITFORMS_KEY and read it from CI env. Hugo's local dev server runs on localhost:1313 — add that to splitforms allowed-domains for testing or use a separate dev key. hugo --minify compresses HTML but doesn't touch attribute values — the form works under minification.
splitforms vs native hugo.
What you get for free vs what you build, pay for, or do without.
Shortcode variant — usable from Markdown content
If you want to drop the form into individual blog posts or content pages (not just template files), build it as a shortcode. Then write `{{< contact-form >}}` in any `.md` file.
Things developers ask before they integrate.
Direct answers, no marketing fluff. Missing one? Email hello@splitforms.com.
Ship your Hugo contact form in 60 seconds.
1,000 free submissions per month. No credit card. Lock the access key to your domains, paste the snippet, watch submissions land in your inbox.
