Link

Building a custom form

Estimated time to implement: 1 hour, difficulty: 6/10

Begin with a simple form structure that looks something like this:

{% form 'create_customer', id: 'my-cool-form' %}
  <div>
    <label>First name</label>
    <input name="customer[first_name]" type="text">
  </div>

  <div>
    <label>Last name</label>
    <input name="customer[last_name]" type="text">
  </div>

  <div>
    <label>Email</label>
    <input name="customer[email]" type="email">
  </div>

  {% unless customer %}
    <div>
      <label>Password</label>
      <input name="customer[password]" type="password">
    </div>
  {% endunless %}

  <button type="submit">Create account</button>
{% endform %}

This form will only create an account in Shopify and won’t support custom fields. What we need to do is:

  1. Render the snippet in the <head>
  2. Add column keys to the input elements
  3. Hook into the form’s submit event
  4. Call event.preventDefault to prevent the form from submitting natively
  5. Merge the form data with the customer with CF.customer.set
  6. Submit the data to Customer Fields by calling CF.customer.save
  7. Login the customer automatically and send them on their way

Render the snippet

Navigate to layout/theme.liquid in your theme editor, then add the following code inside the <head> tag:

{% render 'customer-fields', customer_api: true, version: '<Version number>' %}

Add column keys to the fields

The name attributes on these fields aren’t quite right for Customer Fields. The app needs first_name, not customer[first_name]. We could write code to strip away the customer[] text, but it’s much simpler for all of us to just add an extra attribute we can read from. We’ll leave the name attributes there just in case something wrong happens, it’s a good idea to fallback on Shopify.

For each of the input elements, copy the name attribute to a new one called data-column-key then remove customer[] from its value. We’ll use this attribute later on.

Before:

<input name="customer[first_name]" type="text">

After:

<input name="customer[first_name]" data-column-key="first_name" type="text">

Hook into the form’s submit event

Our form has an id of my-cool-form, so let’s find it by that. We’ll wrap all of our logic in a CF.customerReady call to ensure we’re only changing the form’s behavior if Customer Fields is properly prepared.

CF.customerReady(function() {
  var $myCoolForm = document.querySelector('#my-cool-form');
});

Now, we can addEventListener to run a function when the submit event fires:

CF.customerReady(function() {
  var $myCoolForm = document.querySelector('#my-cool-form');

  $myCoolForm.addEventListener('submit', function handleCustomFormSubmit(event) {
  
  });
});

Prevent native submission

In our event handler called “handleCustomFormSubmit”, let’s call preventDefault so that the form doesn’t go directly to Shopify:

CF.customerReady(function() {
  var $myCoolForm = document.querySelector('#my-cool-form');

  $myCoolForm.addEventListener('submit', function handleCustomFormSubmit(event) {
    event.preventDefault();
  });
});

Merge the form data with the customer

It’d take a ton of time to go through each line of code, so here’s all that’s needed with some helpful comments:

CF.customerReady(function() {
  var $myCoolForm = document.querySelector('#my-cool-form');

  $myCoolForm.addEventListener('submit', function handleCustomFormSubmit(event) {
    event.preventDefault();
    
    // Create an object that we'll add key/value pairs to
    var formData = {};

    // Find any element inside the form that has a data-column-key attribute
    var $inputs = $myCoolForm.querySelectorAll('[data-column-key]');

    // Loop through each of those elements to put together our formData object
    for (var i = 0; i < $inputs.length; i++) {
      var $input = $inputs[i];

      // Extract the key and value from each element
      var key = $input.getAttribute('data-column-key');
      var value = $input.value;

      // Add the key/value pair to formData
      formData[key] = value;
    }

    // Finally, mutate the global customer data object
    CF.customer.set(formData);
  });
});

Submit the data

The data extraction code has been omitted for simplicity:

