Addresses

You are viewing documentation for an unreleased version of Craft Commerce. Please be aware that some pages, screenshots, and technical reference may still reflect older versions.

Addresses are now a native part of Craft! We recommend reviewing the main documentation on addresses before digging in on Commerce-specifics.

Commerce manages shipping and billing information using Craft’s craft\elements\Address (opens new window) element type.

In the control panel, you’ll encounter addresses within the context of orders and users. A Store Location address may also be entered at CommerceStore SettingsStoreStore Location.

Customer’s addresses are managed from their user account, if you’ve added the native Addresses field to Users’ field layout. Commerce also inserts a Commerce Settings field into the address field layout) with primary shipping and billing controls.

# How Addresses are Used

Your customers will work with addresses directly, or via the cart.

Your primary source for information about working with Addresses is the main Craft documentation!

Every order may have a shipping and billing address, and customers with accounts can save and re-use addresses when placing new orders. How you collect and validate addresses on the front end is up to you—but Craft and Commerce provide tools that help streamline address management:

# Store Address

The store address (set via CommerceStore SettingsStore) is available via the Store service (opens new window):

{% set storeAddress = craft.commerce
  .getStore()
  .getStore()
  .getLocationAddress() %}

That getStore().getStore() is not a typo! We’re getting the craft\commerce\services\Store (opens new window) service with the first method and getting the craft\commerce\models\Store (opens new window) model with the second.

# Cart Addresses

# Fetching Cart Addresses

Once you have a cart object, you can access the attached addresses via cart.shippingAddress and cart.billingAddress:

