Wrapper for BeamLabCountries with country data utility functions.
Provides a convenient API for working with country data: country selection, tax rates, EU membership.
Includes workaround for charlist bug in VAT rates until fixed upstream.
Examples
# Get list of countries for dropdown
countries = CountryData.countries_for_select()
# [{"🇦🇩 Andorra", "AD"}, {"🇦🇪 United Arab Emirates", "AE"}, ...]
# Get standard VAT rate
rate = CountryData.get_standard_vat_rate("EE")
# #Decimal<0.20>
# Check EU membership
CountryData.eu_member?("EE")
# true
# Get country information
country = CountryData.get_country("DE")
# %BeamLabCountries.Country{name: "Germany", ...}
# Format company address from Settings
address = CountryData.format_company_address()
# "123 Business Street\nTallinn 10115\nEstonia"
Summary
Functions
Get list of countries for select dropdown.
Get list of EEA countries (EU + Norway, Iceland, Liechtenstein).
Check if country is an EEA (European Economic Area) member.
Get list of EU countries.
Get list of EU countries for select dropdown.
Check if country is an EU member.
Check if country with given code exists.
Format company address from Settings for document printing.
Get bank details from consolidated Settings.
Get company information from consolidated Settings.
Get country by alpha-2 code.
Get country name in the active locale.
Get country currency code.
Get country flag (emoji).
Get standard VAT rate as percentage (integer).
Get standard VAT rate for a country as Decimal.
Get the subdivision label for a country.
Get the unified tax configuration from Organization settings.
Get all VAT rates with workaround for charlist bug.
Keep only the codes that name a real country, in the order given.
Get all countries sorted by name.
Split an operator-entered priority string into alpha-2 codes.
Suggest a starting priority list for a host based in country_code.
Validate IBAN format (length based on bank country, not company country).
Validate SWIFT/BIC format (8 or 11 characters).
Functions
Get list of countries for select dropdown.
Returns list of tuples {display_name, alpha2_code} for use in Phoenix form selects, sorted by the country name in the active locale.
Names come from BeamLabCountries.Translations, not the country struct's
:name field — the two differ even in English, for 20 of the 250
countries (as of beamlab_countries 1.1.0). For example: GB "United
Kingdom of Great Britain and Northern Ireland" -> "United Kingdom", US
"United States of America" -> "United States", CZ "Czech Republic" ->
"Czechia", TR "Turkey" -> "Türkiye", KP "Korea (Democratic People's
Republic of)" -> "North Korea" (which also moves its place in the sorted
list, from the K's to the N's). A host that never passes :locale still
gets different strings, and a different order, than a version of this
function that read .name directly.
Sorting folds accented letters to their base form before comparing (e.g. "ü" sorts with "u"), which is a deliberate approximation, not proper collation — the BEAM has no ICU. It is correct for most Latin-script locales, but wrong for locales that give diacritics their own place in the alphabet: in Estonian, Ü belongs at the very end (after W, Õ, Ä, Ö), and in Swedish, Å, Ä, Ö belong after Z; folding moves those names out of that position instead of leaving them there. A host that needs strict local collation should sort the returned list itself.
Options
:locale— locale for the country names. Defaults to the activePhoenixKitWeb.Gettextlocale, reduced to its base code ("ru-RU"normalizes to"ru").BeamLabCountries1.1.0 ships translations forar,de,en,es,fr,it,ja,ko,nl,pl,pt,ru,sv,uk,zh(BeamLabCountries.Translations.supported_locales/0); any other locale falls back to the country's English name, and so does an unsupported value such as an atom (locale: :ru) — a host only ever loses the translation, never the entry.:priority— alpha-2 codes pinned to the top of the list, in the order given; everything else follows alphabetically. A non-list value is treated as[]. The default is thecountry_select_prioritysetting (Admin → Settings → Organization), and nothing else: there is no compile-time config, deliberately. A default baked into the library would pin whatever countries its author serves, so every other host would install it and find the dropdown already reordered before anyone chose anything. Until an operator stores a list, nothing is pinned and the order is plain alphabetical;suggested_priority/2is what the settings UI offers them as a starting point, derived from their own country rather than from a constant.
opts itself must be a keyword list — a map or a bare string raises
FunctionClauseError naming this function rather than Keyword.
Examples
iex> countries = CountryData.countries_for_select(locale: "en")
iex> {"🇦🇫 Afghanistan", "AF"} in countries
true
iex> CountryData.countries_for_select(locale: "ru", priority: ["EE", "FI"])
...> |> Enum.take(2)
[{"🇪🇪 Эстония", "EE"}, {"🇫🇮 Финляндия", "FI"}]
Get list of EEA countries (EU + Norway, Iceland, Liechtenstein).
Check if country is an EEA (European Economic Area) member.
EEA includes EU + Norway, Iceland, Liechtenstein.
Examples
iex> CountryData.eea_member?("EE")
true
iex> CountryData.eea_member?("NO")
true
iex> CountryData.eea_member?("CH")
false
Get list of EU countries.
Examples
iex> eu = CountryData.eu_countries()
iex> length(eu)
27
iex> Enum.map(eu, & &1.alpha2) |> Enum.sort() |> Enum.take(5)
["AT", "BE", "BG", "CY", "CZ"]
Get list of EU countries for select dropdown.
Takes the same :locale and :priority options as
countries_for_select/1, including its fallback, leniency, and sorting
caveats.
Check if country is an EU member.
Examples
iex> CountryData.eu_member?("EE")
true
iex> CountryData.eu_member?("GB")
false
iex> CountryData.eu_member?("US")
false
Check if country with given code exists.
Examples
iex> CountryData.exists?("EE")
true
iex> CountryData.exists?("XX")
false
Format company address from Settings for document printing.
Assembles address from individual fields (address_line1, address_line2, city, state, postal_code, country) into a single string with line breaks.
Returns
Formatted address as string, for example:
123 Business Street
Suite 100
Tallinn 10115
EstoniaExamples
iex> CountryData.format_company_address()
"123 Business Street\nTallinn 10115\nEstonia"
Get bank details from consolidated Settings.
Reads from company_bank_details JSONB with fallback to legacy billing_bank_* keys.
Get company information from consolidated Settings.
Reads from company_info JSONB with fallback to legacy billing_company_* keys.
Get country by alpha-2 code.
Examples
iex> country = CountryData.get_country("EE")
iex> country.name
"Estonia"
iex> CountryData.get_country("XX")
nil
Get country name in the active locale.
Takes the same :locale option as countries_for_select/1 — including
its supported-locale set and its fallback/leniency rules — and falls back
to the English name when that locale has no translation for the country.
opts must be a keyword list. get_country_name("EE", "ru") is a
realistic slip (the option is :locale), and raises
FunctionClauseError naming this function rather than Keyword.
Examples
iex> CountryData.get_country_name("EE", locale: "en")
"Estonia"
iex> CountryData.get_country_name("EE", locale: "ru")
"Эстония"
iex> CountryData.get_country_name("XX")
nil
Get country currency code.
Examples
iex> CountryData.get_currency_code("EE")
"EUR"
iex> CountryData.get_currency_code("GB")
"GBP"
iex> CountryData.get_currency_code("US")
"USD"
Get country flag (emoji).
Examples
iex> CountryData.get_flag("EE")
"🇪🇪"
Get standard VAT rate as percentage (integer).
Returns rate as percentage (20 = 20%).
Examples
iex> CountryData.get_standard_vat_percent("EE")
20
iex> CountryData.get_standard_vat_percent("DE")
19
iex> CountryData.get_standard_vat_percent("US")
0
Get standard VAT rate for a country as Decimal.
Returns rate in decimal format (0.20 = 20%). If country not found or has no VAT rates, returns 0.
Examples
iex> CountryData.get_standard_vat_rate("EE")
#Decimal<0.20>
iex> CountryData.get_standard_vat_rate("DE")
#Decimal<0.19>
iex> CountryData.get_standard_vat_rate("US")
#Decimal<0>
Get the subdivision label for a country.
Returns appropriate label like "State", "Province", "Region", etc. based on what the country uses for administrative divisions.
Examples
iex> CountryData.get_subdivision_label("US")
"State"
iex> CountryData.get_subdivision_label("CA")
"Province"
iex> CountryData.get_subdivision_label("EE")
"County"
Get the unified tax configuration from Organization settings.
Returns a map with:
:enabled- boolean, whether tax is enabled:rate- string percentage (e.g. "20"):rate_decimal- Decimal fraction (e.g. Decimal.new("0.20"))
Tax rate is stored in the company_info JSON setting under "tax_rate" and
"tax_enabled" keys. Falls back to billing_default_tax_rate / billing_tax_enabled
for backward compatibility.
Get all VAT rates with workaround for charlist bug.
Returns map with normalized rates:
- :standard - standard rate (integer)
- :reduced - reduced rates (list of integers)
- :super_reduced - super reduced rate (integer or nil)
- :parking - parking rate (integer or nil)
Examples
iex> CountryData.get_vat_rates("EE")
%{standard: 20, reduced: [9], super_reduced: nil, parking: nil}
iex> CountryData.get_vat_rates("FR")
%{standard: 20, reduced: [5.5, 10], super_reduced: 2.1, parking: nil}
iex> CountryData.get_vat_rates("US")
nil
Keep only the codes that name a real country, in the order given.
The counterpart of parse_priority/1 for a settings form: it tells the
operator which of the codes they typed will actually pin something.
Examples
iex> CountryData.known_country_codes(["EE", "ZZ", "FI"])
["EE", "FI"]
Get all countries sorted by name.
Examples
iex> countries = CountryData.list_countries()
iex> length(countries)
250
iex> hd(countries).name
"Afghanistan"
Split an operator-entered priority string into alpha-2 codes.
Accepts the separators a human actually types — commas, spaces, semicolons,
newlines — so "EE, FI", "ee fi" and "EE;FI" all parse. Unknown codes
are kept here, not dropped — normalize_priority/1 only filters
non-binaries, upcases, and dedupes. They are dropped later, when
split_priority/2 looks each one up against the real country list and
finds no match; use known_country_codes/1 to report them to the
operator before that happens.
Examples
iex> CountryData.parse_priority("EE, FI ; lv")
["EE", "FI", "LV"]
iex> CountryData.parse_priority("")
[]
Suggest a starting priority list for a host based in country_code.
Returns that country first, then its nearest neighbours by great-circle distance between country centroids — so a host in Estonia is offered Latvia, Åland, Finland and Lithuania, one in Germany gets Luxembourg, the Netherlands, Czechia and Belgium, and one in Singapore gets Malaysia, Indonesia, Cambodia and Brunei. The point is that it is derived from the host's own data rather than from a constant baked in by whoever wrote the library.
This is a suggestion for the settings UI to offer, never applied on its own: nothing is pinned until an operator stores a list. The result can include dependent territories (Åland is the second-nearest thing to Estonia) — the data has no "sovereign state" flag — so the operator is expected to prune it.
Dissolved countries are excluded. Countries with no coordinates cannot be ranked and are skipped.
Options
:limit— how many neighbours to add after the country itself. Defaults to 4.
Examples
iex> CountryData.suggested_priority("EE", limit: 2)
["EE", "LV", "AX"]
iex> CountryData.suggested_priority("XX")
[]
Validate IBAN format (length based on bank country, not company country).
Bank can be in a different country than the company - this is legal. Validates format and length based on IBAN's country prefix.
Returns :ok or {:error, reason}.
Examples
iex> CountryData.validate_iban_format("EE382200221020145685", "EE")
:ok
iex> CountryData.validate_iban_format("DE89370400440532013000", "EE")
:ok # German bank for Estonian company is valid
iex> CountryData.validate_iban_format("DE123", "EE")
{:error, "IBAN must be 22 characters for DE"}
Validate SWIFT/BIC format (8 or 11 characters).
SWIFT codes structure:
- 4 letters: bank code
- 2 letters: country code (ISO 3166)
- 2 characters: location code
- 3 characters (optional): branch code
Examples
iex> CountryData.validate_swift_format("HABAEE2X")
:ok
iex> CountryData.validate_swift_format("HABAEE2XXXX")
:ok
iex> CountryData.validate_swift_format("INVALID")
{:error, "SWIFT/BIC must be 8 or 11 characters"}