JetFormBuilder Phone Field Country Code with Flag and Auto-Detect

Since JetFormBuilder 3.6.0 you can add a phone field with a country code dropdown without writing any code. The built-in Phone Field block shows flags and dial codes, detects the visitor's country, validates the number and saves it in E.164 format (+CC followed by digits). If you don't want IP-based country detection, use the snippet below instead. It adds the same flag and dial-code dropdown to a Text Field set to Tel, guesses the country from the visitor's time zone and browser language, needs no API key, and submits a clean +CC number.
- Which option should you use?
- Set up the built-in Phone Field (no code)
- What you need for the snippet
- Set up the no-API-key snippet
- How the auto-detection works
- Customize, self-host and upgrade
- Common problems
Which option should you use?
| Built-in Phone Field | No-API-key snippet | |
|---|---|---|
| Code needed | None | One PHP snippet |
| Plugin version | JetFormBuilder 3.6.0+ (3.6.7 recommended) | Any version with a Text Field set to Tel |
| Country detection | "auto" uses the visitor's IP, with an optional ipinfo.io token | Saved choice, then time zone, then browser language, then your fallback |
| Remembers the last country | Not documented | Yes, in localStorage |
| Number validation | Yes, with an INVALID PHONE MESSAGE | Yes, blocks submit on invalid numbers |
| Saved format | E.164 or International | E.164 |
| Third parties | ipinfo.io for IP lookup | jsDelivr CDN for the library files (self-hosting possible) |
For most sites the built-in field is the right choice. Use the snippet if you don't want visitor IPs sent to a geolocation service, if you want the visitor's last choice remembered, or if you can't update JetFormBuilder yet.
Set up the built-in Phone Field (no code)
- Update JetFormBuilder to the latest version. The Phone Field arrived in 3.6.0. Version 3.6.2 fixed validation being skipped when the country changed, 3.6.6 fixed landline numbers being rejected as invalid, and 3.6.7 added custom placeholder support. The 3.6.x releases also include several security fixes, so staying current matters anyway.
- Open your form in the editor and add the Phone Field block. Before 3.6.0, "Tel" was only a Type option on the Text Field. Phone is now its own block.
- In the General settings, fill in Field Label and Form Field Name. Use the field name in your email, CRM or WooCommerce actions.
- In the Field tab, set DEFAULT COUNTRY. Leave it on
autoto detect the country from the visitor's IP, or enter a 2-letter ISO code such asUSorGB. - Optionally fill PREFERRED COUNTRIES (comma-separated ISO codes, shown at the top), ONLY COUNTRIES or EXCLUDE COUNTRIES.
- Turn on Separate Dial Code if you want the +CC shown next to the flag instead of inside the input.
- Set SAVE FORMAT to E.164, for example
+380991234567. JetFormBuilder recommends it, and CRMs and SMS tools expect it. "International" saves a spaced version such as+1 201-555-3333. - Under Additional Settings, either turn on Use Global ipinfo.io Token to use the token saved in WordPress Dashboard > JetFormBuilder > Settings > Ipinfo API, or paste a token into the field's own IPINFOIO API TOKEN setting. JetFormBuilder's docs say a token improves detection accuracy.
- Under Validation Messages, set REQUIRED FIELD MESSAGE and INVALID PHONE MESSAGE.
To test, open the form in a private window. The flag should match your country. Type an invalid number and you should get your invalid-phone message. Submit a valid one and the saved value should start with +. Full details are in the Phone Field documentation.
What you need for the snippet
- WordPress 6.1+ (the current JetFormBuilder minimum)
- JetFormBuilder, ideally on the latest 3.6.x release
- A snippet manager: Code Snippets (free), WPCode (free) or WPCodeBox (premium). A child theme's functions.php also works.
Set up the no-API-key snippet
- Add a Text Field and set its Field Type to Tel. The snippet only touches
input[type="tel"]fields. A plain text input is ignored. If your JetFormBuilder version doesn't offer Tel on the Text Field, use the built-in Phone Field instead. - Give the field the CSS class
js-itiin its Advanced settings. This opt-in class keeps the snippet away from the built-in Phone Field, so you never get two flag dropdowns on one input. The selector works whether the class lands on the input or on its wrapper. - Remove any older version of this snippet, including the original gist, so the library is not started twice.
- Create a new PHP snippet and set it to run on the front end only.
- Paste the code below and change
$fallbackto your main market if it isn't the US. - Save, activate and clear your page cache. Then reload the form. You should see a flag with a dial code at the start of the phone input.
An earlier version of this snippet, with placeholder-colour handling, is still on GitHub Gist. The version below fixes the issues covered in Common problems. Add it as a PHP snippet, or at the end of your child theme's functions.php:
Code
/**
* BoostMyCroco: JetFormBuilder Tel field with country flag, dial code and
* auto-detected country. No API key, no IP lookup.
* Targets Text Fields (Field Type: Tel) that carry the CSS class "js-iti".
* Add as a front-end PHP snippet or to a child theme's functions.php.
*/
add_action( 'wp_footer', 'bmc_jfb_intl_tel_input', 100 );
function bmc_jfb_intl_tel_input() {
// Keep the version pinned. intl-tel-input 24.6.0+ renamed utilsScript and 25+ dropped string URLs.
$version = '23.7.4';
$base = 'https://cdn.jsdelivr.net/npm/intl-tel-input@' . $version . '/build/';
$fallback = 'us'; // Last-resort country, 2-letter ISO code (gb, de, au...).
$config = array(
'css' => esc_url_raw( $base . 'css/intlTelInput.css' ),
'js' => esc_url_raw( $base . 'js/intlTelInput.min.js' ),
'utils' => esc_url_raw( $base . 'js/utils.js' ),
'fallback' => substr( sanitize_key( $fallback ), 0, 2 ),
'invalid' => 'Please enter a valid phone number.',
);
?>
<script>
(function () {
'use strict';
var CFG = <?php echo wp_json_encode( $config ); ?>;
var SELECTOR = '.jet-form-builder .js-iti input[type="tel"], .jet-form-builder input[type="tel"].js-iti';
var STORE_KEY = 'jfb_tel_cc';
var TZ2CC = {
'America/New_York':'us','America/Chicago':'us','America/Denver':'us','America/Phoenix':'us',
'America/Los_Angeles':'us','America/Anchorage':'us','Pacific/Honolulu':'us',
'America/Toronto':'ca','America/Vancouver':'ca','America/Edmonton':'ca','America/Halifax':'ca',
'America/Mexico_City':'mx','America/Bogota':'co','America/Lima':'pe','America/Santiago':'cl',
'America/Argentina/Buenos_Aires':'ar','America/Sao_Paulo':'br',
'America/Detroit':'us','America/Montreal':'ca','America/Winnipeg':'ca','America/Buenos_Aires':'ar','America/Montevideo':'uy',
'Europe/London':'gb','Europe/Dublin':'ie','Europe/Lisbon':'pt','Europe/Madrid':'es','Europe/Paris':'fr',
'Europe/Brussels':'be','Europe/Amsterdam':'nl','Europe/Berlin':'de','Europe/Zurich':'ch','Europe/Vienna':'at',
'Europe/Rome':'it','Europe/Copenhagen':'dk','Europe/Oslo':'no','Europe/Stockholm':'se','Europe/Helsinki':'fi',
'Europe/Warsaw':'pl','Europe/Prague':'cz','Europe/Athens':'gr','Europe/Bucharest':'ro','Europe/Kyiv':'ua',
'Europe/Kiev':'ua','Europe/Istanbul':'tr',
'Atlantic/Reykjavik':'is','Europe/Luxembourg':'lu','Atlantic/Madeira':'pt','Atlantic/Azores':'pt',
'Europe/Bratislava':'sk','Europe/Budapest':'hu','Europe/Sofia':'bg','Europe/Zagreb':'hr','Europe/Belgrade':'rs',
'Europe/Sarajevo':'ba','Europe/Skopje':'mk','Europe/Tirane':'al','Europe/Ljubljana':'si',
'Europe/Tallinn':'ee','Europe/Riga':'lv','Europe/Vilnius':'lt',
'Africa/Cairo':'eg','Africa/Lagos':'ng','Africa/Nairobi':'ke','Africa/Johannesburg':'za',
'Africa/Casablanca':'ma','Africa/Algiers':'dz','Africa/Tunis':'tn',
'Asia/Dubai':'ae','Asia/Riyadh':'sa','Asia/Karachi':'pk','Asia/Kolkata':'in','Asia/Calcutta':'in',
'Asia/Dhaka':'bd','Asia/Bangkok':'th','Asia/Jakarta':'id','Asia/Manila':'ph','Asia/Singapore':'sg',
'Asia/Kuala_Lumpur':'my','Asia/Shanghai':'cn','Asia/Hong_Kong':'hk','Asia/Seoul':'kr','Asia/Tokyo':'jp',
'Asia/Jerusalem':'il','Asia/Tehran':'ir','Asia/Qatar':'qa','Asia/Kuwait':'kw','Asia/Taipei':'tw',
'Asia/Ho_Chi_Minh':'vn','Asia/Saigon':'vn',
'Australia/Sydney':'au','Australia/Melbourne':'au','Australia/Brisbane':'au','Australia/Perth':'au',
'Australia/Adelaide':'au',
'Pacific/Auckland':'nz'
};
var instances = new WeakMap();
var queue = [];
var loading = false;
var timer;
function savedCountry() {
try {
var v = (localStorage.getItem(STORE_KEY) || '').toLowerCase();
return /^[a-z]{2}$/.test(v) ? v : '';
} catch (e) { return ''; }
}
// Order: saved choice > time zone > browser language region > Intl.Locale > fallback.
function detectCountry() {
var cc = savedCountry();
if (cc) return cc;
try {
var tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (tz && TZ2CC[tz]) return TZ2CC[tz];
} catch (e) {}
var langs = (navigator.languages && navigator.languages.length) ? navigator.languages : [navigator.language || ''];
for (var i = 0; i < langs.length; i++) {
var m = /[-_]([A-Za-z]{2})$/.exec(langs[i] || '');
if (m) return m[1].toLowerCase();
}
try {
if (window.Intl && Intl.Locale && langs[0]) {
var region = new Intl.Locale(langs[0]).maximize().region;
if (region && /^[A-Z]{2}$/.test(region)) return region.toLowerCase();
}
} catch (e) {}
return CFG.fallback || 'us';
}
// Load the library only when a matching field exists on the page.
function loadLibrary(done) {
if (window.intlTelInput) { done(); return; }
queue.push(done);
if (loading) return;
loading = true;
var css = document.createElement('link');
css.rel = 'stylesheet';
css.href = CFG.css;
document.head.appendChild(css);
var style = document.createElement('style');
style.textContent = '.jet-form-builder .iti{width:100%}.iti--container{z-index:9999}';
document.head.appendChild(style);
var js = document.createElement('script');
js.src = CFG.js;
js.onload = function () {
var q = queue;
queue = [];
q.forEach(function (fn) { fn(); });
};
document.head.appendChild(js);
}
function initField(input) {
if (instances.has(input)) return;
var iti = window.intlTelInput(input, {
initialCountry: detectCountry(),
separateDialCode: true,
nationalMode: true,
autoPlaceholder: 'polite',
dropdownContainer: document.body,
// 23.x validates against MOBILE by default; accept landlines too.
validationNumberType: 'FIXED_LINE_OR_MOBILE',
utilsScript: CFG.utils
});
instances.set(input, iti);
input.addEventListener('countrychange', function () {
input.setCustomValidity('');
var data = iti.getSelectedCountryData();
if (data && data.iso2) {
try { localStorage.setItem(STORE_KEY, data.iso2); } catch (e) {}
}
});
input.addEventListener('input', function () { input.setCustomValidity(''); });
}
function scan() {
if (!document.querySelector(SELECTOR)) return;
loadLibrary(function () {
document.querySelectorAll(SELECTOR).forEach(initField);
});
}
function toE164(input, iti) {
var num = '';
try { num = iti.getNumber(); } catch (e) {}
if (num && num.charAt(0) === '+') return num;
// Fallback when utils.js did not load.
var typed = input.value.trim();
if (typed.indexOf('00') === 0) typed = '+' + typed.slice(2); // 0044... means +44...
var digits = typed.replace(/\D/g, '');
if (!digits) return '';
if (typed.charAt(0) === '+') return '+' + digits;
var data = iti.getSelectedCountryData() || {};
if (!data.dialCode) return typed;
// Drop the national trunk 0 (07700... becomes +447700...). Italy keeps its leading 0.
if (data.iso2 !== 'it') digits = digits.replace(/^0/, '');
return '+' + data.dialCode + digits;
}
// Capture phase on document: runs before JetFormBuilder's own submit handling.
document.addEventListener('submit', function (e) {
var form = e.target;
if (!form || !form.querySelectorAll) return;
var fields = form.querySelectorAll(SELECTOR);
for (var i = 0; i < fields.length; i++) {
var input = fields[i];
var iti = instances.get(input);
if (!iti || !input.value.trim()) continue;
// isValidNumber() returns null while utils.js is not loaded; only block on a definite false.
if (iti.isValidNumber() === false) {
e.preventDefault();
e.stopImmediatePropagation();
input.setCustomValidity(CFG.invalid);
input.reportValidity();
return;
}
input.value = toE164(input, iti);
}
}, true);
function boot() {
scan();
if (!window.MutationObserver) return;
// Catches forms added later: popups, AJAX content, repeater rows.
new MutationObserver(function () {
clearTimeout(timer);
timer = setTimeout(scan, 150);
}).observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();
</script>
<?php
}If you use functions.php, paste the code below the existing opening <?php tag. Don't add a second one. Snippet plugins need no opening tag.
How the auto-detection works
The snippet prints a small inline script in the footer. That script loads the intl-tel-input files only on pages that contain a matching Tel field. The country is picked in this order:
- The country the visitor chose last time, saved in localStorage under
jfb_tel_cc. Visitors who used the older gist version keep their saved choice. - The visitor's time zone, matched against the
TZ2CCtable (for exampleEurope/Londonbecomes GB). - The region in the browser language (for example
en-GBbecomes GB). - The likely region from
Intl.Locale(...).maximize(), so a baredebecomes DE. - Your
$fallbackcountry.
This is an educated guess, not geolocation. A visitor in Mexico with an English US browser and an unlisted time zone will get the US flag. Nothing is sent to a geolocation service, and the visitor can always change the flag.
When the form is submitted, the snippet checks each field with isValidNumber(). intl-tel-input 23.x checks numbers against mobile number rules by default, so the snippet sets validationNumberType: 'FIXED_LINE_OR_MOBILE' to accept landlines as well. In 23.x, isValidNumber() only checks that the length is possible for the selected country, so it catches numbers that are too short or too long but cannot confirm that a number exists. For a stricter check, replace it with isValidNumberPrecise(), which in 23.x checks real number ranges for all number types and ignores validationNumberType. An invalid number stops the submit and shows the browser's validation bubble. A valid number is rewritten to E.164 with getNumber(), so 07700 900123 with the UK flag is saved as +447700900123.
Test it
- Open the form in a private window. You should see a flag and dial code that match your time zone.
- Pick another country, reload the page, and confirm the flag stays on your choice.
- Type a few random digits and submit. The submit should be blocked with "Please enter a valid phone number."
- Submit a real mobile number, then a real landline, for example a German or UK fixed-line number with the matching flag. Neither should be blocked. Check the email or stored record: both should start with + and contain digits only.
Customize, self-host and upgrade
- Fallback country: change
$fallback. To force one country for everyone, replacedetectCountry()in the init options with a code such as'gb'. - Time zones: add your visitors' zones to
TZ2CCusing IANA names (Area/City) and lowercase ISO codes. - Error text: edit the
invalidmessage in$config. - Mobile numbers only: if you only want mobile numbers, change
validationNumberTypeto'MOBILE'. - Self-hosting: the library files come from jsDelivr, a third-party CDN that sees each request. To avoid that, copy the
build/css,build/jsandbuild/imgfolders of version 23.7.4 intoyour-child-theme/iti/build/, keeping the same folder structure. The CSS loads the flag images from theimgfolder, so without it the dropdown shows no flags. Then set$basetoget_stylesheet_directory_uri() . '/iti/build/'. - Upgrading the library: keep 23.7.4 unless you test a newer release. 24.6.0 deprecated
utilsScriptin favour ofloadUtilsOnInit. Version 25 renamed that option toloadUtils, which only accepts a function such asloadUtils: () => import('…/build/js/utils.js'), and renamedvalidationNumberTypetovalidationNumberTypes, which takes an array such as['FIXED_LINE_OR_MOBILE']. All file URLs must point to the same version. - Mobile keyboard: Tel inputs already show a phone keypad on most devices. If yours doesn't, see forcing the number keyboard on JetFormBuilder phone fields.
Common problems
- No flag appears: the field must be a Tel input and carry the
js-iticlass. Clear page and CDN caches, then check the browser console for blocked requests to cdn.jsdelivr.net. - Dropdown shows country names but no flag images: if you self-host, the
build/imgfolder is missing or not next tobuild/css. - Two flags on one field: two scripts are running. Remove the older gist snippet, and never add
js-itito the built-in Phone Field, which already has its own dropdown. - Dropdown hidden behind a sticky header or popup: the snippet sets
.iti--containertoz-index: 9999. Raise it if your header or popup sits higher. - Field not converted inside a popup: the MutationObserver starts fields that are added after page load, such as JetPopup or Elementor popups and repeater rows. If one is still missed, make sure the popup content is inside a
.jet-form-builderform. For a styled confirmation after submit, see our JetFormBuilder success message popup tip. - Number saved without + or with a leading 0: the snippet did not run on that field. Check that it is a Text Field set to Tel with the
js-iticlass, clear page and CDN caches, and exclude the inline script andintl-tel-inputfrom JS combine and delay settings. - Random digits are not blocked: utils.js didn't load, usually because an optimization plugin combined, delayed or deferred it. Exclude
intl-tel-inputfrom JS optimization. The fallback still adds the dial code and drops one trunk zero, but validation and full formatting need utils.js. - Built-in Phone Field rejects landlines: update JetFormBuilder to 3.6.6 or later (3.6.6.1 and newer also include security fixes).
- You also need a country name field: see adding a country list to a JetFormBuilder select field.
Use the built-in Phone Field set to E.164 when IP-based detection is fine for you. Use the snippet when you want flags, validation and clean +CC numbers with no geolocation lookup. If you'd rather have someone set this up and connect it to your CRM or WooCommerce, post a request on BoostMyCroco.
Frequently Asked Questions
Does JetFormBuilder have a phone field with country code now?
How do I set a default country in the JetFormBuilder phone field?
Why is my phone number saved without the plus sign?
Can I store the country separately from the phone number?
Does the no-API-key snippet send visitor data to a third party?
Sources
- Phone Field Overview — JetFormBuilder (jetformbuilder.com)
- Jetformbuilder form, A custom code for auto detect country and land code phone ( New style and new version ). (gist.github.com)
- Releases · jackocnr/intl-tel-input · GitHub (github.com)
- JetFormBuilder — Dynamic Blocks Form Builder (wordpress.org)
- Simplifying utils loading in v25 · jackocnr/intl-tel-input · Discussion #1842 · GitHub (github.com)