CF.customerReady(function() {
  var $myCoolForm = document.querySelector('#my-cool-form');

  $myCoolForm.addEventListener('submit', function handleCustomFormSubmit(event) {
    // event.preventDefault(), get the form data, ... 

    CF.customer.set(formData);

    var customerSaveOptions = { // Always login and redirect to the account page after saving
      redirect: true,
      login: true,
      redirectUrl: '/account',
    }

    CF.customer.save(customerSaveOptions)
      .then(function() {
        // Customer has been created! They will automatically be logged in and redirected to /account
      })
      .catch(function(error) {
        alert('Failed to save customer! :(');
        console.error(error);
      });
  });
});

Fill forms with existing customer data

You’ve built a custom form that allows customers to create accounts. However, what if they come back to fix a typo? The form will be empty for them, even though they’ve already submitted it. All you need to do is fill in the value of each input with the data stored in Shopify’s Customer object.

Here’s an example of how to do it for a first_name field.

Before:

<input
  type="text"
  name="customer[first_name]"
  data-column-key="first_name"
>

After:

<input
  type="text"
  name="customer[first_name]"
  data-column-key="first_name"
  value="{{ customer.first_name }}"
>

Now, for a custom field. Let’s say you have a field for the customer’s favorite_color. Since this data isn’t part of Shopify’s Customer object, you’ll need to read from customer.metafields.

Before:

<input
  type="text"
  name="customer[favorite_color]"
  data-column-key="favorite_color"
>

After:

<input
  type="text"
  name="customer[favorite_color]"
  data-column-key="favorite_color"
  value="{{ customer.metafields.customer_fields.data.favorite_color }}"
>

No JavaScript needed here! Make sure you do this for every field in your custom form.

End result

Here’s a full snippet that includes all of the markup and scripting for a custom form:

It is assumed that you have {% render 'customer-fields', customer_api: true, version: '<Version number>' %} in your theme’s layout/theme.liquid

{% form 'create_customer', id: 'my-cool-form' %}
  <div>
    <label>First name</label>
    <input
      type="text"
      name="customer[first_name]"
      data-column-key="first_name"
      value="{{ customer.first_name }}"
    >
  </div>

  <div>
    <label>Last name</label>
    <input
      type="text"
      name="customer[last_name]"
      data-column-key="last_name"
      value="{{ customer.last_name }}"
    >
  </div>

  <div>
    <label>Email</label>
    <input
      type="text"
      name="customer[email]"
      data-column-key="email"
      value="{{ customer.email }}"
    >
  </div>

  {% unless customer %}
    <div>
      <label>Password</label>
      <input
        type="text"
        name="customer[password]"
        data-column-key="password"
        value="{{ customer.password }}"
      >
    </div>
  {% endunless %}

  <div>
    <label>Favorite color</label>
    <input
      type="text"
      name="customer[favorite_color]"
      data-column-key="favorite_color"
      value="{{ customer.metafields.customer_fields.data.favorite_color }}"
    >
  </div>

  <button type="submit">Create account</button>
{% endform %}

<script>
  CF.customerReady(function() {
    var $myCoolForm = document.querySelector('#my-cool-form');

    $myCoolForm.addEventListener('submit', function handleCustomFormSubmit(event) {
      event.preventDefault();
      
      // Create an object that we'll add key/value pairs to
      var formData = {};

      // Find any element inside the form that has a data-column-key attribute
      var $inputs = $myCoolForm.querySelectorAll('[data-column-key]');

      // Loop through each of those elements to put together our formData object
      for (var i = 0; i < $inputs.length; i++) {
        var $input = $inputs[i];

        // Extract the key and value from each element
        var key = $input.getAttribute('data-column-key');
        var value = $input.value;

        // Add the key/value pair to formData
        formData[key] = value;
      }

      // Mutate the global customer data object
      CF.customer.set(formData);

      var customerSaveOptions = { // Always login and redirect to the account page after saving
        redirect: true,
        login: true,
        redirectUrl: '/account',
      }

      CF.customer.save(customerSaveOptions)
        .then(function() {
          // Customer has been created! They will automatically be logged in and redirected to /account
        })
        .catch(function(error) {
          // Failed to save customer! :(
          console.error(error);
        });
    });
  });
</script>