Addresses
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 Commerce → Store Settings → Store → Store 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:
- The ability to use estimated addresses to calculate shipping and tax costs with minimal data entry before checkout.
- Multiple ways of updating cart addresses to avoid data re-entry.
- Methods for working with the store’s countries & states provided by Craft’s supporting address repository.
- A separate endpoint that can be used to allow customers to manage their saved addresses.
# Store Address
The store address (set via Commerce → Store Settings → Store) is available via the Store service (opens new window):
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.
You don’t need to add your own logic to handle shippingAddressSameAsBilling
or billingAddressSameAsShipping
; Commerce will return the correct address taking those options into account.
# Updating Cart Addresses
Any post to the commerce/cart/update-cart
action can include parameters for modifying shipping and billing address information in two formats:
- A
shippingAddress
and/orbillingAddress
array with details to be added (guests and logged-in users) - A
shippingAddressId
and/orbillingAddressId
parameter for choosing an existing address by ID (logged-in users only)
# 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
, for example, and the order’s billing address should be identical, you can simply pass a non-empty value under billingAddressSameAsShipping
rather than supplying the same billingAddress
or billingAddressId
.
If you provide shippingAddress
fields and shippingAddressId
, the latter takes precedence.
Commerce doesn’t require an order to have a shipping address, but providing one is important for the tax and shipping engine to calculate more accurate options and costs.
# 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">
{# ... #}
{# 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
orbillingAddressId
, the order keeps track of where the address came from viasourceShippingAddressId
andsourceBillingAddressId
properties, but clones the actual address element. This means thatshippingAddressId
andsourceShippingAddressId
will never be the same! - Addresses provided by sending individual fields under the
shippingAddress[...]
andbillingAddress[...]
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
orsourceBillingAddressId
.
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.
# Auto-fill from Address Book
You can allow your customers to populate order 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 customers to manage these addresses, or introduce logic in the 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>
Any field(s) updated on an order address filled 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—not keep them synchronized.
# 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 the cart. Refer to the main addresses documentation for more information and examples.
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.