Hugo

Add a contact form to a Hugo site with a partial or shortcode and your access key in the site config.

Hugo builds plain HTML files, so a form has nowhere to post on your own host. SilentForms is the endpoint instead: the form posts to it from the browser and the submission is emailed to you. On the Hugo side you write one partial for the form markup, keep the access key in your site configuration, and add a shortcode so you can drop the form into any Markdown page.

1. Get an access key#

Enter your email on the access key page and click the confirmation link. The key only routes submissions to your inbox, so it is safe in the generated HTML.

2. Put the key in the site configuration#

Hugo reads hugo.toml, hugo.yaml or hugo.json in the project root (older sites may still use config.toml, which works too). Add the key under params, so every template can read it and you change it in one place:

baseURL = 'https://example.com/'
title = 'My site'

[params.silentforms]
  access_key = 'YOUR_ACCESS_KEY_HERE'

The same in hugo.yaml:

baseURL: https://example.com/
title: My site
params:
  silentforms:
    access_key: YOUR_ACCESS_KEY_HERE

Use lowercase, snake_case keys. Hugo treats parameter keys case-insensitively and stores them in lowercase, so accessKey would have to be read back as accesskey, which is easy to get wrong.

You can also supply the value at build time instead of committing it: Hugo maps environment variables with the HUGO_PARAMS_ prefix onto params, so HUGO_PARAMS_SILENTFORMS_ACCESS_KEY sets the same key.

3. Create the partial#

Since Hugo v0.146 partials live in layouts/_partials (earlier versions use layouts/partials). Create layouts/_partials/contact-form.html:

<form action="https://silentforms.com/api/submit" method="POST" class="contact-form">
  <input type="hidden" name="accessKey" value="{{ .Site.Params.silentforms.access_key }}">
  <input type="hidden" name="subject" value="New message from {{ .Site.Title }}">
  <input type="hidden" name="redirect" value="{{ "thanks/" | absURL }}">

  <label for="cf-name">Name</label>
  <input id="cf-name" type="text" name="name" autocomplete="name" required>

  <label for="cf-email">Email</label>
  <input id="cf-email" type="email" name="email" autocomplete="email" required>

  <label for="cf-message">Message</label>
  <textarea id="cf-message" name="message" rows="6" required></textarea>

  <!-- Honeypot: hidden from people, filled in by bots -->
  <input type="checkbox" name="botcheck" style="display: none;" tabindex="-1" autocomplete="off">

  <button type="submit">Send message</button>
</form>

Why each piece is there:

  • .Site.Params.silentforms.access_key reads the key from step 2. The partial expects the page as its context, which is what you pass when you call it.
  • absURL turns thanks/ into a full URL built from baseURL, such as https://example.com/thanks/. The redirect field must be absolute, and leaving off the leading slash keeps any subpath in baseURL (for example a project site under /my-repo/).
  • subject sets the email subject. See Email subject.

Use it from any template, for example in layouts/page/contact.html or a footer partial:

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

4. Add a shortcode for Markdown pages#

Hugo's Markdown renderer drops raw HTML by default, so pasting the form into a content file shows nothing. A shortcode avoids that without turning on unsafe HTML. Create layouts/_shortcodes/contact.html (layouts/shortcodes before v0.146):

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

Inside a shortcode the context is the shortcode itself, so .Page passes the page on to the partial. Then use it in content/contact.md:

+++
title = 'Contact'
+++

Questions, ideas or bug reports: write to us below.

{{< contact >}}

5. Add the thank-you page#

Create content/thanks.md. It renders with your normal single-page layout, and the build option keeps it out of section lists:

+++
title = 'Thanks!'
[build]
  list = 'never'
+++

Your message is on its way. We usually reply within a day or two.

[Back to the homepage](/)

Hugo publishes it at /thanks/, which is the URL the partial redirects to. If you skip this page and the redirect field, visitors land on the default SilentForms success page instead. More options in Redirect after submit.

Test it#

Run hugo server and open http://localhost:1313/contact/. The development server replaces baseURL with its own address, so absURL points the redirect at your local thank-you page. Send yourself a message, check you land on /thanks/, and check your inbox (and spam folder the first time).

Then run hugo and look at the generated public/contact/index.html: the hidden fields should hold your key and the production thank-you URL.

Gotchas#

  • The access key field is empty in the output. The partial was called without a context (partial "contact-form.html" with no dot), or the key name in the config does not match. Use lowercase keys.
  • The redirect goes to localhost. You deployed files built by hugo server. Deploy the output of hugo instead, which uses the real baseURL.
  • The form shows as plain text or nothing in a Markdown page. Use the shortcode instead of raw HTML.
  • Two forms on one page. The id values in the partial must be unique per page. Pass a prefix through a dict if you need the form twice.

For every error message the endpoint can return, see Troubleshooting.

Next steps#