JavaScript plugin example

Hidden input

If you're using traditional form submissions (rather than fetch), the input values need to contain everything you want sent before submit. That's a problem if the user enters their number in national format or with separateDialCode enabled, as the input value won't include the full international number.

The hiddenInput option solves this by listening for the form submit event and injecting a hidden input with the full international number (and another with the selected country iso2 code, e.g. "gb"), which get submitted along with the rest of your form data.

In the demo below, we have separateDialCode enabled, and hiddenInput defining the full_phone input. Enter a valid number and click submit to see it in action. The form posts to the current URL, so the page reloads with the submitted input values in the URL query string. You will see phone contains the typed number (without the dial code), and full_phone contains the full international number.

Demo

Html

<form id="form">
  <label for="phone">Phone number</label>
  <input id="phone" name="phone" type="tel">
  <button type="submit">Submit</button>
</form>
<span id="error-msg"></span>

JavaScript

geoIpLookup here uses ipapi's limited free tier — for production, pick a paid plan, another provider, or roll your own.
yourCodeToDeriveErrorMessage is up to you — see Deriving a user-facing error message for a worked example.
import intlTelInput from "intl-tel-input";

const form = document.querySelector("#form");
const input = document.querySelector("#phone");
const errorMsg = document.querySelector("#error-msg");
const validMsg = document.querySelector("#valid-msg");

// initialise plugin
const iti = intlTelInput(input, {
  separateDialCode: true,
  strictMode: true,
  strictRejectAnimation: true,
  hiddenInput: () => ({
    phone: "full_phone",
    country: "country_iso2",
  }),
  initialCountry: "auto",
  geoIpLookup: (success, failure) => {
    fetch("https://ipapi.co/json")
      .then(res => res.json())
      .then(data => success(data.country_code))
      .catch(() => failure());
  },
  loadUtils: () => import("intl-tel-input/utils"),
});

// wait for utils to load before calling isValidNumber
await iti.promise;

// validation code
let showValidation = false;

const updateUI = () => {
  if (!showValidation) return;

  let invalidMsg = "";
  const isValid = iti.isValidNumber();
  if (!isValid) {
    const errorCode = iti.getValidationError();
    invalidMsg = yourCodeToDeriveErrorMessage(input.value, errorCode);
  }
  errorMsg.textContent = invalidMsg;
  return isValid;
};

// on submit: enable validation UI
form.addEventListener("submit", (e) => {
  showValidation = true;
  const isValid = updateUI();
  if (!isValid) {
    e.preventDefault();
  }
});

// on blur: enable validation UI
input.addEventListener("blur", () => {
  showValidation = true;
  updateUI();
});

// while typing / pasting / changing country: update validity state
input.addEventListener("input", updateUI);

// if the form was submitted and the page reloaded with the full phone number in the query string, show it here
const urlParams = new URLSearchParams(window.location.search);
const phone = urlParams.get("phone");
const fullPhone = urlParams.get("full_phone");
if (fullPhone) {
  validMsg.innerHTML = `Submitted values<br>phone: ${phone}<br>full_phone: ${fullPhone}`;
}