{% if cart.shippingAddress %}
  {{ cart.shippingAddress.firstName }}
  {# ... #}
{% endif %}

{% if cart.billingAddress %}
  {{ cart.billingAddress.firstName }}
  {# ... #}
{% endif %}

It’s important to code defensively, here! If the customer hasn’t set an address yet, you’ll get back null—otherwise, it’ll be an Address (opens new window) object.

# Updating Cart Addresses

Any time you submit data to the commerce/cart/update-cart action, you can include parameters for modifying shipping and billing addresses:

  1. Send shippingAddress and/or billingAddress arrays with new values (guests and logged-in users);
  2. shippingAddressId and/or billingAddressId parameters for choosing an existing address by ID (logged-in users only);
  3. A shippingAddressSameAsBilling or billingAddressSameAsShipping parameter to copy address information in one direction or another;

The full list of supported parameters can be found in the controller actions documentation.

# Synchronizing Shipping and Billing Addresses

With either approach, you can leverage the shippingAddressSameAsBilling or billingAddressSameAsShipping parameters to synchronize addresses and avoid having to send the same information twice.

If you provide a shippingAddress or shippingAddressId and the order’s billing address should be identical, you can simply send a non-empty billingAddressSameAsShipping param rather than supplying the same billingAddress or billingAddressId.

If you provide shippingAddress fields and a shippingAddressId, the latter takes precedence.

Customize what address information is required at checkout with the requireBillingAddressAtCheckout and requireShippingAddressAtCheckout config settings.

The tax and shipping engines require address information to generate accurate options and costs.

Use the cart.hasMatchingAddresses() method to confirm to customers that their addresses match. Read more about how Commerce handles changes to addresses in the address book section.

# Address Fields

The specific properties and fields supported when updating an order’s shippingAddress or billingAddress in this way are determined by the Address (opens new window) element, regional differences based on the provided countryCode, and any custom fields assigned to it.

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {# Native `fullName` field: #}
  {{ input('text', 'shippingAddress[fullName]', cart.shippingAddress.fullName) }}

  {# Custom fields (note the `[fields]` prefix): #}
  {{ input('checkbox', 'shippingAddress[fields][isResidentialAddress]') }}
</form>

For more information on address management, continue reading—or see the main Craft documentation for Address elements.

# Address Ownership

Logged-in users can directly manage their addresses via the front-end, and pick from them during checkout. However, addresses are only ever “owned” by one element—let’s look at some examples of how Commerce handles this:

  • When an address is selected by updating a cart with a shippingAddressId or billingAddressId, the order keeps track of where the address came from via sourceShippingAddressId and sourceBillingAddressId properties, but clones the actual address element. This means that shippingAddressId and sourceShippingAddressId will never be the same—even if they were filled from the same source address!
  • Addresses provided by sending individual fields under the shippingAddress[...] and billingAddress[...] keys are created and owned by the order.
  • Similarly, sending individual field values for an order’s shipping or billing address (regardless of how it was originally populated) will only update the order address, and breaks any association to the user’s address book via sourceShippingAddressId or sourceBillingAddressId.

If you want to make it clear that your customer has selected a preexisting address, compare order.sourceShippingAddressId or order.sourceBillingAddressId with the IDs of the addresses in their address book. We have an example of this, below.

# Cart Address Examples

Let’s look at some approaches to updating order addresses during checkout.

With either of these strategies, you can apply changes to shipping and billing addresses, simultaneously. Read more about synchronizing addresses.

# Submit New Addresses

Customers—especially guests—will probably need to enter an address at checkout. To set address information directly on the order, pass individual properties under a shippingAddress or billingAddress key, depending on what you want to update:

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {{ input('text', 'shippingAddress[fullName]', shippingAddress.fullName) }}

  <select name="shippingAddress[countryCode]">
    {% for code, name in craft.commerce.getStore().getStore().getCountriesList() %}
      {{ tag('option', {
        value: code,
        text: name,
        selected: code == shippingAddress.countryCode,
      }) }}
    {% endfor %}
  </select>

  {# ... #}

  <button>Save Shipping Address</button>
</form>

If your request also includes a non-empty shippingAddressId or `billingAddressId param, the corresponding individual address fields are ignored and Commerce attempts to fill from an existing address.

# Save Addresses when Completing an Order

Your customers can save the billing and/or shipping addresses on their cart to their address book when they check out. These options are stored as saveBillingAddressOnOrderComplete and saveShippingAddressOnOrderComplete on the cart or craft\commerce\elements\Order (opens new window) object. You may send corresponding values any time you update the customer’s cart:

{% set cart = craft.commerce.carts.cart %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {# ... #}

  {% if currentUser %}
    {% if cart.billingAddressId and not cart.sourceBillingAddressId %} 
      {{ input('checkbox', 'saveBillingAddressOnOrderComplete', 1, { checked: cart.saveBillingAddressOnOrderComplete }) }}
    {% endif %}

    {% if cart.shippingAddressId and not cart.sourceShippingAddressId %} 
      {{ input('checkbox', 'saveShippingAddressOnOrderComplete', 1, { checked: cart.saveShippingAddressOnOrderComplete }) }}
    {% endif %}
  {% endif %}

  <button>Save Cart</button>
</form>

This example guards against saving an existing address, indicated by the presence of a cart.sourceBillingAddressId or cart.sourceShippingAddressId.

Both properties can be set at once with the saveAddressesOnOrderComplete parameter, but you are still responsible for deriving UI state from the underlying address-specific properties:

{% set cart = craft.commerce.carts.cart %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {% if currentUser and ((cart.billingAddressId and not cart.sourceBillingAddressId) or (cart.shippingAddressId and not cart.sourceShippingAddressId)) %}
    {{ input('checkbox', 'saveAddressesOnOrderComplete', 1, {checked: cart.saveBillingAddressOnOrderComplete and cart.saveShippingAddressOnOrderComplete}) }}
  {% endif %}

  {# ... #}

  <button>Save Cart</button>
</form>

The saveAddress* properties are only applicable to customers who created addresses directly on the cart. Setting these options to true if a registered customer selected an address from their address book has no effect.

Guests’ addresses are automatically saved to their customer account when registering at checkout.

# Auto-fill from Address Book

You can let your customers populate their cart addresses with a previously-saved one by sending a shippingAddressId and/or billingAddressId param when updating the cart.

{% set cart = craft.commerce.carts.cart %}
{% set customerAddresses = currentUser ? currentUser.addresses : [] %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {# Display saved addresses as options if we have them #}
  {% if customerAddresses | length %}
    <div class="shipping-address">
      {% for address in customerAddresses %}
        <label>
          {{ input('radio', 'shippingAddressId', address.id, {
            checked: cart.sourceShippingAddressId == address.id,
          }) }}
          {{ address|address }}
        </label>
      {% endfor %}
    </div>

    {# ... same process for `billingAddressId` ... #}
  {% else %}
    {# No existing addresses! See examples above to learn about sending a new address. #}
  {% endif %}

  <button>Save Addresses</button>
</form>

You may need to create custom routes to allow logged-in customers to manage these addresses, or introduce logic in the template or browser to hide and show new address forms based on the type(s) of addresses you need.

# Update an Existing Address

An address on the cart may be updated in-place by passing individual address properties.

{% set cart = craft.commerce.carts.getCart() %}
{% set address = cart.shippingAddress %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}

  {{ input('text', 'shippingAddress[fullName]', address.fullName) }}
  {{ input('text', 'shippingAddress[addressLine1]', address.addressLine1) }}

  {# ... #}

  <button>Save Shipping Info</button>
</form>

You may also send firstName and lastName properties, separately.

Changes to an address in the customer’s address book (via the users/save-address action) are copied to any carts it is attached to.

However, any field(s) updated on an order address that was originally populated from the customer’s address book will not propagate back to the source, and will break the association to it. Sending shippingAddressId and billingAddressId are only intended to populate an order address with existing information, and to track where it originally came from—not bind them in both directions.

# Estimate Cart Addresses

It’s common to provide a shipping or tax cost estimate before a customer has entered full address details. To help with this, the cart can use “estimated” shipping and billing addresses for calculations, before complete addresses are available.

Estimated addresses are craft\elements\Address (opens new window) elements, just like shipping and billing addresses.

# Adding a Shipping Estimate Address to the Cart

You can add or update an estimated addresses on the order with the same commerce/cart/update-cart form action.

In this example we’ll first check for existing estimate addresses with the estimatedShippingAddressId and estimatedBillingAddressId attributes on the cart (opens new window) object, displaying a form to collect only the shipping country, state, and postal code if we don’t already have them:

{% set cart = craft.commerce.carts.cart %}
{% set store = craft.commerce.getStore().getStore() %}

<form method="post">
  {{ csrfInput() }}
  {{ actionInput('commerce/cart/update-cart') }}
  {{ hiddenInput('estimatedBillingAddressSameAsShipping', '1') }}

  {% if not cart.estimatedShippingAddressId %}
    {# Display country selection dropdown #}
    <select name="estimatedShippingAddress[countryCode]">
      {% for code, option in store.getCountriesList() %}
        <option value="{{ code }}">{{ option }}</option>
      {% endfor %}
    </select>

    {# Display state selection dropdown #}
    <select name="estimatedShippingAddress[administrativeArea]">
      {% for states in store.getAdministrativeAreasListByCountryCode() %}
        {% for key, option in states %}
          <option value="{{ key }}">{{ option }}</option>
        {% endfor %}
      {% endfor %}
    </select>

    {# Display a postal code input #}
    <input type="text" name="estimatedShippingAddress[postalCode]" value="">
  {% endif %}

  {% if cart.availableShippingMethodOptions|length and cart.estimatedShippingAddressId %}
    {# Display name+price selection for each available shipping method #}
    {% for handle, method in cart.availableShippingMethodOptions %}
      {% set price = method.priceForOrder(cart)|commerceCurrency(cart.currency) %}
      <label>
        <input type="radio"
          name="shippingMethodHandle"
          value="{{ handle }}"
          {% if handle == cart.shippingMethodHandle %}checked{% endif %}
        />
        {{ method.name }} - {{ price }}
      </label>
    {% endfor %}
  {% endif %}

  <button>Submit</button>
</form>

Tax adjusters (opens new window) and shipping adjusters (opens new window) will include an isEstimated attribute when their calculations were based on estimated address data.

A full example of this can be seen in the example templates on the cart page.

# Address Book

When logged in, your customers can manage their addresses independently of a cart. Refer to the main addresses documentation and to this page’s section on auto-filling addresses for more information and examples.

# Primary Billing + Shipping Addresses

In addition to the natively supported params, Commerce will look for isPrimaryShipping and isPrimaryBilling. These values determine which addresses get attached to a fresh cart when the autoSetNewCartAddresses option is enabled.

To add support for setting default addresses, add this code to the new address and/or existing addresses forms:

{# Make primary shipping address? #}
{{ hiddenInput('isPrimaryShipping', 0) }}
<label>
  {{ input('checkbox', 'isPrimaryShipping', 1, {
    checked: address.isPrimaryShipping
  }) }}
  Set as primary shipping address
</label>

{{ hiddenInput('isPrimaryBilling', 0) }}
<label>
  {{ input('checkbox', 'isPrimaryBilling', 1, {
    checked: address.isPrimaryBilling
  }) }}
  Set as primary billing address
</label>

checkbox inputs only send a value when checked. In order to support un-setting a primary billing or shipping address, you must include the hidden input before the visible checkbox, in the DOM. This ensures that the falsy 0 value is sent when the checkbox is unchecked, differentiating it from simply not sending a value at all (omitting isPrimaryShipping or isPrimaryBilling entirely makes no changes to the user’s current settings).

Similarly, you can send makePrimaryBillingAddress or makePrimaryShippingAddress params along with any cart/update-cart request to set an address attached to the cart as the customer’s primary billing or shipping address. Only addresses that retain their sourceBillingAddressId or sourceShippingAddressId can be configured this way.

# Address Synchronization