Developers

Indian mobile number validation: regex, the 10-digit rule and special number series

Validate Indian mobile numbers: the +91 10-digit format, what ^[6-9]\d{9}$ misses, 140/1600/1601 series, porting, and code for JavaScript, Python, PHP and C#.

By Published 8 min read

On this page

An Indian mobile number has 10 digits and starts with 6, 7, 8 or 9. In international E.164 format it is written +91 followed by those 10 digits. The regex ^[6-9]\d{9}$ checks that shape, but not whether the number is really a mobile or active, so validate it with libphonenumber and use a lookup for line type and reachability.

We ran every example below on 2026-09-26 against libphonenumber metadata 9.0.39 and 9.0.40 (the JavaScript, Python and PHP ports). The example numbers are libphonenumber's own. Where a row stands for a whole block, the digits are masked with X, so none of them is anyone's phone number.

What is the format of an Indian mobile number?

India's National Numbering Plan 2003 sets a 10-digit national number for both mobile and fixed-line ("basic") services (DoT, 2003). The same number can be written several ways:

Written asExampleUse
National, 10 digits81234 56789What people type in forms
With trunk prefix081234 56789Dialling inside India from some lines
International+91 81234 56789Dialling from abroad, display
E.164+918123456789Storage, APIs, deduplication
International from India0091 81234 5678900 is India's international dialling prefix

Store only the E.164 form. It is the one format every system agrees on, and it removes duplicates like 081234 56789 versus +91-81234-56789. The full international number, 91 followed by the 10 digits, is what operators call the MSISDN. The mobile ranges in today's metadata start with 6, 7, 8 or 9. Fixed-line numbers are a 2- to 4-digit trunk code for a local area (the 2003 plan listed 2,645 of them), followed by the subscriber number, 10 digits in total (DoT, 2003).

The +91 prefix, a struck-out trunk 0 and ten digit boxes starting with 6 to 9; a grid of 12 test inputs through the raw regex, the normalised regex and the library, and the 140, 1600 and 1601 series.The +91 prefix, a struck-out trunk 0 and ten digit boxes starting with 6 to 9; a grid of 12 test inputs through the raw regex, the normalised regex and the library, and the 140, 1600 and 1601 series.
The 6–9 regex is a typing hint: normalise first and validate with numbering-plan data.

Which regex should I use, and what does it miss?

If you need one, use ^[6-9]\d{9}$, and only after normalizing the input (see the next section). It is a good typing hint. It isn't validation, for three reasons:

  1. It accepts landlines. Some trunk codes start with 6, 7 or 8. Bengaluru's is 80, so a Bengaluru landline written without its 0, such as 802XXXXXXX, passes the regex. We counted all 4 billion 10-digit strings that start with 6–9 against libphonenumber's metadata: 67.0% are in mobile ranges only, 15.4% are fixed-line only, and 17.6% could be either (our overlap study).
  2. It rejects numbers you may want. The 1600 series used by banks and government bodies starts with 1, and so do 140 promotional numbers.
  3. It can't see reality. A number can fit the plan and still be unassigned, disconnected or ported to another operator.

This table shows the 12 inputs we tested, with what each layer catches. "Normalized regex" means the regex after removing spaces, dashes, +91, 0091, a bare 91 prefix and a leading 0.

#InputRaw regexNormalized regexlibphonenumber (IN)
18123456789passpassvalid, MOBILE
2+91 81234 56789failpassvalid, MOBILE
3081234 56789failpassvalid, MOBILE
4918123456789failpassvalid, MOBILE
50091 81234 56789failpassvalid, MOBILE
681234-56789failpassvalid, MOBILE
7812345678 (9 digits)failfailinvalid
881234567890 (11 digits)failfailinvalid
97410410123passpassvalid, FIXED_LINE_OR_MOBILE
10802XXXXXXX (Bengaluru landline, no 0)passpassvalid, FIXED_LINE
111600XXXXXXfailfailvalid, MOBILE
12140XXXXXXXfailfailvalid, UAN

Rows 10 to 12 stand for whole blocks. We counted every number in each block against the metadata: all of them match only the type shown. Row 9 is libphonenumber's own fixed-line example for India; it sits in a range the metadata assigns to both. See why a regex is not enough for the same problem in other countries.

How do I handle +91, a leading 0 and spaces?

Let a numbering-plan library do it. Pass the raw input and the default region IN, and it removes separators, the 0 trunk prefix, +91 and 0091 according to India's rules. It returns the E.164 number or an error. Hand-written stripping goes wrong at the edges: removing a leading 91 from a 10-digit number that happens to start with 91 breaks a valid number.

Three rules keep forms clean:

  • Ask for the country separately, prefilled with India, and parse with that region. Don't make people type +91.
  • Accept what people paste, including spaces, dashes and brackets.
  • Show the normalized number back ("+91 81234 56789") so the user can spot a typo before you send an OTP.

