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 Picklistsin 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 Fieldsto 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
ModifyAllDataorMofifyMetadatapermissions. - In my opinion, it’s not the best solution. We don’t want end users with
ModifyAllDataandMofifyMetadatapermissions, 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
CountryandStatewrappers 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.
