WordPress

Add a contact form to WordPress with a Custom HTML block and no form plugin. Submissions go straight to your inbox.

Most WordPress contact forms are a plugin that stores entries in your database and sends mail through your server. With SilentForms you skip all of that: a Custom HTML block holds the form, and the visitor's browser posts it straight to the submit endpoint. There is no plugin to install or update, and nothing is sent from your server.

Get an access key on the access key page first. It only routes submissions to your inbox, so it is fine to have it in the page's HTML.

Why this avoids mail problems#

WordPress sends email with PHP's mail() by default. On many hosts that mail never arrives or lands in spam, because the server is not authorised to send for your domain, so form plugins usually end up needing an SMTP plugin and mailbox credentials on top. Here, WordPress never sends anything: SilentForms delivers the email from its own address and sets the visitor's email as Reply-To. You keep no SMTP password in WordPress and have one less plugin to maintain.

Add the form with a Custom HTML block#

  1. Edit the page in the block editor.
  2. Click the block inserter (+) and choose Custom HTML, or type /html in an empty paragraph and press Enter.
  3. Click Edit HTML, paste the form below and click Update.
  4. Save or publish the page.
<form class="sf-form" action="https://silentforms.com/api/submit" method="POST">
  <input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY_HERE">
  <input type="hidden" name="subject" value="New message from the website">
  <input type="hidden" name="redirect" value="https://example.com/thank-you/">

  <p>
    <label for="sf-name">Name</label>
    <input id="sf-name" type="text" name="name" autocomplete="name" required>
  </p>
  <p>
    <label for="sf-email">Email</label>
    <input id="sf-email" type="email" name="email" autocomplete="email" required>
  </p>
  <p>
    <label for="sf-message">Message</label>
    <textarea id="sf-message" name="message" rows="6" required></textarea>
  </p>

  <input type="checkbox" name="botcheck" style="display: none;" tabindex="-1" autocomplete="off">
  <p><button type="submit" class="wp-element-button">Send message</button></p>
</form>

Create a page called "Thank you" and put its full URL in the redirect field; after a successful submission the visitor lands there. Without the field, they see the SilentForms default thank-you page. See Redirect after submit.

Warning:Add the block as an Administrator (on multisite, a Super Admin). WordPress filters the HTML of users without the unfiltered_html capability when they save, and form markup can be stripped. If the form disappears after saving, this is why.

To use the same form on several pages, turn the block into a synced pattern from the block's options menu, so you can change the form in one place.

Style it#

Themes style form fields very differently, so add a few rules of your own. The wp-element-button class on the button picks up the button styles of most block themes.

  • Block themes: go to Appearance > Editor > Styles, open the Styles menu and choose Additional CSS.
  • Classic themes: go to Appearance > Customize > Additional CSS.
.sf-form label { display: block; margin-bottom: 0.25rem; font-weight: 600; }
.sf-form input[type="text"],
.sf-form input[type="email"],
.sf-form textarea {
  width: 100%;
  max-width: 36rem;
  padding: 0.65rem 0.8rem;
  border: 1px solid currentColor;
  border-radius: 4px;
  font: inherit;
}

Caching plugins and nonces#

Page caching does not affect this form. It is static HTML with no WordPress nonce or session to expire, so a cached copy of the page works exactly like a fresh one, and you don't need to exclude the contact page from your caching or CDN plugin. Security plugins that block form posts to your own site are not involved either, because the form never posts to WordPress.

Optional: stay on the page#

To show a message without leaving the page, add this under the form in the same block. From WordPress 7.0 the Custom HTML block also has separate HTML, CSS and JavaScript panels, so you can put the script (without its <script> tags) and the CSS above in their own panels instead. Either way, only users with unfiltered_html can save scripts, the same rule as above.

<p id="sf-status" role="status"></p>
<script>
  document.querySelector('.sf-form').addEventListener('submit', async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const status = document.getElementById('sf-status');
    status.textContent = 'Sending…';
    try {
      const response = await fetch(form.action, {
        method: 'POST',
        headers: { Accept: 'application/json' },
        body: new FormData(form),
      });
      const result = await response.json();
      status.textContent = result.ok ? 'Thanks! Your message has been sent.' : result.message;
      if (result.ok) form.reset();
    } catch {
      status.textContent = 'Something went wrong. Please try again.';
    }
  });
</script>

Test it#

Open the published page in a private window (so you see what visitors see, not a logged-in preview), send a message and check your inbox. The email lists every field in order, and Reply goes to the address you typed. If an error page appears instead, its message tells you what to fix; all of them are in Troubleshooting.

Next steps#