What are 140, 1600 and 1601 numbers?

They are number series for commercial calls, created so that people can tell official service calls from promotions. Our guide to TRAI's 2026 anti-spam rules covers them in detail.

SeriesUsed forSource
140xxPromotional calls, checked against the do-not-disturb registryTRAI PR 91/2026
1600xxService and transactional calls from entities regulated by RBI, SEBI, IRDAI and PFRDA, and government bodiesTRAI PR 91/2026, PR 135/2025
1601xxService and transactional calls from other sectors, starting with utilities and courier and logisticsTRAI PR 113/2026

Since TRAI's September 2026 amendment, call management apps may not blanket-block or spam-tag calls from these series (TRAI PR 119/2026).

For validation, two points matter. First, these numbers call people; people don't sign up with them, so rejecting them in a sign-up form is fine. Second, if you process caller IDs or call logs, a ^[6-9] rule silently drops them. In the metadata, 1600 and 1601 numbers are typed MOBILE and 140 numbers UAN, so filter on the series if you need to.

How do I validate in JavaScript, Python, PHP and C#?

All four use ports of Google's libphonenumber with the same metadata. Each function returns the E.164 number and line type for a valid Indian number, or nothing.

JavaScript (npm install libphonenumber-js), use the /max metadata so the line type is available:

JavaScript
import { parsePhoneNumberFromString } from "libphonenumber-js/max";

/** Returns { e164, type } for a valid Indian number, or null. */
export function validateIndian(input) {
  const phone = parsePhoneNumberFromString(String(input), "IN");
  if (!phone || !phone.isValid() || phone.country !== "IN") return null;
  return { e164: phone.number, type: phone.getType() ?? "UNKNOWN" };
}

Python (pip install phonenumbers):

Python
import phonenumbers
from phonenumbers import NumberParseException, PhoneNumberFormat, PhoneNumberType


def validate_indian(raw: str):
    """Return (e164, type_name) for a valid Indian number, or None."""
    try:
        n = phonenumbers.parse(raw, "IN")
    except NumberParseException:
        return None
    if not phonenumbers.is_valid_number(n) or phonenumbers.region_code_for_number(n) != "IN":
        return None
    kind = phonenumbers.number_type(n)
    return phonenumbers.format_number(n, PhoneNumberFormat.E164), PhoneNumberType.to_string(kind)

PHP (composer require giggsey/libphonenumber-for-php-lite, PHP 8.1 or later):

PHP
<?php
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;

/** Returns ['e164' => ..., 'type' => ...] for a valid Indian number, or null. */
function validateIndian(string $raw): ?array
{
    $util = PhoneNumberUtil::getInstance();
    try {
        $number = $util->parse($raw, 'IN');
    } catch (NumberParseException) {
        return null;
    }
    if (!$util->isValidNumber($number) || $util->getRegionCodeForNumber($number) !== 'IN') {
        return null;
    }
    return ['e164' => $util->format($number, PhoneNumberFormat::E164), 'type' => $util->getNumberType($number)->name];
}

C# (dotnet add package libphonenumber-csharp):

CSHARP
using PhoneNumbers;

public static class IndianPhone
{
    private static readonly PhoneNumberUtil Util = PhoneNumberUtil.GetInstance();

    /// <summary>Returns the E.164 number and line type for a valid Indian number, or null.</summary>
    public static (string E164, PhoneNumberType Type)? Validate(string raw)
    {
        PhoneNumber number;
        try { number = Util.Parse(raw, "IN"); }
        catch (NumberParseException) { return null; }
        if (!Util.IsValidNumber(number) || Util.GetRegionCodeForNumber(number) != "IN") return null;
        return (Util.Format(number, PhoneNumberFormat.E164), Util.GetNumberType(number));
    }
}

The JavaScript, Python and PHP versions returned identical results for the same inputs: +918123456789 MOBILE for rows 1 to 6 of the table, +917410410123 FIXED_LINE_OR_MOBILE for row 9, and nothing for rows 7 and 8. The C# version calls the same API in the libphonenumber-csharp port; we didn't run it for this article. The region check stops a number typed with another country's code (say +1) from passing as Indian. More languages are in phone number validation in code.

Treat FIXED_LINE_OR_MOBILE as "unknown line type", not as mobile.

How should a sign-up form handle Indian numbers?

