Updated on Jun 3rd, 20216 min readjavascriptreactklaviyonextjs

React Contact Form With Formik + Klaviyo

Klaviyo is generally known as a platform to help websites gather contacts and deliver marketing emails and text messages. It is certainly a powerful tool, complete with deep analytics, many ecommerce integrations, and great design tools.

In addition to these features, Klaviyo also offers a Track API that can be used by developers to create their own events and pass information to email Flows via HTTP requests from a server. This system can be leveraged to create a simple Contact Form that we will essentially email to ourselves.

The Klaviyo free tier allows for 500 emails per month, making this a worthwhile tutorial any developer can easily set up. Create an account if you don't already have one.

In Account -> Settings -> API Keys you will be supplied a 6 character API Key. You will need this later.

It should be noted that the Klaviyo API can be used from any server side language, provided that language gives you the ability to send an HTTP GET request and deliver a Base64 and JSON encoded string.

There are official and unofficial libraries wrapping the Klaviyo API in a number of languages, but in my opinion these are not needed here.


I am actually using the email Contact Form from this site as the example in this article. As such, I am working with NextJS, a React framework supporting server side rendering. I am also using the library Formik to build and handle form actions.

Check out my post on Full Stack React, Formik & Yup for a more thorough example with respect to React form construction.

Install Formik into a JavaScript project with the following command.

We will also use Cors and Axios to facilitate HTTP communication between our frontend, API, and Klaviyo.

npm i formik cors axios

The Form

For this tutorial, we will use the Contact Form located on the home page of this website. A very basic setup to send me a message, along with the name and email of the user.

First we will create a structural form component, and then it will be wrapped in the withFormik higher order component.

Note that I have removed the CSS classnames from all components for simplicity. Style yours as you see fit.

Form Field Component

The basic building block of the form is the Form Field component. This can be modified to handle most inputs, and in this case we need the following:

  • A text input for the name field
  • An email input for the email field
  • A textarea for the message field
import { useField } from 'formik'

export default function FormField({ textarea, label, ...props }) {
  const [field] = useField(props)

  return (
      {textarea ? (
        <textarea {...field} {...props} rows={8} spellCheck={false} />
      ) : (
        <input {...field} {...props} spellCheck={false} />

The newish useField hook from Formik allows us to automatically spread properties and event listeners to our inputs. This works because we will be wrapping the FormField using withFormik.

We create a boolean called textarea to determine which basic HTML structure to use. Make this a string to create logic for more complex use cases, such as select dropdowns.

The label is self explanatory.

Any other props we pass are spread to the underlying form input.

Form Structure Component

The ContactForm component lays out the basic JSX of the form.

Pass the handleSubmit prop to a basic HTML form element and use a button element of type submit.

The type and required props will provide the necessary field level validation, which is built into HTML5.

import FormField from './form-field'

function ContactForm({ handleSubmit }) {
  return (
      <h2>Contact Us</h2>
      <form onSubmit={handleSubmit}>
          <FormField type='text' name='name' label='Name' required />
          <FormField type='email' name='email' label='Email' required />
          <FormField textarea name='message' label='Message' required />
          <button type='submit'>Submit</button>

Higher Order Component

The withFormik HOC takes a configuration object with a number of properties. In this case we only need 3 of them.

mapPropsToValues supplies the initial values for our form which match the names of our FormField components. These all begin as an empty string.

handleSubmit utilizes the Axios library to make a simple POST request to an api endpoint that we will create in the next step. With the help of async/await we wait for the request to complete and then reset the form fields.

displayName is helpful for debugging purposes, giving the component a human readable name.

import { withFormik } from 'formik'
import Axios from 'axios'

export default withFormik({
  mapPropsToValues: () => ({ name: '', email: '', message: '' }),

  handleSubmit: async (values, { resetForm }) => {
    await Axios({
      method: 'POST',
      url: '/api/contact',
      data: {

  displayName: 'ContactForm'

API Endpoint

The NextJS setup I am using allows us to create a basic API without having to create a full blown server from scratch, but you could certainly go that route as well. To accomplish this we need to navigate to our pages directory and create the needed file structure.

cd pages
mkdir api
cd api
touch contact.js

The individual filenames within the /pages/api directory correspond to the path of each endpoint.

The initMiddleware function helps us establish cors to allow Cross Origin Resource sharing between servers.

We export a handler function that takes a request and response as arguments. Once we call cors we can proceed with our request to Klaviyo.

While there are libraries out there that abstract the Klaviyo APIs, they are not needed, our Node environment provides the JSON and Base64 encoding that is required by Klaviyo.

Create a data object with the format I have outlined, passing the API Key as token, an arbitrary name as event, your own email as customer_properties.$email and the form values from req.body as properties, again with arbitrary names.

Finally, make a GET request to the Klaviyo Track API at https://a.klaviyo.com/api/track and attach the data object as a URL parameter, using built in functionality to format the payload properly.

I enclose all logic in a try/catch to provide error handling and to give the frontend an idea of how everything went. While I don't utilize this response in this project, you would simply assign a variable to the Axios POST request and then check it to determine your UI.

import Cors from 'cors'
import Axios from 'axios'

function initMiddleware(middleware) {
  return (req, res) =>
    new Promise((resolve, reject) => {
      middleware(req, res, (result) => {
        if (result instanceof Error) {
          return reject(result)
        return resolve(result)

const cors = initMiddleware(
    methods: ['GET', 'POST', 'OPTIONS']

export default async function handler(req, res) {
  await cors(req, res)

  const data = {
    token: 'XXXXXX',
    event: 'Contact Form',
    customer_properties: {
      $email: 'you@gmail.com'
    properties: {
      Name: req.body.name,
      Email: req.body.email,
      Message: req.body.message

  try {
    await Axios({
      method: 'GET',
      url: `https://a.klaviyo.com/api/track?data=${Buffer.from(
    res.json({ success: true })
  } catch (error) {
    res.json({ success: false })

Klaviyo Flow

Once the first GET request is sent to the Klaviyo system, go to your account and check the Metrics page. You should see a new metric that corresponds to the name of your event. Navigate to the Activity Feed for Contact Form and the first event with all the attached data should be listed. Cool, right?

Contact Form Metric
Contact Form Metric

Now you can follow the Create A Flow' Article From Klaviyo, using the Contact Form metric as a trigger. You can use one of their templates, or just create the email from scratch. Klaviyo has a high quality drag and drop editor to create impressive emails.

To pull in the contents of our form use {{ event.property }} directly within a Text Block, replacing property with Name, Email, or Message in this case.

Create An Email Template
Create An Email Template

Add a logo to fancy things up.

As a side note, the Klaviyo logo is hidden on paid accounts.

Final Thoughts

Armed with the ability to create custom Klaviyo events, you can create a great Contact Form or systems which go much, much further beyond this basic example.

For my employer I have developed a number of custom Klaviyo events we use to trigger email Flows. These includes adding a product to a shopping cart, adding a tracking link for an order, and a customer requesting a price quote on an expensive item. There is really no limit to what you can create and Klaviyo's WYSIWYG editor makes creating beautiful looking emails much easier than having to code them from scratch.

A custom metric can accept links, images or any other data you work with. Flows can incorporate a series of different emails with delays, logical forks, and filtering. Klaviyo provides granular analytics for sending and engagement. The Track API makes much of this possible.

Benjamin Brooke Avatar
Benjamin Brooke

Hi, I'm Ben. I work as a full stack developer for an eCommerce company. My goal is to share knowledge through my blog and courses. In my free time I enjoy cycling and rock climbing.