Are you sick and tired of implementing HubSpot forms that look like absolute garbage? Basically struggling with iframes that just completely destroy your user's experience? Or even better yet, do you guys get nightmares from when you try to render a Gravity Form on a headless site? Because all that I get from that is a whole lot of HTML spaghetti and a confusing-ass API.
But guess what, you're in luck. I'm going to show you, you can ditch those Frankenstein forms, escape microservices, how? By creating great-looking custom forms for your website using Payload's Form Builder plugin. Now, we designed this plugin to simplify your life as an engineer so that you don't need to go sign up for another form service, but instead, you can start easily creating forms using your existing components on your current website.
And going back to HubSpot and Gravity Forms themselves, they're just becoming more and more outdated as we move forward. And it's 2023, there's just better solutions out there, like Payload. So let's delve right into how Payload's Form Builder plugin operates. I'll guide you through the setup process and I'll show you how to seamlessly integrate it into your front end. So let's jump right in.
Installing & Configuring the Plugin
Alright, let's get this plugin up and running. First thing we want to do is just go ahead and install it. You'll know you have it installed once you go to your package.json file and see the Form Builder plugin within your dependencies.
Now we just need to head over to our payload config file and add the Form Builder plugin to our plugins array. I've already went ahead and done that, this is what it looks like right here. I have a formFields type object, and this is where all the four different form fields that you have access to are being stored. By default, you have access to all of them, so you don't need to do anything. But if you want to go ahead and specify which ones to include and not to include, just pass a boolean value to which ones you want or don't want. And I'll add a link in the description to the plugin's GitHub repo where we have a list of all the different field types you do have access to.
By now, if you go to your admin UI, you will see two new collections added to your instance, and that's going to be the forms collection and the form-submissions collection. forms collection is where all the different forms that you create will be stored, and the form-submissions collection, just pretty self-explanatory, it's where all the different submission data is going to be stored.
Now ideally we're going to want to be able to add forms to any page of our website, so to do that we need to create a reusable form block component. And trust me, it's easier than it looks. Within our blocks folder, we just need to create a form-block configuration file, and that looks like this. Pretty standard. We're going to name it form-block, give it a few fields. The key field is this relationship field, which links our form block to the forms collection. And then just a couple other fields: an enable-intro checkbox and an intro-content rich text field, which is enabled if enable-intro is selected.
And finally, we can't forget, we need to add this block to our blocks array within our pages collection. So if I go ahead into my pages collection, I just need to add the form block to this specific array. And this essentially replicates what like a layout builder is.
Now we can go ahead and take a look at all this in the admin UI. And as a quick side note, everything you see on my screen, you all have full access to. I'm using a form builder example repo which exists in the Payload repo in the examples folder. And this comes with a fully built-out CMS and a fully built-out Next.js front end with some pre-seeded data, which includes some built-out forms and some built-out pages using those forms. And I'm going to add a link in the description so you guys can go ahead and check this out yourselves.
Exploring the Admin UI
But back to it. If you take a look, we've got our two new collections: forms and form-submissions. Currently, there's no submission, so this is empty. And forms has some pre-seeded forms. If we take a look at these, here we can... this is where we build out our forms. So there's very... already some fields. You can go ahead and add more fields. This is where you can craft your confirmation message as well as enable some email configuration if you want to send some emails on the form submission. Pretty cool stuff, very easy to integrate.
And finally, here's our pages collection. And in our home page, I've already went ahead and subbed out a form block. So here it is in our layout builder. You have access to every different form that you've built out. Right now this is using the basic form and here's some intro content.
So at this point, it's safe to say that our back end is fully configured. All that's left to do is to develop our front ends, enabling it to fetch form data and submit it back to our form submissions collection.
So first thing we need to do is create our form blocks folder on our front end. And this is going to contain our essential components like the form block component itself and all the field components as well, like for a text field, select field, text area field, etc.
And as a quick note, this example is using the React Hook Form library for easy validation and error handling of each of our form fields. If you're not too familiar with the API, I'm also going to add a link to that in the description down below.
Now let's start by taking a look at the form block component itself. So this is a functional component that is receiving props of type FormBlockType. FormBlockType is defined by our TS interface which is generated on our Payload backend. We use this method so that we have an extensible amount of reusable types that we can then use on any of our components, and this keeps all of our components type-safe. Some of these props include enableIntro, introContent, form which is just our form fields. Also, we've got the ID of the form, submitButtonLabel, confirmationType, whether it be a message or a redirect, and if it's a message, confirmationMessage is also available.
Next, we are calling the useForm hook, which comes from the React Hook Form library. And this right here creates a form instance for us with default values. And we are also using it to validate our form fields that are being passed down by our form. This allows all of our form field components to be able to use these useForm hook properties, and it gives them all independent validation, which is pretty cool.
Next, we are employing a various amount of form states, which includes isLoading, hasSubmitted, and error. Pretty self-explanatory of what they do, but we're using them so that we can monitor potential errors that we get or if we're not doing a good job handling certain scenarios. And then this allows us to solve these errors or potential scenarios very efficiently.
Now, before we go ahead and discuss the data submission process, because that is the next thing that's happening, I want to go ahead and show you guys the form itself. So let's go ahead and scroll down. And here we are. So first off, we are rendering a rich text component for the intro content, which only gets rendered if enableIntro is true and there is introContent and the form has not been submitted yet.
Next, we're rendering a confirmation message if the form has been submitted and the confirmationType is a message. And then also we are adding a loading state. So once you submit your form, it'll probably pop up saying loading so you understand that your form has been submitted. And if something goes wrong, we have a error handling message so that tells you what error you're getting, what happened.
And finally, we have our form element itself, which consists of our form fields and a submit button.
Now our form fields are being rendered through a map function of the form from props fields array. Now our fields are using the payload block field type, which this allows us to compose a form of fields that are essentially just independent pallet blocks. And just like how we use our page layout builder, instead of building on a page, we're just building out a form of blocks. But in other words, it's just a form of fields, which is pretty cool.
Now I do want to show you guys what a field component actually looks like, what it consists of. But before I do that, I want to show you guys the bulk of this form component, which is the on submission function which gets triggered by when somebody submits a form. So let's go ahead and take a look at that.
Now here is the onSubmit function itself. This is responsible for sending our form data back to Payload. So how is that done? Basically, when a form is submitted, we are sending a POST request to a specified endpoint. In our case, this specified endpoint is /api/form-submissions. Now we want to send it to this endpoint because we want our form data to reside in the form submissions collection. After that, we just have some error handling.
The last thing we need to do is handle redirects. So if the confirmationType is of type redirect, then we just want to obtain the URL from the redirect object and do a router.push, which sends the user to the specified redirect URL after a form submission. Which is very easy. And that's about it. It's really just a simple fetch to Payload, and then either a POST request to Payload's GraphQL API or the REST API, and then you're done. Payload is really easy to understand. There's no black magic happening here. When you're creating a form submission, it's no different than creating anything else in Payload, which is great.
Deep Dive: The TextField Component
Now before I want to show you guys an actual form happening on the front end, I want to show you guys one of our field components, so you guys have an understanding of what's happening there. And I think we should take a look at the text field component. So here it is, here is our text field. This takes the props name, label, width, register, required, and errors. This field starts off by rendering a width component, and that takes width as a prop, which is something we define on the back end when we're creating our forms; we get to say how wide we want each of our fields to be.
Next, we have a label element that takes the label prop as its content. And then we have our input text element, which is basically our text field. And this is calling a register function that takes name and required as its properties. Now to give a little background of what's happening in this function is we're basically telling React Hook Form's form management system that you need to identify this text field by this specific name, and then you also need to follow the validation rules that are specified. In our case, our text field could be required if we give it that prop so it needs to follow that specific rule. So if we say this text field is going to be required, and we go to submit this form without filling out this field, then it's going to error out. And in our case, if that happens we are throwing this error component, sorry, so that you see that it's not going to submit until you fill out this required text field.
And that's basically it. We do this exact same thing with the rest of our field components. It's nothing fancy, but it gets the job done really well. And at this point, all that we really need to do is render our form block in our blocks component so that we can then see it on our front end. So here is our blocks component. And it's really no different than how I showed you our form element or form component works. We map through all of our blocks, and then we just render each block, similar to how we map through all of our fields and render each field. And yep, now that's it. Let me show you guys what the front end looks like.
All right, so here's the front end side of the Form Builder example repo that I've been showing you guys. This is the home page. It's got the basic form on it which I showed you guys in the CMS a little bit ago. A couple of text fields, an email field, and this text area field. So let's go ahead and fill it out and see what happens on submission.
Okay, I'm not going to fill out the email field just yet because I know it's a required field and I want to show you guys validation happening when I submit on something that's required but not filled out. What's the coolest project I've built? Has to be the Form Builder example, right? Submit. And boom, field is required. So I did not submit, and it threw an error saying I have to fill it out before I can submit. So let's fill that out and submit again. Boom, basic form has been submitted. This is great. Now let's confirm that this data was sent over to Payload by checking the form submissions collection. And here's our submission data: Pat K, my email, and my text area field.
Conclusion & Final Thoughts
So there you have it. With Payload's Form Builder plugin, creating and managing forms is a breeze. And the best part is is that you can reuse the setup that I've shown you on any future project, which is going to decrease development time by a lot. So please go ahead and try this out and let us know what you think. If you enjoyed the video or found it helpful, please like the video and subscribe to Payload CMS. I'll see you guys soon. Bye.