Put the checks in order, from cheapest to most expensive, and only block on answers that are certain:

  1. Country first. Show a country selector defaulting to India, and parse with that region. People who type +44… or pick another country shouldn't be forced through Indian rules.
  2. Parse and validate on the server. Client-side checks are for typing feedback only; the server decides. Return a specific message for too short or too long rather than a generic "invalid number".
  3. Don't reject FIXED_LINE_OR_MOBILE. 17.6% of numbers starting 6–9 fall in ranges the metadata assigns to both, so rejecting them locks out real mobile users. If you need SMS, send the code and let delivery decide.
  4. Ask again for fixed-line-only numbers when you need a mobile. A number the metadata places only in a fixed-line range, such as a Bengaluru landline typed without its 0, won't receive SMS. Say so in the message: "This looks like a landline. Please enter a mobile number."
  5. Look up only where it pays. For high-value or high-risk flows, a live lookup confirms line type and current operator before you spend an SMS or trust the number.
  6. Store the E.164 number with the date you checked it. Numbers are ported and reassigned over time, so an old check is only a hint.

If you send SMS to Indian numbers yourself, the sender-side rules (registered headers, templates and consent) are covered in our TRAI anti-spam rules guide.

Does porting change the carrier? (MNP in India)

Yes, and the number stays the same. Mobile number portability started in Haryana on 25 November 2010, went nationwide on 20 January 2011, and has worked between service areas since 3 July 2015. In July 2026 alone, 15.98 million subscribers asked to port, out of 1,306.27 million wireless subscribers (TRAI PR 116/2026). TRAI's porting process takes 3 working days within a service area and 5 between areas, and up to 15 in some regions (TRAI MNP FAQ). Our MNP atlas compares that with 67 other countries.

So a prefix table ("98xxx is operator A") tells you who was first allocated the range, not who serves the number today. The carrier data bundled with libphonenumber has the same limit. If you route SMS by operator, or use the operator as a fraud signal, get the current operator from a carrier lookup. It returns line_type, carrier, original_carrier and country where network data is available, and unknown otherwise. Unknown results aren't billed.

What are the key takeaways?

  • An Indian mobile number is 10 digits starting with 6–9; store it as E.164, +91 plus the 10 digits.
  • ^[6-9]\d{9}$ is a shape check: 15.4% of the numbers it accepts are fixed-line only in the metadata, and it rejects the 140, 1600 and 1601 series.
  • Normalize with libphonenumber and region IN, not with string slicing, and check that the parsed region is India.
  • Treat FIXED_LINE_OR_MOBILE as unknown line type.
  • Porting is common (15.98 million requests in July 2026), so the prefix doesn't tell you the current operator. Use a live lookup when line type or operator drives a decision.

Sources

  1. National Numbering Plan 2003 (linked from ITU's India page, posted 2014-07-31) — Department of Telecommunications, Government of India, 2003
  2. Press release No. 116/2026: telecom subscription data as on 31 July 2026 (28 August 2026) — Telecom Regulatory Authority of India, 2026
  3. Press release No. 119/2026: TRAI strengthens framework for curbing unsolicited commercial communications (18 September 2026) — Telecom Regulatory Authority of India, 2026
  4. Press release No. 113/2026: phase-wise implementation of the 1601 series (10 August 2026) — Telecom Regulatory Authority of India, 2026
  5. Press release No. 135/2025: 1600 series for BFSI entities (19 November 2025) — Telecom Regulatory Authority of India, 2025
  6. Press release No. 91/2026: 140 and 1600 series (10 July 2026) — Telecom Regulatory Authority of India, 2026
  7. Mobile number portability FAQ — Telecom Regulatory Authority of India, 2026
  8. libphonenumber PhoneNumberMetadata.xml (region IN) — libphonenumber project (Apache License 2.0), 2026

Frequently asked questions

What is the regex for an Indian mobile number?

The common pattern is ^[6-9]\d{9}$: ten digits starting with 6, 7, 8 or 9, after removing +91, a leading 0 and spaces. It checks the shape only. In libphonenumber's metadata, 15.4% of the numbers it accepts are fixed-line only, and it rejects the 1600 series used by banks and government bodies.

How many digits is an Indian mobile number?

Ten. India's National Numbering Plan 2003 fixed the national number at 10 digits for both mobile and fixed-line services. In international E.164 format that becomes +91 followed by the 10 digits, 12 digits in total without the plus sign.

Should I remove the leading 0 from an Indian number?

Yes. The 0 is a trunk prefix for dialling inside India, not part of the number. 081234 56789 and +91 81234 56789 are the same number. Let a library such as libphonenumber strip it, rather than removing the first character yourself.

Can an Indian number change carrier?

Yes. Mobile number portability has been available nationwide since 20 January 2011, and between service areas since 3 July 2015. In July 2026 alone, 15.98 million subscribers asked to port, according to TRAI. The prefix doesn't show the current operator; a carrier lookup does.

Is a number that passes validation active?

No. Validation checks that the number fits India's numbering plan. It can't tell whether the SIM is active, which operator serves it or whether it is a mobile. That needs a lookup against network data.

All articles

Know before you send.

Tell us about your use case. We review every request and set you up with test and live keys.