Skip to main content
New:Build with AI agents

Hugo contact forms
without serverless glue.

Keep your Hugo site static and point the generated form at Form Plume for email notifications, dashboard storage, spam filtering, file uploads, integrations, and signed webhooks.

Free foreverNo credit cardHugo endpoint ready in under a minute
layouts/partials/contact-form.html
<form action="{{ site.Params.formplumeEndpoint }}" method="POST">
  <input type="hidden" name="source" value="hugo">
  <input name="name" autocomplete="name" required>
  <input name="email" type="email" autocomplete="email" required>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

The full guide

A hosted backend for Hugo partials.

Use Hugo params and partials to generate a plain form that works from any static host.

  1. 1
    Create your Form Plume accountStart free and give your Hugo form a hosted endpoint for email, submissions, spam filtering, uploads, integrations, and webhooks.Start free
  2. 2
    Create the form and copy the endpointCopy the endpoint URL from Form Plume. It looks like https://api.formplume.com/f/your-slug
  3. 3
    Connect it inside HugoPut the endpoint in Hugo params, render it in a contact form partial, build the site, and inspect the generated HTML before deploying. Send one test submission from the real Hugo app or preview environment and confirm it reaches Form Plume.

You can use this AI prompt to hand the platform-specific wiring to an assistant without accidentally creating a backend.

Hugo forms belong in partials

Hugo generates static HTML, so the contact form only needs a real action, method="POST", and named fields. Put the markup in a partial such as layouts/partials/contact-form.html so every page uses the same endpoint and field names.

<form action="{{ site.Params.formplumeEndpoint }}" method="POST">
  <input type="hidden" name="source" value="hugo">
  <input type="hidden" name="_redirect" value="/thanks/">
 
  <label for="contact-name">Name</label>
  <input id="contact-name" name="name" autocomplete="name" required>
 
  <label for="contact-email">Email</label>
  <input id="contact-email" type="email" name="email" autocomplete="email" required>
 
  <label for="contact-message">Message</label>
  <textarea id="contact-message" name="message" rows="5" required></textarea>
 
  <button type="submit">Send message</button>
</form>

Then render the partial from a page template.

{{ partial "contact-form.html" . }}

Store the endpoint in params

Keep the endpoint out of repeated templates. Put it in hugo.toml, config/_default/params.toml, or an environment-specific config file.

[params]
formplumeEndpoint = "PASTE_YOUR_FORM_PLUME_ENDPOINT_HERE"

After hugo builds the site, inspect public/contact/index.html and confirm the real endpoint replaced the placeholder.

Uploads

Hugo does not need a plugin for file uploads. The generated HTML just needs multipart encoding and a named file input.

<form
  action="{{ site.Params.formplumeEndpoint }}"
  method="POST"
  enctype="multipart/form-data"
>
  <input type="file" name="attachment" accept=".pdf,.png,.jpg">
  <button type="submit">Send file</button>
</form>

Form Plume stores attachments separately from email delivery so large files do not break notifications.

Deploy anywhere

This works on Cloudflare Pages, Netlify, Vercel, GitHub Pages, S3, or any host that serves Hugo output. The host does not need native form handling because the browser posts to Form Plume.

Test the deployed URL, not only a local preview. Redirect paths, custom domains, and spam signals are easiest to verify on the final public page.

Primary sources

FAQ

Hugo form questions
before deploy.

Yes. Hugo outputs HTML, and the browser can post that form directly to Form Plume from any static host.

One line. Zero backend.

The form backend you don’t have to build.

Free foreverNo credit cardSet up in under a minute