Addresses
Addresses are a type of element you’ll most commonly encounter in conjunction with Users. Querying addresses and working with their field data is nearly identical to the experience working with any other element type.
For sites supporting public registration (like a storefront built on Craft Commerce) users can manage their own address book.
Plugins are also able to use addresses to store their own location data.
# Setup
Address elements share a single field layout, which is managed in the control panel via
- Settings
- Addresses
# Native and Custom Fields
The address field layout has additional native (but optional) fields for a handful of useful attributes. Addresses—just like other element types—support custom fields for anything else you might need to store.
For compatibility and localization, core address components (aside from the Country Code) can’t be separated from one another in the field layout.
# Config Options
You may set a default country for new addresses via the defaultCountryCode setting.
# Querying Addresses
You can fetch addresses in your templates or PHP code using an AddressQuery (opens new window).
# Example
Let’s output a list of the logged-in user’s addresses:
- Create an address query with
craft.addresses()
. - Restrict the query to addresses owned by the current User, with the
owner
parameter. - Fetch the addresses with
.all()
. - Loop through the addresses using a
{% for %}
tag (opens new window). - Output preformatted address details with the
|address
filter.
{% requireLogin %}
{% set addresses = craft.addresses()
.owner(currentUser)
.all() %}
{% for addr in addresses %}
<address>{{ addr|address }}</address>
{% endfor %}
We’ll expand on this example in the Managing Addresses section.
Protect your users’ personal information by carefully auditing queries and displaying private addresses only on pages that require login.
# Parameters
Address queries support the following parameters:
Param | Description |
---|---|
addressLine1 | Narrows the query results based on the first address line the addresses have. |
addressLine2 | Narrows the query results based on the second address line the addresses have. |
addressLine3 | Narrows the query results based on the third address line the addresses have. |
administrativeArea | Narrows the query results based on the administrative areas the addresses belongs to. |
afterPopulate | Performs any post-population processing on elements. |
andNotRelatedTo | Narrows the query results to only addresses that are not related to certain other elements. |
andRelatedTo | Narrows the query results to only addresses that are related to certain other elements. |
asArray | Causes the query to return matching addresses as arrays of data, rather than Address (opens new window) objects. |
cache | Enables query cache for this Query. |
clearCachedResult | Clears the cached result (opens new window). |
countryCode | Narrows the query results based on the country the addresses belong to. |
dateCreated | Narrows the query results based on the addresses’ creation dates. |
dateUpdated | Narrows the query results based on the addresses’ last-updated dates. |
dependentLocality | Narrows the query results based on the dependent locality the addresses belong to. |
eagerly | Causes the query to be used to eager-load results for the query’s source element and any other elements in its collection. |
firstName | Narrows the query results based on the first name the addresses have. |
fixedOrder | Causes the query results to be returned in the order specified by id. |
fullName | Narrows the query results based on the full name the addresses have. |
id | Narrows the query results based on the addresses’ IDs. |
ignorePlaceholders | Causes the query to return matching addresses as they are stored in the database, ignoring matching placeholder elements that were set by craft\services\Elements::setPlaceholderElement() (opens new window). |
inBulkOp | Narrows the query results to only addresses that were involved in a bulk element operation. |
inReverse | Causes the query results to be returned in reverse order. |
language | Determines which site(s) the addresses should be queried in, based on their language. |
lastName | Narrows the query results based on the last name the addresses have. |
limit | Determines the number of addresses that should be returned. |
locality | Narrows the query results based on the locality the addresses belong to. |
notRelatedTo | Narrows the query results to only addresses that are not related to certain other elements. |
offset | Determines how many addresses should be skipped in the results. |
orderBy | Determines the order that the addresses should be returned in. (If empty, defaults to dateCreated DESC, elements.id .) |
organization | Narrows the query results based on the organization the addresses have. |
organizationTaxId | Narrows the query results based on the tax ID the addresses have. |
postalCode | Narrows the query results based on the postal code the addresses belong to. |
preferSites | If unique() (opens new window) is set, this determines which site should be selected when querying multi-site elements. |
prepForEagerLoading | Prepares the query for lazy eager loading. |
prepareSubquery | Prepares the element query and returns its subquery (which determines what elements will be returned). |
relatedTo | Narrows the query results to only addresses that are related to certain other elements. |
render | Executes the query and renders the resulting elements using their partial templates. |
search | Narrows the query results to only addresses that match a search query. |
siteSettingsId | Narrows the query results based on the addresses’ IDs in the elements_sites table. |
sortingCode | Narrows the query results based on the sorting code the addresses have. |
trashed | Narrows the query results to only addresses that have been soft-deleted. |
uid | Narrows the query results based on the addresses’ UIDs. |
wasCountEagerLoaded | Returns whether the query result count was already eager loaded by the query's source element. |
wasEagerLoaded | Returns whether the query results were already eager loaded by the query's source element. |
with | Causes the query to return matching addresses eager-loaded with related elements. |
withCustomFields | Sets whether custom fields should be factored into the query. |
# addressLine1
Narrows the query results based on the first address line the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'23 Craft st' | with a addressLine1 of 23 Craft st . |
'*23*' | with a addressLine1 containing 23 . |
'23*' | with a addressLine1 beginning with 23 . |
{# Fetch addresses at 23 Craft st #}
{% set addresses = craft.addresses()
.addressLine1('23 Craft st')
.all() %}
# addressLine2
Narrows the query results based on the second address line the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'Apt 5B' | with an addressLine2 of Apt 5B . |
'*5B*' | with an addressLine2 containing 5B . |
'5B*' | with an addressLine2 beginning with 5B . |
{# Fetch addresses at Apt 5B #}
{% set addresses = craft.addresses()
.addressLine2('Apt 5B')
.all() %}
# addressLine3
Narrows the query results based on the third address line the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'Suite 212' | with an addressLine3 of Suite 212 . |
'*Suite*' | with an addressLine3 containing Suite . |
'Suite*' | with an addressLine3 beginning with Suite . |
{# Fetch addresses at Suite 212 #}
{% set addresses = craft.addresses()
.addressLine3('Suite 212')
.all() %}
# administrativeArea
Narrows the query results based on the administrative areas the addresses belongs to.
Possible values include:
Value | Fetches addresses… |
---|---|
'WA' | with a administrative area of WA . |
'not WA' | not in a administrative area of WA . |
['WA', 'SA'] | in a administrative area of WA or SA . |
['not', 'WA', 'SA'] | not in a administrative area of WA or SA . |
{# Fetch addresses in Western Australia #}
{% set addresses = craft.addresses()
.administrativeArea('WA')
.all() %}
# afterPopulate
Performs any post-population processing on elements.
# andNotRelatedTo
Narrows the query results to only addresses that are not related to certain other elements.
See Relations (opens new window) for a full explanation of how to work with this parameter.
{# Fetch all addresses that are related to myCategoryA and not myCategoryB #}
{% set addresses = craft.addresses()
.relatedTo(myCategoryA)
.andNotRelatedTo(myCategoryB)
.all() %}
# andRelatedTo
Narrows the query results to only addresses that are related to certain other elements.
See Relations (opens new window) for a full explanation of how to work with this parameter.
{# Fetch all addresses that are related to myCategoryA and myCategoryB #}
{% set addresses = craft.addresses()
.relatedTo(myCategoryA)
.andRelatedTo(myCategoryB)
.all() %}
# asArray
Causes the query to return matching addresses as arrays of data, rather than Address (opens new window) objects.
# cache
Enables query cache for this Query.
# clearCachedResult
Clears the cached result (opens new window).
# countryCode
Narrows the query results based on the country the addresses belong to.
Possible values include:
Value | Fetches addresses… |
---|---|
'AU' | with a countryCode of AU . |
'not US' | not in a countryCode of US . |
['AU', 'US'] | in a countryCode of AU or US . |
['not', 'AU', 'US'] | not in a countryCode of AU or US . |
{# Fetch Australian addresses #}
{% set addresses = craft.addresses()
.countryCode('AU')
.all() %}
# dateCreated
Narrows the query results based on the addresses’ creation dates.
Possible values include:
Value | Fetches addresses… |
---|---|
'>= 2018-04-01' | that were created on or after 2018-04-01. |
'< 2018-05-01' | that were created before 2018-05-01. |
['and', '>= 2018-04-04', '< 2018-05-01'] | that were created between 2018-04-01 and 2018-05-01. |
now /today /tomorrow /yesterday | that were created at midnight of the specified relative date. |
{# Fetch addresses created last month #}
{% set start = date('first day of last month')|atom %}
{% set end = date('first day of this month')|atom %}
{% set addresses = craft.addresses()
.dateCreated(['and', ">= #{start}", "< #{end}"])
.all() %}
# dateUpdated
Narrows the query results based on the addresses’ last-updated dates.
Possible values include:
Value | Fetches addresses… |
---|---|
'>= 2018-04-01' | that were updated on or after 2018-04-01. |
'< 2018-05-01' | that were updated before 2018-05-01. |
['and', '>= 2018-04-04', '< 2018-05-01'] | that were updated between 2018-04-01 and 2018-05-01. |
now /today /tomorrow /yesterday | that were updated at midnight of the specified relative date. |
{# Fetch addresses updated in the last week #}
{% set lastWeek = date('1 week ago')|atom %}
{% set addresses = craft.addresses()
.dateUpdated(">= #{lastWeek}")
.all() %}
# dependentLocality
Narrows the query results based on the dependent locality the addresses belong to.
Possible values include:
Value | Fetches addresses… |
---|---|
'Darlington' | with a dependentLocality of Darlington . |
'*Darling*' | with a dependentLocality containing Darling . |
'Dar*' | with a dependentLocality beginning with Dar . |
{# Fetch addresses in Darlington #}
{% set addresses = craft.addresses()
.dependentLocality('Darlington')
.all() %}
# eagerly
Causes the query to be used to eager-load results for the query’s source element and any other elements in its collection.
# firstName
Narrows the query results based on the first name the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'John' | with a firstName of John . |
'*Joh*' | with a firstName containing Joh . |
'Joh*' | with a firstName beginning with Joh . |
{# Fetch addresses with first name John #}
{% set addresses = craft.addresses()
.firstName('John')
.all() %}
# fixedOrder
Causes the query results to be returned in the order specified by id.
If no IDs were passed to id, setting this to true
will result in an empty result set.
{# Fetch addresses in a specific order #}
{% set addresses = craft.addresses()
.id([1, 2, 3, 4, 5])
.fixedOrder()
.all() %}
# fullName
Narrows the query results based on the full name the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'John Doe' | with a fullName of John Doe . |
'*Doe*' | with a fullName containing Doe . |
'John*' | with a fullName beginning with John . |
{# Fetch addresses for John Doe #}
{% set addresses = craft.addresses()
.fullName('John Doe')
.all() %}
# id
Narrows the query results based on the addresses’ IDs.
Possible values include:
Value | Fetches addresses… |
---|---|
1 | with an ID of 1. |
'not 1' | not with an ID of 1. |
[1, 2] | with an ID of 1 or 2. |
['not', 1, 2] | not with an ID of 1 or 2. |
This can be combined with fixedOrder if you want the results to be returned in a specific order.
# ignorePlaceholders
Causes the query to return matching addresses as they are stored in the database, ignoring matching placeholder elements that were set by craft\services\Elements::setPlaceholderElement() (opens new window).
# inBulkOp
Narrows the query results to only addresses that were involved in a bulk element operation.
# inReverse
Causes the query results to be returned in reverse order.
{# Fetch addresses in reverse #}
{% set addresses = craft.addresses()
.inReverse()
.all() %}
# language
Determines which site(s) the addresses should be queried in, based on their language.
Possible values include:
Value | Fetches addresses… |
---|---|
'en' | from sites with a language of en . |
['en-GB', 'en-US'] | from sites with a language of en-GB or en-US . |
['not', 'en-GB', 'en-US'] | not in sites with a language of en-GB or en-US . |
Elements that belong to multiple sites will be returned multiple times by default. If you only want unique elements to be returned, use unique() (opens new window) in conjunction with this.
{# Fetch addresses from English sites #}
{% set addresses = craft.addresses()
.language('en')
.all() %}
# lastName
Narrows the query results based on the last name the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'Doe' | with a lastName of Doe . |
'*Do*' | with a lastName containing Do . |
'Do*' | with a lastName beginning with Do . |
{# Fetch addresses with last name Doe #}
{% set addresses = craft.addresses()
.lastName('Doe')
.all() %}
# limit
Determines the number of addresses that should be returned.
# locality
Narrows the query results based on the locality the addresses belong to.
Possible values include:
Value | Fetches addresses… |
---|---|
'Perth' | with a locality of Perth . |
'*Perth*' | with a locality containing Perth . |
'Ner*' | with a locality beginning with Per . |
{# Fetch addresses in Perth #}
{% set addresses = craft.addresses()
.locality('Perth')
.all() %}
# notRelatedTo
Narrows the query results to only addresses that are not related to certain other elements.
See Relations (opens new window) for a full explanation of how to work with this parameter.
{# Fetch all addresses that are related to myEntry #}
{% set addresses = craft.addresses()
.notRelatedTo(myEntry)
.all() %}
# offset
Determines how many addresses should be skipped in the results.
{# Fetch all addresses except for the first 3 #}
{% set addresses = craft.addresses()
.offset(3)
.all() %}
# orderBy
Determines the order that the addresses should be returned in. (If empty, defaults to dateCreated DESC, elements.id
.)
{# Fetch all addresses in order of date created #}
{% set addresses = craft.addresses()
.orderBy('dateCreated ASC')
.all() %}
# organization
Narrows the query results based on the organization the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'Pixel & Tonic' | with an organization of Pixel & Tonic . |
'*Pixel*' | with an organization containing Pixel . |
'Pixel*' | with an organization beginning with Pixel . |
{# Fetch addresses for Pixel & Tonic #}
{% set addresses = craft.addresses()
.organization('Pixel & Tonic')
.all() %}
# organizationTaxId
Narrows the query results based on the tax ID the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'123-456-789' | with an organizationTaxId of 123-456-789 . |
'*456*' | with an organizationTaxId containing 456 . |
'123*' | with an organizationTaxId beginning with 123 . |
{# Fetch addresses with tax ID 123-456-789 #}
{% set addresses = craft.addresses()
.organizationTaxId('123-456-789')
.all() %}
# postalCode
Narrows the query results based on the postal code the addresses belong to.
Possible values include:
Value | Fetches addresses… |
---|---|
'10001' | with a postalCode of 10001 . |
'*001*' | with a postalCode containing 001 . |
'100*' | with a postalCode beginning with 100 . |
{# Fetch addresses with postal code 10001 #}
{% set addresses = craft.addresses()
.postalCode('10001')
.all() %}
# preferSites
If unique() (opens new window) is set, this determines which site should be selected when querying multi-site elements.
For example, if element “Foo” exists in Site A and Site B, and element “Bar” exists in Site B and Site C,
and this is set to ['c', 'b', 'a']
, then Foo will be returned for Site B, and Bar will be returned
for Site C.
If this isn’t set, then preference goes to the current site.
{# Fetch unique addresses from Site A, or Site B if they don’t exist in Site A #}
{% set addresses = craft.addresses()
.site('*')
.unique()
.preferSites(['a', 'b'])
.all() %}
# prepForEagerLoading
Prepares the query for lazy eager loading.
# prepareSubquery
Prepares the element query and returns its subquery (which determines what elements will be returned).
# relatedTo
Narrows the query results to only addresses that are related to certain other elements.
See Relations (opens new window) for a full explanation of how to work with this parameter.
{# Fetch all addresses that are related to myCategory #}
{% set addresses = craft.addresses()
.relatedTo(myCategory)
.all() %}
# render
Executes the query and renders the resulting elements using their partial templates.
If no partial template exists for an element, its string representation will be output instead.
# search
Narrows the query results to only addresses that match a search query.
See Searching (opens new window) for a full explanation of how to work with this parameter.
{# Get the search query from the 'q' query string param #}
{% set searchQuery = craft.app.request.getQueryParam('q') %}
{# Fetch all addresses that match the search query #}
{% set addresses = craft.addresses()
.search(searchQuery)
.all() %}
# siteSettingsId
Narrows the query results based on the addresses’ IDs in the elements_sites
table.
Possible values include:
Value | Fetches addresses… |
---|---|
1 | with an elements_sites ID of 1. |
'not 1' | not with an elements_sites ID of 1. |
[1, 2] | with an elements_sites ID of 1 or 2. |
['not', 1, 2] | not with an elements_sites ID of 1 or 2. |
{# Fetch the address by its ID in the elements_sites table #}
{% set address = craft.addresses()
.siteSettingsId(1)
.one() %}
# sortingCode
Narrows the query results based on the sorting code the addresses have.
Possible values include:
Value | Fetches addresses… |
---|---|
'ABCD' | with a sortingCode of ABCD . |
'*BC*' | with a sortingCode containing BC . |
'AB*' | with a sortingCode beginning with AB . |
{# Fetch addresses with sorting code ABCD #}
{% set addresses = craft.addresses()
.sortingCode('ABCD')
.all() %}
# trashed
Narrows the query results to only addresses that have been soft-deleted.
# uid
Narrows the query results based on the addresses’ UIDs.
{# Fetch the address by its UID #}
{% set address = craft.addresses()
.uid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
.one() %}
# wasCountEagerLoaded
Returns whether the query result count was already eager loaded by the query's source element.
# wasEagerLoaded
Returns whether the query results were already eager loaded by the query's source element.
# with
Causes the query to return matching addresses eager-loaded with related elements.
See Eager-Loading Elements (opens new window) for a full explanation of how to work with this parameter.
{# Fetch addresses eager-loaded with the "Related" field’s relations #}
{% set addresses = craft.addresses()
.with(['related'])
.all() %}
# withCustomFields
Sets whether custom fields should be factored into the query.
# Address Repository
The commerceguys/addressing (opens new window) library powers planet-friendly address handling and formatting, and its exhaustive repository of global address information is available to all Craft projects. If you need a list of countries, states, or provinces, for example, you can fetch them via Craft’s Addresses (opens new window) service, from Twig templates or PHP:
This returns an array of Country (opens new window) objects, indexed by their two-letter code. You might use this to populate a drop-down menu:
<select name="myCountry">
{% for code, country in countries %}
<option value="{{ code }}">{{ country.name }}</option>
{% endfor %}
</select>
{# Output:
<select name="myCountry">
<option value="AF">Afghanistan</option>
<option value="AX">Åland Islands</option>
...
</select>
#}
Similarly, a repository of subdivisions are available, hierarchically—with up to three levels, depending on how a given country is organized: Administrative Area → Locality → Dependent Locality.
Expanding upon our previous example, we could output a nicely organized list of “administrative areas,” like this:
{% set subdivisionRepo = craft.app.getAddresses().getSubdivisionRepository() %}
{% set countriesWithSubdivisions = countries | filter(c => subdivisionRepo.getAll([c.countryCode]) | length) %}
<select name="administrativeArea">
{% for country in countriesWithSubdivisions %}
{% set administrativeAreas = subdivisionRepo.getAll([country.countryCode]) %}
<optgroup label="{{ country.name }}">
{% for a in administrativeAreas %}
<option value="{{ a.code }}">{{ a.name }}</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
Either repository’s getList()
method is a shortcut that returns only key-value pairs, suitable for our examples—it also accepts an array of “parent” groups (beginning with country code) to narrow the subdivisions.
These designations are deliberately generic, and won’t generally be recognized by users. Check out the labels section for information on how to output localized or context-aware names for each level (i.e. Provinces in Canada or States and Counties in the United States).
You may supplement the subdivision data provided by the upstream repository (opens new window) by listening to the craft\services\Addresses::EVENT_DEFINE_ADDRESS_SUBDIVISIONS (opens new window) event in a plugin or module. Similarly, deeper customization of the required fields (and those fields’ labels) may require modifying the defaults via the EVENT_DEFINE_USED_SUBDIVISION_FIELDS (opens new window) or EVENT_DEFINE_FIELD_LABEL (opens new window) events.
Check out the addressing docs (opens new window) for more details and examples of what’s possible—including translation of place names, postal codes, timezones, and formatting!
# Fields and Formatting
# Field Handles
Individual fields—native and custom—are accessed via their handles, like any other element:
<ul>
<li>Name: {{ myAddress.title }}</li>
<li>Postal Code: {{ myAddress.postalCode }}</li>
<li>Custom Label Color: {{ myAddress.myCustomColorFieldHandle }}</li>
</ul>
# Attribute Labels
The addressing library’s abstracted Administrative Area → Locality → Dependent Locality terminology probably isn’t what you are accustomed to calling those address components in your part of the world—and it’s even less likely you’d want to show those terms to site visitors.
You can use any address element’s attributeLabel()
method to get human-friendly labels for a given locale. Assuming we’re working with a U.S. address…
{{ myAddress.attributeLabel('administrativeArea') }} {# State #}
{{ myAddress.attributeLabel('locality') }} {# City #}
{{ myAddress.attributeLabel('dependentLocality') }} {# Suburb #}
{{ myAddress.attributeLabel('postalCode') }} {# Zip Code #}
Labels use the address’s current countryCode
value for localization.
# |address
Formatter
You can use the |address
filter to output a formatted address with basic HTML:
{{ myAddress|address }}
{# Output:
<p translate="no">
<span class="address-line1">1234 Balboa Towers Circle</span><br>
<span class="locality">Los Angeles</span>, <span class="administrative-area">CA</span> <span class="postal-code">92662</span><br>
<span class="country">United States</span>
</p>
#}
The default formatter includes the following options:
- locale – defaults to
'en'
- html – defaults to
true
; disable withfalse
to maintain line breaks but omit HTML tags - html_tag – defaults to
p
- html_attributes – is an array that defaults to
['translate' => 'no']
{# Swap enclosing paragraph tag for a `div`: #}
{{ myAddress|address({ html_tag: 'div' }) }}
{# Output:
<div translate="no">
<span class="address-line1">1234 Balboa Towers Circle</span><br>
<span class="locality">Los Angeles</span>, <span class="administrative-area">CA</span> <span class="postal-code">92662</span><br>
<span class="country">United States</span>
</div>
#}
{# Turn the entire address into a Google Maps link: #}
{{ myAddress|address({
html_tag: 'a',
html_attributes: {
href: url('https://maps.google.com/maps/search/', {
query_place_id: address.myCustomPlaceIdField,
}),
},
}) }}
{# Output:
<a href="https://maps.google.com/maps/search/?query_place_id=...">
<span class="address-line1">1234 Balboa Towers Circle</span><br>
<span class="locality">Los Angeles</span>, <span class="administrative-area">CA</span> <span class="postal-code">92662</span><br>
<span class="country">United States</span>
</a>
#}
{# Omit all HTML tags: #}
{{ myAddress|address({ html: false }) }}
{# Output:
1234 Balboa Towers Circle
Los Angeles, CA 92662
United States
#}
{# Force output in the Ukrainian (`uk`) locale: #}
{{ myAddress|address({ html: false, locale: 'uk' }) }}
{# Output:
1234 Balboa Towers Circle
Los Angeles, CA 92662
Сполучені Штати
#}
# Country Names
Only the two-letter “country code” is stored on addresses. To display the full country name, use the attached Country
(opens new window) model:
5.3.0+
{% set country = address.country %}
{{ country.name }}
Via the Country
object, you also have access to the following data:
countryCode
— Same ISO 3166-1 alpha-2 code as was stored asaddress.countryCode
.name
— Name of the country, localized for the current site.threeLetterCode
— ISO 3166-1 alpha-3 code for the country.numericalCode
— ISO ISO 3166-1 numeric codes.currencyCode
— ISO 4217 currency code (not the symbol).timezones
— A list of PHP timezone identifiers for the country. This is not necessarily specific to the address!locale
— The locale the country name is localized for. Defaults to the app’s current language, which might be a site’s language or a control panel user’s language preference. If you need to manually localize a country’s name, see below.
In earlier versions of Craft, you must directly retrieve its definition from the address repository:
{% set country = craft.app.getAddresses().getCountryRepository().get(address.countryCode) %}
{{ country.name }}
To get the localized name of a country outside of a formatted address, you must re-fetch it from the address repository:
{% set repo = craft.app.addresses.getCountryRepository() %}
{% set country = repo.get(entry.country, currentSite.locale) %}
{{ country.name }}
{# -> In a site set to use US English (en-US): "United States" #}
{# -> In a site set to use Swiss French (fr-CH): "États-Unis" #}
# Customizing the Formatter
You can also pass your own formatter to the |address
filter. The addressing library includes PostalLabelFormatter (opens new window) to make it easier to print shipping labels. Here, we can specify that formatter and set its additional origin_country
option:
{# Use the postal label formatter #}
{% set addressService = craft.app.getAddresses() %}
{% set labelFormatter = create(
'CommerceGuys\\Addressing\\Formatter\\PostalLabelFormatter',
[
addressService.getAddressFormatRepository(),
addressService.getCountryRepository(),
addressService.getSubdivisionRepository(),
]) %}
{{ addr|address({ origin_country: 'GB' }, labelFormatter) }}
{# Output:
1234 Balboa Towers Circle
LOS ANGELES, CA 92662
UNITED STATES
#}
You can also write a custom formatter that implements FormatterInterface (opens new window). We could extend the default formatter, for example, to add a hide_countries
option that avoids printing the names of specified countries:
<?php
namespace mynamespace;
use CommerceGuys\Addressing\AddressInterface;
use CommerceGuys\Addressing\Formatter\DefaultFormatter;
use CommerceGuys\Addressing\Locale;
use craft\helpers\Html;
class OptionalCountryFormatter extends DefaultFormatter
{
/**
* @inheritdoc
*/
protected $defaultOptions = [
'locale' => 'en',
'html' => true,
'html_tag' => 'p',
'html_attributes' => ['translate' => 'no'],
'hide_countries' => [],
];
/**
* @inheritdoc
*/
public function format(AddressInterface $address, array $options = []): string
{
$this->validateOptions($options);
$options = array_replace($this->defaultOptions, $options);
$countryCode = $address->getCountryCode();
$addressFormat = $this->addressFormatRepository->get($countryCode);
if (!in_array($countryCode, $options['hide_countries'])) {
if (Locale::matchCandidates($addressFormat->getLocale(), $address->getLocale())) {
$formatString = '%country' . "\n" . $addressFormat->getLocalFormat();
} else {
$formatString = $addressFormat->getFormat() . "\n" . '%country';
}
} else {
// If this is in our `hide_countries` list, omit the country
$formatString = $addressFormat->getFormat();
}
$view = $this->buildView($address, $addressFormat, $options);
$view = $this->renderView($view);
$replacements = [];
foreach ($view as $key => $element) {
$replacements['%' . $key] = $element;
}
$output = strtr($formatString, $replacements);
$output = $this->cleanupOutput($output);
if (!empty($options['html'])) {
$output = nl2br($output, false);
// Add the HTML wrapper element with Craft’s HTML helper:
$output = Html::tag($options['html_tag'], $output, $options['html_attributes']);
}
return $output;
}
}
We can instantiate and use that just like the postal label formatter:
{# Use our custom formatter #}
{% set addressService = craft.app.getAddresses() %}
{% set customFormatter = create(
'mynamespace\\OptionalCountryFormatter',
[
addressService.getAddressFormatRepository(),
addressService.getCountryRepository(),
addressService.getSubdivisionRepository(),
]
) %}
{{ addr|address({ hide_countries: ['US'] }, customFormatter) }}
{# Output:
1234 Balboa Towers Circle
Los Angeles, CA 92662
#}
To replace the default formatter, add the following to your application configuration:
return [
// ...
'components' => [
// ...
'addresses' => [
'class' => \craft\services\Addresses::class,
'formatter' => new \mynamespace\OptionalCountryFormatter(
new \CommerceGuys\Addressing\AddressFormat\AddressFormatRepository(),
new \CommerceGuys\Addressing\Country\CountryRepository(),
new \CommerceGuys\Addressing\Subdivision\SubdivisionRepository()
)
],
],
];
The default formatter is used in the control panel as well as your templates, so make sure it includes all the information required for administrators to act on users’ information! Complete address data is always be available when viewing or editing it in a slideout.
# Managing Addresses
Users can add, edit, and delete their own addresses from the front-end via the users/save-address
and users/delete-address
controller actions.
Craft doesn’t automatically give Addresses their own URLs, though—so it’s up to you to define a routing scheme for them via routes.php
. We’ll cover each of these three routes in the following sections:
<?php
return [
// Listing Addresses
'account' => ['template' => '_account/dashboard'],
// New Addresses
'account/addresses/new' => ['template' => '_account/edit-address'],
// Existing Addresses
'account/addresses/<addressUid:{uid}>' => ['template' => '_account/edit-address'],
];
The next few snippets may be a bit dense—numbered comments are peppered throughout, corresponding to items in the Guide section just below it.
# Scaffolding
The following templates assume you have a layout functionally equivalent to the models and validation example.
# Listing Addresses
Let’s display the current user’s address book on their account “dashboard.”
{% extends '_layouts/default' %}
{% requireLogin %}
{# 1. Load Addresses: #}
{% set addresses = currentUser.getAddresses() %}
{% block content %}
<h1>Hello, {{ currentUser.fullName }}!</h1>
{% if addresses | length %}
<ul>
{% for address in addresses %}
<li>
{{ address.title }}<br>
{{ address|address }}<br>
{# 2. Build an edit URL: #}
<a href="{{ url("account/addresses/#{address.uid}") }}">Edit</a>
{# 3. Use a form to delete Addresses: #}
<form method="post">
{{ csrfInput() }}
{{ actionInput('users/delete-address') }}
{{ hiddenInput('addressId', address.id) }}
<button>Delete</button>
</form>
</li>
{% endfor $}
</ul>
{% else %}
<p>You haven’t added any addresses, yet!</p>
{% endif %}
{# 4. Link to "new" route: #}
<p><a href="{{ url('account/addresses/new') }}">New Address</a></p>
{% endblock %}
# Guide
- We’re using a craft\elements\User (opens new window) convenience method to load the current user’s saved addresses, but this is equivalent to our earlier query example.
- These URLs will need to match the pattern defined in
routes.php
. In our case, that means we need to interpolate the Address’s UID into the path. - Deleting an Address requires a POST request, which—for the sake of simplicity—we’re handling with a regular HTML form.
- The New Address route is static—there’s nothing to interpolate or parameterize.
# New Addresses
The code for new addresses will end up being reused for existing addresses.
{% extends '_layouts/default' %}
{% requireLogin %}
{% block content %}
<h1>New Address</h1>
{# 1. Render the form #}
{{ include('_account/address-form', {
address: address ?? create('craft\\elements\\Address'),
}) }}
{% endblock %}
# Guide
- We pass an craft\elements\Address (opens new window) to the form partial—either from an
address
variable that is available to the template after an attempted submission (say, due to validation errors), or a new one instantiated with thecreate()
function. - Whether we’re creating a new address or editing an existing one (this partial handles both), the request should be sent to the
users/save-address
action. - Addresses that have been previously saved will have an
id
, so we need to send that back to apply updates to the correct Address. - The
redirectInput()
function accepts an object template, which can include properties of the thing we’re working with. The template won’t be evaluated when it appears in the form—instead, Craft will render it using the address element after it’s been successfully saved. In this case, we’ll be taken to the edit screen for the newly-saved address. - Which fields are output is up to you—but be aware that the selected
countryCode
may influence what additional address fields are required. If you want to capture input for custom fields, it should be nested under thefields
key, as it is for entries and other element types:<input type="text" name="fields[myCustomFieldHandle]" value="...">
See the complete list of parameters that can be sent to the users/save-address
action.
# Existing Addresses
To edit an existing address, we’ll use the addressUid
parameter from our route.
{% extends '_layouts/default' %}
{% requireLogin %}
{# 1. Resolve the address: #}
{% set address = address ?? craft.addresses
.owner(currentUser)
.uid(addressUid)
.one() %}
{# 2. Make sure we got something: #}
{% if not address %}
{% exit 404 %}
{% endif %}
{% block content %}
<h1>Edit Address: {{ address.title }}</h1>
{# 3. Render form: #}
{{ include('_account/address-form', {
address: address,
}) }}
{% endblock %}
# Guide
- In one statement, we’re checking for the presence of an
address
variable sent back to the template by a prior submission, and falling back to a lookup against the user’s Addresses. By calling.owner(currentUser)
, we can be certain we’re only ever showing a user an Address they own. - If an Address wasn’t passed back to the template, and the UID from our route didn’t match one of the current user’s addresses, we bail.
- The Address is passed to the form partial for rendering—see the new address example for the form’s markup.
# Validating Addresses
Addresses are validated like any other type of element, but some of the rules are dependent upon its localized format.
You can set requirements for custom fields in the Address Fields field layout, but additional validation of any address properties requires a custom plugin or module.
Take a look at the Using Events in a Custom Module (opens new window) article for a dedicated primer on module setup and events.
Validation rules (opens new window) are added via the Model::EVENT_DEFINE_RULES
event:
use yii\base\Event;
use craft\base\Model;
use craft\elements\Address;
use craft\events\DefineRulesEvent;
Event::on(
Address::class,
Model::EVENT_DEFINE_RULES,
function(DefineRulesEvent $event) {
$event->rules[] = [
['fullName'],
'match',
'pattern' => '/droid|bot/i',
'message' => Craft::t('site', 'Robots are not allowed.'),
];
}
);
Errors are available through the same address.getErrors()
method used in other action and model validation examples, regardless of whether they were produced by built-in rules or ones you added.