I’m constantly excited to take on fresh challenges and work alongside inspiring innovators.

Phone

+38349706007

Email

waloonn@live.com

Website

https://molla.host

Address

40000 | Mitrovicë

Social Links

AutoGeo Phone Field

A fully automated international phone input system that detects the user’s country using multiple geo-IP providers and instantly applies the correct dial code. Built with intl-tel-input, smart fallbacks, and modern UX styling for seamless onboarding.

Nov 21, 2025 By Valon Tahiri
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Phone Input — robust geo detect</title>

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/css/intlTelInput.css">

 
</head>
<body class="p-4">
  <div class="d-flex justify-content-center align-items-center vh-100"> 
  <fieldset class="w-50">
    <div class="form-row">
      <div class="form-group col-md-6 mx-auto">
        <input type="tel" id="phone2" class="form-control" placeholder="Loading...">
      </div>
    </div>
  </fieldset>
</div>


  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/intlTelInput.min.js"></script>

  
</body>
</html>
<style>
    /* Primary color #c34b9a */
    .iti__country-list li:hover,
    .iti__country.iti__highlight {
      background: #c34b9a !important;
      color: #fff !important;
    }
    .iti input:focus {
      border-color: #c34b9a !important;
      box-shadow: 0 0 0 0.2rem rgba(195,75,154,0.25) !important;
    }

    body::-webkit-scrollbar {
  display: none;
}
  </style>
<script>
    (function () {
      const input = document.querySelector("#phone2");

      // Try multiple geo IP providers in order, return first valid country code
      function tryGeoProviders(callback) {
        // Helper to validate country codes (2 letters)
        function validCode(code) {
          return typeof code === "string" && /^[A-Za-z]{2}$/.test(code);
        }

        // 1) ipapi.co
        fetch("https://ipapi.co/json/")
          .then(r => r.json())
          .then(data => {
            const code = (data && data.country_code) ? data.country_code.toUpperCase() : null;
            if (validCode(code)) return callback(code);
            // else fallthrough to next
            return fetch("https://ipwho.is/")
              .then(r => r.json())
              .then(d2 => {
                const code2 = (d2 && d2.country_code) ? d2.country_code.toUpperCase() : null;
                if (validCode(code2)) return callback(code2);
                // fallback to ipinfo (no token) — may be rate limited
                return fetch("https://ipinfo.io/json?token=") // token optional; leave empty if you don't have one
                  .then(r3 => r3.json())
                  .then(d3 => {
                    const code3 = (d3 && d3.country) ? d3.country.toUpperCase() : null;
                    if (validCode(code3)) return callback(code3);
                    // fallback to browser locale
                    const nav = (navigator && navigator.language) ? navigator.language : null;
                    if (nav && nav.includes("-")) {
                      const parts = nav.split("-");
                      if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                    }
                    // last resort: return empty so intl-tel-input will choose default behavior
                    return callback("");
                  })
                  .catch(() => {
                    // ipinfo failed => try locale
                    const nav = (navigator && navigator.language) ? navigator.language : null;
                    if (nav && nav.includes("-")) {
                      const parts = nav.split("-");
                      if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                    }
                    return callback("");
                  });
              })
              .catch(() => {
                // ipwho.is failed, try ipinfo path
                fetch("https://ipinfo.io/json?token=")
                  .then(r3 => r3.json())
                  .then(d3 => {
                    const code3 = (d3 && d3.country) ? d3.country.toUpperCase() : null;
                    if (validCode(code3)) return callback(code3);
                    const nav = (navigator && navigator.language) ? navigator.language : null;
                    if (nav && nav.includes("-")) {
                      const parts = nav.split("-");
                      if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                    }
                    return callback("");
                  })
                  .catch(() => {
                    const nav = (navigator && navigator.language) ? navigator.language : null;
                    if (nav && nav.includes("-")) {
                      const parts = nav.split("-");
                      if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                    }
                    return callback("");
                  });
              });
          })
          .catch(() => {
            // ipapi failed -> try ipwho.is
            fetch("https://ipwho.is/")
              .then(r => r.json())
              .then(d2 => {
                const code2 = (d2 && d2.country_code) ? d2.country_code.toUpperCase() : null;
                if (validCode(code2)) return callback(code2);
                fetch("https://ipinfo.io/json?token=")
                  .then(r3 => r3.json())
                  .then(d3 => {
                    const code3 = (d3 && d3.country) ? d3.country.toUpperCase() : null;
                    if (validCode(code3)) return callback(code3);
                    const nav = (navigator && navigator.language) ? navigator.language : null;
                    if (nav && nav.includes("-")) {
                      const parts = nav.split("-");
                      if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                    }
                    return callback("");
                  })
                  .catch(() => {
                    const nav = (navigator && navigator.language) ? navigator.language : null;
                    if (nav && nav.includes("-")) {
                      const parts = nav.split("-");
                      if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                    }
                    return callback("");
                  });
              })
              .catch(() => {
                const nav = (navigator && navigator.language) ? navigator.language : null;
                if (nav && nav.includes("-")) {
                  const parts = nav.split("-");
                  if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
                }
                return callback("");
              });
          });
      }

      // initialize intl-tel-input using geoIpLookup (so that plugin knows the country from callback)
      function initWithCountry(countryCode) {
        // intl-tel-input expects lowercase 2-letter ISO (or "auto" with geoIpLookup).
        // If countryCode is empty string, we won't pass initialCountry, letting plugin default.
        const options = {
          separateDialCode: true,
          preferredCountries: ["xk", "al", "mk", ], // keep your preferred list
        };

        if (countryCode && typeof countryCode === "string" && countryCode.length === 2) {
          options.initialCountry = countryCode.toLowerCase();
        } else {
          // if country unknown, let intl-tel-input use its default behaviour (you can set a specific default here)
          options.initialCountry = "auto"; // but we'll also provide a geoIpLookup that returns empty => plugin picks its default
          options.geoIpLookup = function (cb) { cb(""); };
        }

        const iti = window.intlTelInput(input, options);

        // If plugin has a selected country, set the visible value to the dial code so user sees it
        const data = iti.getSelectedCountryData();
        if (data && data.dialCode) {
          input.value = "+" + data.dialCode;
        } else {
          // fallback number
          input.value = "";
        }
      }

      // Run providers sequence, then init plugin with result
      tryGeoProviders(function (countryCode) {
        // Some services return 'XK' for Kosovo which is fine (intl-tel-input supports 'xk')
        // console.log("Detected countryCode:", countryCode);
        if (!countryCode) {
          // last resort: try browser locale split (if not already tried inside function)
          const nav = (navigator && navigator.language) ? navigator.language : null;
          if (nav && nav.includes("-")) {
            const parts = nav.split("-");
            if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) countryCode = parts[1].toUpperCase();
          }
        }

        // If still missing, avoid hardcoding to "de" — use empty to allow plugin default
        initWithCountry(countryCode || "");
      });
    })();
  </script>
Share

Leave a comment

Your email address will not be published. Required fields are marked *

Your experience on this site will be improved by allowing cookies. Cookie Policy