Country State Dependent Picklist in Apex
Hello there! 👋
How to get country-state dependent picklists in Apex or LWC? Check various solutions and choose the best for you!
Pre Steps
The first required step is to enable State and Country/territory picklists.
When you enable state and country/territory picklists, the picklists are immediately available to users.
- From Setup, enter
State and Country/Territory Picklists
in the Quick Find box, then selectState and Country/Territory Picklists
. - Follow instruction to convert your country and state data.
- On the State and Country/Territory Picklists setup page, click
Enable Picklists for Address Fields
to turn on the picklists.
You will be able to see new fields:
- User
- User.CountryCode
- User.StateCode
- Account
- Account.BillingCountryCode
- Account.BillingStateCode
- Contact
- Contact.MailingCountryCode
- Contact.MailingStateCode
- Contact.OtherCountryCode
- Contact.OtherStateCode
Enable and Disable State and Country/Territory Picklists
LWC
Standard
- You can use lightning-record-edit-form. Address fields will be displayed automatically.
- It should be your first choice, an out-of-the-box solution with only a few lines of code.
However what if you cannot uselightning-record-edit-form
? Let’s jump to a custom solution.
Dependent picklist fields depend on the value of another field, called the controlling field. If you don’t provide a controlling field, the picklist displays in a disabled and read-only state. ~ lightning-input-field
<template> <lightning-record-edit-form object-api-name="Account" record-type-id="012000000000000AAA"> <lightning-input-field field-name="BillingAddress"> </lightning-input-field> </lightning-record-edit-form> </template>
Custom
- You don’t need Apex, just LWC. All necessary information can be gathered via UI Object Info API.
- The solution is very effective, the only con is that you need to preprocess state data because the retrieved format is kind of specific. 🙂
<template> <lightning-combobox name="country" label="Country" value={selectedCountry} placeholder="Select Country" options={countries} onchange={handleCountry}> </lightning-combobox> <lightning-combobox name="state" label="State" value={selectedState} placeholder="Select State" options={states} onchange={handleState}> </lightning-combobox> </template>
import { LightningElement, wire, track } from 'lwc'; import { getPicklistValues } from 'lightning/uiObjectInfoApi'; import COUNTRY_CODE from '@salesforce/schema/Account.BillingCountryCode'; import BILLING_STATE_CODE from '@salesforce/schema/Account.BillingStateCode'; export default class AddressSelector extends LightningElement { _countries = []; _countryToStates = {}; selectedCountry; selectedState; @wire(getPicklistValues, { recordTypeId: '012000000000000AAA', fieldApiName: COUNTRY_CODE }) wiredCountires({ data }) { this._countries = data?.values; } @wire(getPicklistValues, { recordTypeId: '012000000000000AAA', fieldApiName: BILLING_STATE_CODE }) wiredStates({ data }) { if (!data) { return; } const validForNumberToCountry = Object.fromEntries(Object.entries(data.controllerValues).map(([key, value]) => [value, key])); this._countryToStates = data.values.reduce((accumulatedStates, state) => { const countryIsoCode = validForNumberToCountry[state.validFor[0]]; return { ...accumulatedStates, [countryIsoCode]: [...(accumulatedStates?.[countryIsoCode] || []), state] }; }, {}); } get countries() { return this._countries; } get states() { return this._countryToStates[this.selectedCountry] || []; } handleCountry(e) { this.selectedCountry = e.detail.value; } handleState(e) { this.selectedState = e.detail.value; } }
Apex
We talked about LWC, but how to do it in Apex?
I found two solutions to get country-state dependent picklists.
First of all, let’s define a simple wrapper.
It allows us to keep the address settings clean way.
public class Country { public String label; public String value; public Country(String label, String value) { this.label = label; this.value = value; } } public class State { public String label; public String value; public String dependingOn; public State(String label,String value, String dependingOn) { this.label = label; this.value = value; this.dependingOn = dependingOn; } }
Metadata API
- You need to deploy https://github.com/financialforcedev/apex-mdapi before you start with the approach below.
- Use of the Metadata API requires a user with the
ModifyAllData
orMofifyMetadata
permissions. - In my opinion, it’s not the best solution. We don’t want end users with
ModifyAllData
andMofifyMetadata
permissions, so maybe it will be fine for the System Administrator, but not generally.
public with sharing class AddressSelectorMetadataApi { public static Map<String, List<Object>> getAddressSettings() { MetadataService.MetadataPort service = new MetadataService.MetadataPort(); service.SessionHeader = new MetadataService.SessionHeader_element(); service.SessionHeader.sessionId = UserInfo.getSessionId(); service.CallOptions = new MetadataService.CallOptions_element(); service.timeout_x = 120000; List<MetadataService.AddressSettings> ans = new List<MetadataService.AddressSettings>(); ans.addAll((List<MetadataService.AddressSettings>) service.readMetadata('AddressSettings', new List<String>{ 'Address' }).getRecords()); MetadataService.AddressSettings addressSettings = ans[0]; List<Country> countires = new List<Country>(); List<State> states = new List<State>(); for (MetadataService.Country country : addressSettings.countriesAndStates.countries) { countires.add(new Country(country.label, country.isoCode)); if (country.states == null) { continue; } for (MetadataService.State state : country.states) { states.add(new State(state.label, state.isoCode, country.isoCode)); } } return new Map<String, List<Object>>{ 'countries' => countires, 'states' => states }; } }
UI API
- Solution below uses User Interface API.
- It’s the same approach as for LWC#Custom mentioned above.
- I would choose it when you need a country-state picklist in Apex.
public with sharing class AddressSelectorUiApi { public static Map<String, List<Object>> getAddressSettings() { return new Map<String, List<Object>>{ 'countries' => getCountries(), 'states' => getStates() }; } public static List<Country> getCountries() { Map<String, Object> uiApiResponse = (Map<String, Object>) JSON.deserializeUntyped( AddressSelectorUiApi.callSalesforceUiApi('/services/data/v54.0/ui-api/object-info/Account/picklist-values/012000000000000AAA/BillingCountryCode') ); List<Country> countries = new List<Country>(); for (Object countryObject : (List<Object>) uiApiResponse.get('values')) { Map<String, Object> country = (Map<String, Object>) countryObject; countries.add(new Country((String) country.get('label'), (String) country.get('value'))); } return countries; } public static List<State> getStates() { Map<String, Object> uiApiResponse = (Map<String, Object>) JSON.deserializeUntyped( AddressSelectorUiApi.callSalesforceUiApi('/services/data/v54.0/ui-api/object-info/Account/picklist-values/012000000000000AAA/BillingStateCode') ); Map<String, Object> countryToValidFor = (Map<String, Object>) uiApiResponse.get('controllerValues'); Map<Integer, String> validForToCountry = new Map<Integer, String>(); for (String countryIsoCode : countryToValidFor.keySet()) { validForToCountry.put((Integer) countryToValidFor.get(countryIsoCode), countryIsoCode); } List<State> states = new List<State>(); for (Object stateObject : (List<Object>) uiApiResponse.get('values')) { Map<String, Object> state = (Map<String, Object>) stateObject; List<Object> validFor = (List<Object>) state.get('validFor'); states.add( new State( (String) state.get('label'), (String) state.get('value'), (String) (validFor.isEmpty() ? '' : validForToCountry.get((Integer) validFor[0])) ) ); } return states; } public static String callSalesforceUiApi(String endpoint) { String restApiUrl = URL.getOrgDomainUrl().toExternalForm() + endpoint; HttpRequest request = new HttpRequest(); request.setEndpoint(restApiUrl); request.setMethod('GET'); request.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId()); HttpResponse response = new Http().send(request); if (response.getStatusCode() == 200) { return response.getBody(); } return ''; } }
Result
- Wrappers created at the beginning of the chapter give a clean data structure.
- Below you will find result of
AddressSelectorMetadataApi.getAddressSettings()
orAddressSelectorUiApi.getAddressSettings()
.
{ { "states":[ { "value":"AJ", "label":"Ajman", "dependingOn":"AE" }, { "value":"AZ", "label":"Abu Zaby", "dependingOn":"AE" }, { "value":"DU", "label":"Dubayy", "dependingOn":"AE" }, { "value":"FU", "label":"Al Fujayrah", "dependingOn":"AE" }, { "value":"RK", "label":"Ra's al Khaymah", "dependingOn":"AE" }, { "value":"SH", "label":"Ash Shariqah", "dependingOn":"AE" }, { ... } ], "countries":[ { "value":"AD", "label":"Andorra" }, { "value":"AE", "label":"United Arab Emirates" }, { "value":"AF", "label":"Afghanistan" }, { "value":"AG", "label":"Antigua and Barbuda" }, { "value":"AI", "label":"Anguilla" }, { "value":"AL", "label":"Albania" }, { "value":"AM", "label":"Armenia" }, { "value":"AN", "label":"Netherlands Antilles" }, { "value":"AO", "label":"Angola" } , { ... } ] }
Apex – without dependencies
- Solution below does not provide dependencies between state and country.
- You can find more details here: Access the state and country picklist through Apex
- I used the
Country
andState
wrappers mentioned above. - It’s a really simple solution. Quite lovely if you don’t need dependencies between countries and states.
Schema.DescribeFieldResult countryCodeFieldResult = User.CountryCode.getDescribe(); List<Country> countries = new List<Country>(); for (Schema.PicklistEntry countryField : countryCodeFieldResult.getPicklistValues()) { countries.add( new Country(countryField.getLabel(), countryField.getValue()) ); } System.debug(countries); Schema.DescribeFieldResult stateCodeFieldResult = User.StateCode.getDescribe(); List<State> states = new List<State>(); for (Schema.PicklistEntry stateField : stateCodeFieldResult.getPicklistValues()) { states.add( new State(stateField.getLabel(), stateField.getValue(), '') ); } System.debug(states);
Repository
If you have any questions feel free to ask in the comment section below. 🙂
Was it helpful? Check out our other great posts here.