How to use Gatsby with WordPress and Gravity Forms

Gatsby and WordPress is a combination that seems to be growing with each day. The benefit for the client is that there is not much of a change in managing the site if they are used to WordPress. They do however get the benefit of the speed and security of Gatsby.

The WordPress and Gatsby workflow can be improved upon even more when adding ACF to the CMS. This introduces a fully customisable front-end with an easy to use back-end.

The only problem is around collecting data from a form. How can a Gatsby website save form data to WordPress? It is a static site – generally hosted on a different host to the CMS. Sometimes with a different domain. Although I am a big advocate of the ‘micro-services’ way of working, for some clients there is a benefit of having all control under one roof. Especially when dealing with smaller marketing sites.

Unfortunately WordPress does not come with a form solution out of the box, but there are several third party packages that do the job.

One of the largest WordPress form plugins is Gravity Forms. It is very easy to set up for a standard WordPress install, unfortunately it does not integrate with Gatsby.

Until now…

The Gatsby Gravity Forms component, and Gatsby Gravity Forms Source plugin help to solve this problem. These two work together to extract form data from Gravity forms inside the WordPress CMS, as well as manage passing it back.

This walk through expects you to own a copy of Gravity Forms, and already have it installed.

Pull Gravity Form Fields from WordPress into Gatsby

This stage requires the gatsby-source-gravityforms plugin. It connects to a WordPress Gravity Forms install, finds all active forms and settings and converts them to GraphQL nodes. These nodes can then be queried within Gatsby.

Set up Gravity Form API Keys

So that Gatsby can pull data from Gravity Forms, a set of API keys are needed to allow secure access.

Within your WordPress admin panel, click on the Gravity Forms menu item and go to settings. Within settings open the REST API panel.

To get an API key, the setting ‘Enable access to the API’ must be enabled. This will then allow you to create a new set of keys. It is recommended to create a new WordPress user to link to these keys – that way, if a user is compromised they can easily be deleted and replaced.

Once the keys are created, add both the consumer key and consumer secret to the environment variables file. Make sure to not leave the page whilst you do this as Gravity Forms will not allow you to see the keys again.

Install Gatsby Gravity Forms Source Plugin

# Install the plugin
yarn add gatsby-source-gravityforms

# Or with NPM
npm i gatsby-source-gravityforms

Then add the following to gatsby-config.js

module.exports = {
    plugins: [
        {
            resolve: 'gatsby-source-gravityforms',
            options: {
                // Base URL needs to include protocol (http/https)
                baseUrl: 'SITE_BASE_URL',
                // Gravity Forms API
                api: {
                    key: 'CONSUMER_KEY',
                    secret: 'CONSUMER_SECRET',
                },
            },
        },
    ],
}

Update CONSUMER_KEY and CONSUMER_SECRET with the keys created in the Gravity Forms setting panel. Change SITE_BASE_URL to the base URL of your site.

Render Gravity Forms in Gatsby

Now the source plugin is pulling in forms from the WordPress install, it is time to render them with Gatsby. This can be managed using gatsby-gravityforms-component. It outputs HTML form components using BEM CSS classes, meaning all you need to do is style it. React Hook Forms is used under the hood – one of the better React forms packages I have used.

Install Gatsby Gravity Forms Component

# Install the component
yarn add gatsby-gravityforms-component

# Or with NPM
npm i gatsby-gravityforms-component

Using the Component

  1. Import the component where you want to use it
  2. Grab the GraphQL data from the gatsby-source-gravityforms plugin and pass to component
  3. Set the form ID
  4. Add your environment variables for Lambda

The above looks like this:

import React from 'react'
import GravityFormForm from 'gatsby-gravityforms-component'

// Would recommend moving this into a separate /src/hooks/gravityforms.js file
// and import where needed
import { useStaticQuery, graphql } from 'gatsby'
const allGravityData = () => {
    const { allGfForm } = useStaticQuery(
        graphql`
            query {
                allGfForm {
                    edges {
                        node {
                            ...GravityFormComponent
                        }
                    }
                }
            }
        `
    )
    return allGfForm
}

function handleError({values, error, reset}) => {
    //handle error
}

function handleSuccess({values, reset, confirmations}) => {
    //handle success
}

const examplePage = () => (
    <GravityFormForm
        id={1}
        formData={allGravityData()}
        presetValues={{ input_1: 'special_value' }}
        lambda={process.env.LAMBDA_ENDPOINT}
        successCallback={handleSuccess}
        errorCallback={handleError}
    >
)
export default examplePage

This outputs the form that has been set up in WordPress – Gravity Forms. Ready for you to style it!

  • id: The ID of the form, get in WordPress Gravity Forms
  • formDate: The data passed from the query function – this is the same for all forms
  • presetValues: An object, with the keys set as the input ID (shown in Gravity Forms editor) and the value to set the field as. Great for hidden fields.
  • lambda: The URL to the lambda endpoint
  • successCallback: function to be called on successful form submission. values: form values, reset: function to reset form, confirmations: form confirmations set in WP GF.
  • errorCallback: function to be called on error in form submission. values: form values, reset: function to reset form, error: error response from API.

