Managing user addresses in Shopify

In Shopify, you can allow your customers to create logins and see stuff like past purchases and edit their details. There's a handful of templates expressly for this purpose inside the /templates/customers directory. But if you're using Shopify Theme Kit it's likely that these files are blank by default.

Shopify have some documentation on what's supposed to be in the addresses.liquid file, but it's ... lacking in detail. There's also some undocumented tricks in Shopify's (abandoned?) Starter Theme, not all of which I could get to work. This article will explain what customer functionality I have got into a usable state.

Minimum viable product

Here's what I'd want to be able to do, as a customer:

  • View a list of my addresses
  • Edit old addresses (including selecting one address as the default)
  • Add a new address
  • Delete even older addresses

View a list of my addresses

Generating a list of a customer's addresses is as straightforward as initiating a for address in customer.addresses loop. The address object allows us access to the rest of the data we need.

All of this code is available to download at Finetune Partners Mannequin repo, on Github.

View addresses.liquid on Github

Edit old addresses

When it comes to editing current addresses, Shopify even have this nice table for you to refer to:

Input Type Required name attribute
First name text address[first_name]
Last name text address[last_name]
Company text address[company]
Address 1 text address[address1]
Address 2 text address[address2]
City text address[city]
Country select address[country]
Province select address[province]
ZIP/Postal Code text address[zip]
Phone Number tel address[phone]

We can generate an edit address form within the for loop we started above.

This all seems pretty straightforward, until you get to Country and Province select boxes. How do you populate them with the right values?

The country select box uses {{ country_option_tags }} to generate a generic list of countries. This list does not automagically select the correct country for the current address. What about province? Surely that's tied to the country, right? What if the user updates their country and the province needs to reflect this?

Shopify suggests installing a whole JavaScript library to handle this, which seems to do a whole lot more than just populating a select box. Hmm.

There is, however, an easier way.

I couldn't be bothered reading the documentation, so I re-wrote the code from scratch

Inspect the output of those {{ country_option_tags }} and you'll see it comes with a whole heap of extras. Each option tag has a data-provinces attribute, which mostly holds an empty object ([]), but sometimes holds a lot more.

We need the following to happen:

  1. Loop through each of the user's addresses, then build an array of values for Country and Province (this is done by Liquid, before the page loads)
  2. Loop through each option tag within each country select box and add a selected="selected to the option which matches the current address (or remove the attribute, if it doesn't)
  3. Check each country select box, to see if it has any province data hidden on the currently selected option
  4. Update the corresponding province select box with this data
  5. Loop through each province select box and ensure that the correct province is selected by default (or disable the select box, if there are no provinces)

Building relationships

Shopify have already established that the countries select box must have a name attribute of address[country] and the provinces select box should have a name attribute of address[province]. But there might be potentially multiple addresses on the page. How do we establish a relationship between these two select boxes?

Notice that the country select box has a data-provinceid="province{{ forloop.index }}" attribute. This will generate a string which matches the id of the following select box, establishing a means for the JavaScript to jump from one select box to the other (also, this imposes no other markup dependencies).

Using Shopify's attributes

Shopify's province data takes the form of two nested Array-like structures. The inner of these only has two values, which match the value of the option tag and the string of text which appears between the opening and closing option tags.

These two strings are mostly identical, but not always. The script pulls them out by their index position.

Adding a new address

Before the JavaScript portion of addresses.liquid is the form which allows your customers to add a new address. This markup could be moved elsewhere in the file if you prefer, or even to a different template. The only restriction is that it should not be nested inside another form tag.

Delete an address

Customers can delete addresses using a form with a hidden input:

<input type="hidden" name="_method" value="delete">

As this form element would need to be a sibling of an existing edit form, this might impact the layout slightly (but you could argue that the delete address button should be corralled off to the side, in case the user hits it accidentally).

It looks like it should be possible to add:

<input type="submit" name="_method" value="delete">

... to the edit address form, but this does not delete the selected address. I suspect this is due to the form_type hidden input which is added automatically by Shopify, when you use a {% form 'customer_address', address %} liquid tag.

Note that in Shopify's documentation of this they add a JavaScript confirm box, before allowing the form to submit.

There's nothing to stop you from including this markup within any of the other places in the liquid file where the same for loop takes place.