techbits.io

Adding a members-only contact form with Ghost

Here's how I created a members-only contact form with Ghost that effectively eliminates spam messages while improving the user experience.

Adding a members-only contact form with Ghost

When I created the contact page for this site, I went through a few options before going with a fairly vanilla Formspree setup. But once I had that, I wasn’t really happy with the options. AJAX forms worked ok, POST forms with reCAPTCHA worked ok, but I didn’t really want one without the other.

After trying and failing several times, I scrapped trying to combine both deciding decided that an AJAX form is friendlier for users, but how could I prevent the form from being abused by spambots?

Ghost Members Only

The member signup process in Ghost is very seamless and doesn’t collect any information that isn’t necessary, and especially useful for those responsible for this data: Ghost doesn’t use passwords for members.

Signup can be either just an email address or an email address and name. Either way, fields that you’d expect to enter on a contact form.

Once you’ve hit the sign up button, you’re emailed a ‘magic link’ that you click to sign in. Future sign-ins use another magic link. The nice thing about using this for contact form messages is: we’ve already validated the email address.

After deciding this would be the best route, I took a look at the contact form template for the theme I’m using (Joben from Biron Themes). If your theme doesn’t have a specific template for the contact page, you’ll need to create a file to do so (Norbert from BironThemes has you covered there too, actually, with this guide) because it uses Handlebars syntax to decide what to serve to the user.

The form I ended up with is below, but the key points are:

  • The handlebars conditional {{#if @member}} will show everything within that block to a logged-in member (both free and paid).
  • Because we’re certain the form will only be shown to members, we can pre-fill member attributes, confident that there won’t be errors. In this case, we’re using {{@member.name}} and {{@member.email}}. We’re also adding readonly to those particular form fields, as well as adding some custom CSS to target readonly fields:
input[readonly] {
	background-color: lightgray;
 	color: gray;
 	box-shadow: 0 0 0 0;
 }
  • If the user isn’t a logged in member, they see what’s behind door number two: {{else}}—sign-in and sign-up links, as well as an alternative contact method (Twitter DMs).

The Form

<div class="content">
	{{#if @member}}
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <h4>{{t "Get in touch:"}}</h4>
        <div class="contact-form m-b-lg">
        	<p>Please send a message using the form below. Alternatively, DMs are open <a href="https://fosstodon.org/@techbits">@techbits@fosstodon.org</a>.</p>
			<div id="formBlock">
            	<form id="contact-form">
            		<input type="text" name="name" id="name" title="{{@member.name}}" value="{{@member.name}}" readonly>
              		<input type="text" name="email" id="email" title="{{@member.email}}" class="email" value="{{@member.email}}" readonly>
              		<textarea rows="5" name="message" id="message" title="{{t "Message"}}" placeholder="{{t "Message"}}" required></textarea>
              		<div class="text-center">
                		<button class="btn btn-default" type="submit" value="Send">Submit</button>
              		</div>
            	</form>
          	</div>
        </div>
        <div class="alert alert-danger center-block submit-fail display-none">Something has gone wrong...<br><a href="#"></a></div>
        <div class="alert alert-success center-block submit-success display-none">Thank you. Your message has been sent. Please sit tight while the packets are reassembled by an intern.<br><a href="#"></a></div>

        <script>
        $(document).ready(function() {
          $('#contact-form').submit(function(e) {
              var name = $('#inputName')
              var email = $('#inputEmail')
              var message = $('#inputMessage')
            
              if(name.val() == "" || email.val() == "" || message.val() == "") {
                $('.submit-fail').fadeToggle(400);
                return false;
              }
              else {
                $.ajax({
                  method: 'POST',
                  url: 'https://formspree.io/f/yourformid',
                  data: $('#contact-form').serialize(),
                  datatype: 'json'
                });
                e.preventDefault();
                $(this).get(0).reset();
                $('.submit-success').fadeToggle(400);
                $('#formBlock').fadeToggle(400);
              }
            });
          
        });
        </script>

      {{else}}
        <h4>{{t "Get in touch:"}}</h4>
        <div class="contact-form m-b-lg">
			Please <a class="signup-link header-cta" href="/signup/">{{t "Sign Up"}}</a> or <a class="signin-link" href="/signin/">{{t "Sign In"}}
            </a> to send a message. Alternatively, DMs are open <a href="https://fosstodon.org/@techbits">@techbits@fosstodon.org</a>.
        </div>
	{{/if}}      
</div>

The Results

Users that aren’t logged in will see this basic message:

After signing up, or in, and clicking the magic link email, the form loads with the user’s details filled:

Once the user hits submit, we show an error or success message depending on the server response:

Potential issues

There are caveats to using this method, although I don’t think they are too bad.

Firstly, even those the form fields are set to readonly, users are still able to edit this in the browser developer tools, and so the resulting email information would be different from a name/email that doesn’t match their account. Is this a big deal? The user would still need to be a signed-in member, and there are potentially other ways to include data that the user wouldn’t know to change. For example, a hidden form field, pre-filled with the hash of the user’s name and email. Another hidden field created by the javascript on send that hashes the name and email included in the form. If they match, it’s either genuine, or you have a really smart user.

The other potential issue is that the sign-in requirement might mean that users might be put off from messaging. Rewording the text might help, but for a website like this where there’s a low chance of users emailing, and the user isn’t a paying customer, I think it’s ok. Having a link/other contact information as a backup is good.


I enjoyed coming up with this workaround as it fits in really nicely with the core Ghost member features. If you find it useful, or you have a suggestion for improving it, please let me know in the comments below, or over at @techbits@fosstodon.org.

Tagged with: #ghost-tag

Share this article:

Archived comments

migrated from Ghost
A
Anonymous

May I get the <style> code for this form?

T
techbits.io

The only real customisation is setting name and email to readonly, as Ghost pre-fills using member information. That is:

input[readonly] {
background-color: lightgray;
color: gray; box-shadow: 0 0 0 0;
}

The rest is based on the theme styling, which I can't really share, but would recommend: https://brightthemes.com/themes/joben

The theme is quite heavily modified, but I don't think the form styling was modified.

Comments

Related posts

Ghost: Redirect www to non-www to fix SEO issues
#ghost-tag #nginx

Ghost: Redirect www to non-www to fix SEO issues

Learn how to forward all www requests to the non-www version of your Ghost site, thereby fixing SEO issues and ensuring any old links work.

Speed up your Ghost site with Varnish Cache and Webhooks
#ghost-tag #linux

Speed up your Ghost site with Varnish Cache and Webhooks

This guide will show you how to speed up your Ghost CMS site for visitors by utilising Varnish Cache, and how to clear the cache using webhooks.

Serve Images, Static File from a Different Domain for Ghost CMS
#ghost-tag #seo #nginx

Serve Images, Static File from a Different Domain for Ghost CMS

Speed up the your Ghost website loading time, as well as increasing your SEO by loading static content from a separate domain or sub-domain.