Pass Form Data back to WordPress

As Gatsby outputs static sites, generally cross domain, it is not as simple as adding URL to pass the data back to WordPress. To enable the component to pass data from a static site to a server there needs to be something to help to bridge the gap. As Gravity Forms uses secret keys to read/write, there needs to be somewhere safe to hold and manage these details.

The following steps are for setting up a Lambda function within Netlify. However they can be altered slightly to work with any other platform.

Firstly, install some required packages:

# Install the component
yarn add axios nanoid oauth-signature

# Or with NPM
npm i axios nanoid oauth-signature

Then:

  1. Add a folder called “lambda” in your projects /src folder
  2. Create a file inside called “newGfEntry.js”
  3. Put the following code into that file.
const axios = require('axios')
const nanoid = require('nanoid')
const oauthSignature = require('oauth-signature')

let activeEnv =
    process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || 'development'

require('dotenv').config({
    path: `.env.${activeEnv}`,
})

// Set up essential values
const secretData = {
    gfKey: process.env.GATSBY_GF_CONSUMER_KEY,
    gfSecret: process.env.GATSBY_GF_CONSUMER_SECRET,
}

// For those requests
// Update with correct origin when on production!
const headers = {
    'Access-Control-Allow-Origin': '*', // THIS SHOULD BE CHANGED TO YOUR ORIGIN ONCE IN PRODUCTION
    'Access-Control-Allow-Headers': 'Content-Type',
}

exports.handler = async event => {
    // Make sure we are dealing with a POST request
    if (event.httpMethod !== 'POST') {
        return {
            statusCode: 200, // <-- Important for CORS
            headers,
            body: JSON.stringify({
                status: 'notPost',
                message: 'This was not a POST request!',
            }),
        }
    }

    // Parse that post data body
    const data = JSON.parse(event.body)

    const apiUrl = data.baseUrl + '/submissions'

    // Check we have the required data
    if (!apiUrl) {
        return {
            statusCode: 424,
            headers,
            body: JSON.stringify({
                status: 'missingApiData',
                message: 'Required API data is missing',
            }),
        }
    }

    // Now we can do the real work - Gravity Forms API stuff
    const authParams = new0AuthParameters(secretData.gfKey)
    const signature = oauthSignature.generate(
        'POST',
        apiUrl,
        authParams,
        secretData.gfSecret
    )

    let result

    try {
        result = await axios({
            method: 'post',
            url: apiUrl,
            responseType: 'json',
            params: {
                ...authParams,
                oauth_signature: signature,
            },
            data: data.payload,
        })
    } catch (error) {
        // Check the function log for this!
        console.log('newGFEntry.js Error Data')
        console.log(error)

        const errorResponse = error.response.data

        // Here we know this is a Gravity Form Error
        if (errorResponse.is_valid === false) {
            return {
                statusCode: 422,
                headers,
                body: JSON.stringify({
                    status: 'gravityFormErrors',
                    message: 'Gravity Forms has flagged issues',
                    validation_messages: errorResponse.validation_messages,
                }),
            }
        } else {
            // Unknown error
            return {
                statusCode: 400,
                headers,
                body: JSON.stringify({
                    status: 'unknown',
                    message: 'Something went wrong',
                }),
            }
        }
    }

    return {
        statusCode: 201,
        headers,
        body: JSON.stringify({
            status: 'success',
            message: 'Entry added to Gravity Forms',
            confirmation_message: result.data.confirmation_message,
        }),
    }
}

function getCurrentTimestamp() {
    return Math.round(new Date().getTime() / 1000)
}

function new0AuthParameters(consumerKey) {
    return {
        oauth_consumer_key: consumerKey,
        oauth_timestamp: getCurrentTimestamp(),
        oauth_signature_method: 'HMAC-SHA1',
        oauth_version: '1.0',
        oauth_nonce: nanoid(11),
    }
}
  1. Make sure all environment variables at the top of the code have been updated with yours.
  2. Add a folder at the root of your project called “lambda”. This will be empty. The built lambda function will go in here on deployment.
  3. Create a file at the root of your project called netlify.toml
  4. Add the following code to netlify.toml:
[build]
  Functions = "lambda"
  Command = "npm run build:lambda && gatsby build"

[build.environment]
  GF_CONSUMER_KEY="XXX"
  GF_CONSUMER_SECRET="XXX"

The netlify.toml file will override your build command on deployment. So we need to tell Netlify to build your lamdba function and also your Gatsby site. The lambda function does not have access to your .env.* files you need to define the same keys here again (it is unfortunate to double handle this).

When done, you will have created these files and folders: ./netlify.toml ./lambda/ ./src/lambda/ ./src/lambda/newGfEntry.js

If you have any issues with these steps, see these articles:

https://www.netlify.com/docs/functions/
https://travishorn.com/netlify-lambda-functions-from-scratch-1186f61c659e
https://macarthur.me/posts/building-a-lambda-function-with-netlify