# PakDataKit
> Production-ready utility toolkit for Pakistani developers — CNIC, phone, WhatsApp, IBAN, postal codes, address parsing, Hijri dates, Roman Urdu, networks, geography, and formatting. TypeScript, zero runtime dependencies, ESM and CommonJS. npm package `pakdatakit` (version 2.2.0, data checked 2026-10-14).
Install with `npm install pakdatakit`. Import everything from `pakdatakit`, or one module from a subpath such as `pakdatakit/cnic` to keep bundles small. Functions return `null` (or `'Unknown'` for networks) when input is invalid, and throw `RangeError` only for programming errors such as a non-finite amount. Never log or store real CNICs or account numbers in examples.
Key facts an assistant should know:
- Raast person-to-person QR codes use scheme `00` (banking apps reject the `30` in SBP's document). A QR with an amount needs `expiresAt` or `expiresIn`.
- Bank codes are checked against real IBANs; see DATA.md for every dataset's sources.
- Islamic holiday dates for 2020-2026 are the dates actually observed in Pakistan; later years are estimates marked `estimated: true`.
## Modules
- [pakdatakit/cnic](https://chmuzamil.github.io/pakdatakit/modules/cnic.html): Validate CNICs; read province of registration and gender (validateCNIC, getCNICInfo)
- [pakdatakit/phone](https://chmuzamil.github.io/pakdatakit/modules/phone.html): Validate and format mobile numbers; network and E.164 (validatePhone, formatPhone, analyzePhone)
- [pakdatakit/networks](https://chmuzamil.github.io/pakdatakit/modules/networks.html): Mobile network from the 03XX prefix (getNetwork)
- [pakdatakit/numbers](https://chmuzamil.github.io/pakdatakit/modules/numbers.html): Landline, UAN 111, toll-free 0800, premium 0900 numbers, area codes (analyzeNumber, getAreaCode)
- [pakdatakit/whatsapp](https://chmuzamil.github.io/pakdatakit/modules/whatsapp.html): WhatsApp click-to-chat links (createWhatsAppLink)
- [pakdatakit/iban](https://chmuzamil.github.io/pakdatakit/modules/iban.html): Validate IBANs; bank name, type and status (validateIBAN, getBankFromIBAN, getBankInfo, getBanks)
- [pakdatakit/raast](https://chmuzamil.github.io/pakdatakit/modules/raast.html): Create, read and check Raast payment QR codes (createRaastP2PQr, createMerchantQr, parseRaastQr, validateRaastQr)
- [pakdatakit/tax](https://chmuzamil.github.io/pakdatakit/modules/tax.html): FBR NTN and STRN format checks (analyzeNTN, validateNTN, validateSTRN)
- [pakdatakit/input](https://chmuzamil.github.io/pakdatakit/modules/input.html): As-you-type formatters and empty/incomplete/valid/invalid checkers for CNIC, phone and IBAN fields
- [pakdatakit/postal](https://chmuzamil.github.io/pakdatakit/modules/postal.html): Postal code lookups (getPostalCode, getCityByPostalCode, searchPostalCodes)
- [pakdatakit/geo](https://chmuzamil.github.io/pakdatakit/modules/geo.html): City and district to province (getProvince, getDistrictProvince, searchCities)
- [pakdatakit/address](https://chmuzamil.github.io/pakdatakit/modules/address.html): Parse and format Pakistani addresses (parseAddress, formatAddress)
- [pakdatakit/words](https://chmuzamil.github.io/pakdatakit/modules/words.html): Amounts in words with lakh/crore, English and Urdu, cheque style (amountInWords, numberToWords)
- [pakdatakit/format](https://chmuzamil.github.io/pakdatakit/modules/format.html): PKR and lakh/crore formatting (formatPKR, formatLakh)
- [pakdatakit/text](https://chmuzamil.github.io/pakdatakit/modules/text.html): Roman Urdu normalization and Urdu digits (normalizeRomanUrdu, toUrduDigits)
- [pakdatakit/hijri](https://chmuzamil.github.io/pakdatakit/modules/hijri.html): Hijri dates with moon-sighting offset (toHijri, fromHijri, formatHijri, isRamadan)
- [pakdatakit/holidays](https://chmuzamil.github.io/pakdatakit/modules/holidays.html): Federal public holidays with observed Eid dates (getPakistanHolidays, isPakistanHoliday)
- [pakdatakit/zod](https://chmuzamil.github.io/pakdatakit/modules/adapters_zod.html): Zod schemas: pk.cnic(), pk.phone(), pk.iban(), pk.ntn(), pk.strn(), pk.landline()
- [pakdatakit/valibot](https://chmuzamil.github.io/pakdatakit/modules/adapters_valibot.html): Valibot schemas with the same names
- [pakdatakit/yup](https://chmuzamil.github.io/pakdatakit/modules/adapters_yup.html): Yup schemas with the same names
## Guides
- [README](https://chmuzamil.github.io/pakdatakit/index.html): overview and examples for every function
- [Data sources](https://chmuzamil.github.io/pakdatakit/documents/DATA.html): where each dataset comes from and how sure we are
- [Changelog](https://chmuzamil.github.io/pakdatakit/documents/CHANGELOG.html)
## Optional
- [Full text for LLMs](https://chmuzamil.github.io/pakdatakit/llms-full.txt): README, data sources and every type signature in one file
- [Playground](https://pakdatakit-playground.vercel.app/): try the functions in a browser
- [Source code](https://github.com/chmuzamil/pakdatakit)
---
🇵🇰 PakDataKit
The Standard Utility Toolkit for Pakistani Developers
Production-ready TypeScript utilities for CNIC validation, phone parsing, WhatsApp links, IBAN validation, postal lookup, network detection, geographic lookup, and PKR formatting.
---
## 🌐 Playground
Try PakDataKit in your browser, no installation needed:
👉 **[pakdatakit-playground.vercel.app](https://pakdatakit-playground.vercel.app/)**
Validate CNICs, analyze phone numbers, detect networks, build WhatsApp links, check IBANs and banks, look up postal codes, cities and provinces, format PKR amounts, and try the text and Hijri utilities. Use it to preview outputs before you integrate the library.
---
## 📦 Packages
| Package | Install | For |
| --------------------------------------------------------------------------------- | ------------------------------ | -------------------------------------------------------------- |
| [`pakdatakit`](https://www.npmjs.com/package/pakdatakit) | `npm install pakdatakit` | JavaScript and TypeScript (Node, browsers, Bun, Deno) |
| [`pakdatakit` (Python)](https://github.com/chmuzamil/pakdatakit/tree/main/python) | `pip install pakdatakit` | Python 3.9+, same functions in snake_case, same data |
| [`pakdatakit-react`](https://github.com/chmuzamil/pakdatakit/tree/main/react) | `npm install pakdatakit-react` | React inputs that format as you type, and a Raast QR component |
The Python package is checked against the TypeScript library with over 12,000 shared test cases, so both give the same results.
---
## 📚 Documentation
- **[API reference](https://chmuzamil.github.io/pakdatakit/)**: every function, option and return type
- **[llms.txt](https://chmuzamil.github.io/pakdatakit/llms.txt)** and **[llms-full.txt](https://chmuzamil.github.io/pakdatakit/llms-full.txt)**: the library in a form AI coding assistants can read
- **[DATA.md](./DATA.md)**: where every dataset comes from
---
## Why PakDataKit?
Every Pakistani application eventually needs to solve the same problems:
- Validate CNIC numbers
- Normalize mobile numbers
- Detect mobile networks
- Generate WhatsApp click-to-chat links
- Validate Pakistani IBANs and detect banks
- Look up postal codes and cities
- Map cities to provinces
- Format PKR values in Lakh and Crore conventions
Most teams implement these repeatedly.
PakDataKit provides a single, typed, reusable solution so you can focus on building your product instead of rebuilding common utilities.
---
## Features
- ✅ CNIC Validation
- ✅ CNIC Province & Gender Decoding
- ✅ Phone Number Validation
- ✅ Phone Number Normalization
- ✅ Phone Intelligence (`analyzePhone`)
- ✅ WhatsApp Link Generator
- ✅ IBAN Validation
- ✅ Bank Detection from IBAN
- ✅ Postal Code Lookup
- ✅ Address Parsing
- ✅ Roman Urdu Normalization
- ✅ Hijri Date Utilities
- ✅ CLI Tool
- ✅ Mobile Network Detection
- ✅ City → Province Lookup
- ✅ PKR Formatting
- ✅ Lakh / Crore Formatting
- ✅ Amounts in Words (English & Urdu, cheque style)
- ✅ Zod, Valibot & Yup Form Validation
- ✅ As-You-Type Formatting for CNIC, Phone & IBAN
- ✅ NTN & STRN Validation
- ✅ Landline, UAN (111), Toll-Free (0800) & Premium (0900) Numbers
- ✅ Raast QR Codes (SBP standard)
- ✅ Pakistan Public Holidays (moon-sighting aware)
### Developer Friendly
- Fully Typed (TypeScript)
- Zero Runtime Dependencies (form libraries are optional peers)
- Tree-Shakeable
- Lightweight & Fast
- ESM and CommonJS, with per-module subpath imports
- Production Ready
---
## Installation
```bash
npm install pakdatakit
```
Requirements:
- Node.js 20+
- Works with both `import` and `require()`
Import everything from `pakdatakit`, or a single module to keep bundles small:
```ts
import { validateCNIC } from 'pakdatakit/cnic';
const { analyzePhone } = require('pakdatakit/phone');
```
Subpaths: `address`, `cnic`, `format`, `geo`, `hijri`, `holidays`, `iban`, `input`, `networks`, `numbers`, `phone`, `postal`, `raast`, `tax`, `text`, `whatsapp`, `words`, plus the form adapters `zod`, `valibot` and `yup`.
---
## Quick Start
```ts
import {
validateCNIC,
analyzePhone,
createWhatsAppLink,
validateIBAN,
getBankFromIBAN,
getPostalCode,
getCityByPostalCode,
getNetwork,
formatLakh,
} from 'pakdatakit';
validateCNIC('35202-1234567-1');
// true
analyzePhone('03001234567');
// { valid: true, formatted: "+923001234567", network: "Jazz" }
createWhatsAppLink('03001234567', 'Assalamualaikum');
// "https://wa.me/923001234567?text=Assalamualaikum"
validateIBAN('PK36SCBL0000001123456702');
// true
getBankFromIBAN('PK36SCBL0000001123456702');
// "Standard Chartered Bank (Pakistan) Limited"
getPostalCode('Multan');
// "60000"
getCityByPostalCode('60000');
// "Multan"
getNetwork('03001234567');
// "Jazz"
formatLakh(2500000);
// "25 Lakh"
```
### CLI
```bash
npx pakdatakit validate-cnic 35202-1234567-1
npx pakdatakit analyze-phone 03001234567
npx pakdatakit parse-address "House 12, Gulberg III, Lahore, Punjab 54660"
npx pakdatakit hijri
npx pakdatakit normalize-urdu "ap kia ker rhe ho"
```
The CLI exits with status 0 when the input is valid or found, 1 when it is invalid or not found, and 2 for usage errors, so it can be used in scripts. Run `npx pakdatakit --help` for all commands.
---
# API Reference
## CNIC
### validateCNIC()
Validates Pakistani CNIC numbers.
```ts
validateCNIC(cnic: string): boolean
```
Examples:
```ts
validateCNIC('3520212345671');
// true
validateCNIC('35202-1234567-1');
// true
validateCNIC('35202-123456-1');
// false
```
Supports:
- Plain format
- Dashed format (hyphens or en-dashes)
- Space- or dot-separated format
- Urdu digits
Separators must sit between the 5-7-1 digit groups, and the first digit must be a province code (1–8). NADRA does not publish a check-digit algorithm, so this checks structure only.
### getCNICInfo()
Reads the province of registration and gender encoded in a CNIC.
```ts
getCNICInfo(cnic: string): CNICInfo | null
```
```ts
getCNICInfo('35202-1234567-1');
// { digits: "3520212345671", formatted: "35202-1234567-1", province: "Punjab", gender: "male" }
getCNICInfo('invalid');
// null
```
The first digit is the province where the CNIC was first registered, which is not necessarily where the holder lives now. The last digit is odd for male and even for female.
---
## Phone Utilities
### validatePhone()
Validates Pakistani mobile numbers. Accepts `03…`, `+92…`, `0092…`, `+92 (0) 3…` and short `3…` forms with spaces, dashes, dots or Urdu digits. The `03XX` prefix must belong to a mobile operator.
```ts
validatePhone(phone: string): boolean
```
Examples:
```ts
validatePhone('03001234567');
// true
validatePhone('+923001234567');
// true
validatePhone('3001234567');
// true
```
---
### formatPhone()
Converts valid Pakistani mobile numbers to E.164 format.
```ts
formatPhone(phone: string): string | null
```
Examples:
```ts
formatPhone('03001234567');
// "+923001234567"
formatPhone('+92 300-123-4567');
// "+923001234567"
formatPhone('03601234567');
// null (unallocated prefix)
```
---
### analyzePhone()
Runs validation, formatting, and network detection in one call.
```ts
analyzePhone(phone: string): PhoneAnalysis
```
Returns:
```ts
{
valid: boolean;
formatted: string | null;
network: 'Jazz' | 'Zong' | 'Telenor' | 'Ufone' | 'SCO' | 'Unknown';
}
```
Examples:
```ts
analyzePhone('03001234567');
// { valid: true, formatted: "+923001234567", network: "Jazz" }
analyzePhone('invalid');
// { valid: false, formatted: null, network: "Unknown" }
```
---
## WhatsApp Utilities
### createWhatsAppLink()
Generates a WhatsApp click-to-chat link for Pakistani mobile numbers.
```ts
createWhatsAppLink(phone: string, message?: string): string | null
```
Examples:
```ts
createWhatsAppLink('03001234567');
// "https://wa.me/923001234567"
createWhatsAppLink('03001234567', 'Assalamualaikum');
// "https://wa.me/923001234567?text=Assalamualaikum"
```
Returns `null` for invalid phone numbers.
---
## IBAN Utilities
### validateIBAN()
Validates Pakistani IBANs using format and ISO 13616 mod-97 checksum.
```ts
validateIBAN(iban: string): boolean
```
Examples:
```ts
validateIBAN('PK36SCBL0000001123456702');
// true
validateIBAN('PK37SCBL0000001123456702');
// false
```
---
### getBankFromIBAN()
Returns the bank name from a valid Pakistani IBAN's 4-letter bank code.
```ts
getBankFromIBAN(iban: string): string | null
```
Examples:
```ts
getBankFromIBAN('PK36SCBL0000001123456702');
// "Standard Chartered Bank (Pakistan) Limited"
getBankFromIBAN('invalid');
// null
```
Supported bank codes include HABB, MEZN, SCBL, UNIL, BAHL, NBPA, MUCB, JSBL, SADA (SadaPay), NAYA (NayaPay), RQMI (Raqami), MSHQ (Mashreq), and 48 total entries in the dataset, checked against real IBANs (see [DATA.md](./DATA.md)). Merged or renamed banks are labelled with their current name, e.g. `SAUD` → "Silkbank Limited (merged into United Bank Limited)".
---
## Postal Code Utilities
### getCityByPostalCode()
Returns the city for a Pakistani postal code.
```ts
getCityByPostalCode(postalCode: string): string | null
```
Examples:
```ts
getCityByPostalCode('60000');
// "Multan"
getCityByPostalCode('99999');
// null
```
---
### getPostalCode()
Returns the primary postal code for a Pakistani city (case-insensitive).
```ts
getPostalCode(city: string): string | null
```
Examples:
```ts
getPostalCode('Multan');
// "60000"
getPostalCode('karachi');
// "74200"
```
---
## Mobile Networks
### getNetwork()
Detects network using the original mobile prefix.
```ts
getNetwork(phone: string): NetworkName
```
Returns:
- Jazz
- Zong
- Ufone
- Telenor
- SCO (0355, AJK and Gilgit-Baltistan)
- Unknown
Examples:
```ts
getNetwork('03001234567');
// Jazz
getNetwork('03151234567');
// Zong
getNetwork('03331234567');
// Ufone
getNetwork('03451234567');
// Telenor
```
> Note: This is the operator the prefix was originally allocated to. With Mobile Number Portability (MNP), and Telenor Pakistan's 2026 merger into PTML (Ufone), the current carrier may differ.
---
## Landline, UAN & Service Numbers
### analyzeNumber() / getNumberType() / validateLandline() / getAreaCode()
Identifies any Pakistani number: mobile, landline, UAN (`111`), toll-free (`0800`), premium-rate (`0900`) or personal (`0122`). Uses the same number patterns as Google's libphonenumber, plus PTCL's area-code list.
```ts
analyzeNumber('021-111-825-888');
// { type: 'uan', formatted: '021-111-825-888', e164: '+9221111825888', areaCode: '021', cities: ['Karachi'] }
analyzeNumber('042 35761234');
// { type: 'landline', formatted: '042-35761234', e164: '+924235761234', areaCode: '042', cities: ['Lahore'] }
getNumberType('0800-12345'); // 'toll-free'
validateLandline('051-9260138'); // true
getAreaCode('Mardan'); // '0937'
```
---
## Geography
### getProvince()
Returns province for a city.
```ts
getProvince(city: string): string | null
```
Examples:
```ts
getProvince('Multan');
// Punjab
getProvince('Karachi');
// Sindh
getProvince('Unknown');
// null
```
---
### searchCities()
Case-insensitive city search.
```ts
searchCities(query: string): string[]
```
Examples:
```ts
searchCities('mul');
// ["Multan"]
searchCities('kar');
// ["Karachi"]
```
---
### getDistrictProvince() / searchDistricts()
District-level geography lookup across 177 districts, including the 2026 Balochistan and Khyber Pakhtunkhwa changes. Former names such as Quetta and South Waziristan still resolve.
```ts
getDistrictProvince('Lahore'); // "Punjab"
searchDistricts('karachi'); // ["Karachi Central", "Karachi East", ...]
```
---
## Address Utilities
### parseAddress()
Parses free-form Pakistani addresses into structured components.
```ts
parseAddress('House 12, Gulberg III, Lahore, Punjab 54660');
// { street: "House 12", area: "Gulberg III", city: "Lahore", province: "Punjab", postalCode: "54660", warnings: [], ... }
parseAddress('12 Model Town Lahore');
// { street: "12 Model Town", city: "Lahore", province: "Punjab", ... }
parseAddress('House 1, Lahore, Sindh').warnings;
// ["Lahore is in Punjab, but the address says Sindh"]
```
House, plot, street and PO box numbers are not mistaken for postal codes.
### formatAddress()
Formats structured components back into a mailing address string. Only the components you pass are included.
---
## Text Utilities
### normalizeRomanUrdu()
Normalizes informal Roman Urdu spellings (`ap kia ker` → `aap kya kar`).
### toUrduDigits() / toEnglishDigits()
Convert between English, Urdu, and Arabic-Indic digits.
---
## Hijri Calendar
### toHijri() / formatHijri() / isRamadan()
```ts
formatHijri(new Date(2025, 5, 7)); // "10 Dhu al-Hijjah 1446"
isRamadan(new Date()); // true | false
// Shift by whole days to match local moon sighting
formatHijri(new Date(2025, 5, 7), { offset: -1 }); // "9 Dhu al-Hijjah 1446"
```
Uses the tabular ("islamic-civil") calendar. Dates announced in Pakistan follow moon sighting and can differ by a day; use `offset` to adjust. Invalid dates throw a `RangeError`.
---
## Currency Formatting
### formatPKR()
Formats PKR amounts with separators, rounded to whole rupees. Negative amounts keep their sign; `NaN` and `Infinity` throw a `RangeError`.
```ts
formatPKR(amount: number): string
```
Examples:
```ts
formatPKR(150000);
// "Rs. 150,000"
formatPKR(0);
// "Rs. 0"
formatPKR(-1500);
// "-Rs. 1,500"
```
---
### formatLakh()
Formats values using Pakistan's Lakh / Crore numbering system.
```ts
formatLakh(amount: number): string
```
Examples:
```ts
formatLakh(2500000);
// "25 Lakh"
formatLakh(10000000);
// "1 Crore"
formatLakh(50000);
// "Rs. 50,000"
```
---
## Amounts in Words
### amountInWords() / numberToWords()
Writes numbers and rupee amounts in words using the Pakistani scale (hundred, thousand, lakh, crore, arab, kharab), in English or Urdu. Useful for cheques, invoices and receipts.
```ts
amountInWords(amount: number, options?: { language?: "en" | "ur"; cheque?: boolean }): string
numberToWords(n: number, options?: { language?: "en" | "ur" }): string
```
```ts
amountInWords(125000.5);
// "one lakh twenty-five thousand rupees and fifty paisa"
amountInWords(125000, { cheque: true });
// "Rupees One Lakh Twenty-Five Thousand Only"
amountInWords(125000, { language: 'ur' });
// "ایک لاکھ پچیس ہزار روپے"
amountInWords(500.5, { language: 'ur', cheque: true });
// "پانچ سو روپے اور پچاس پیسے صرف"
numberToWords(12_34_56_789);
// "twelve crore thirty-four lakh fifty-six thousand seven hundred eighty-nine"
```
Amounts are rounded to the nearest paisa. Negative or non-finite amounts throw a `RangeError`.
---
## Tax Numbers
### analyzeNTN() / validateNTN() / validateSTRN()
Validates FBR tax numbers. Individuals use their CNIC as their NTN; companies and AOPs have a 7-digit NTN, often written with a check digit as `1234567-8`. STRNs are 13 digits.
```ts
analyzeNTN('0622438-9');
// { type: 'business', digits: '06224389', formatted: '0622438-9' }
analyzeNTN('3520212345671');
// { type: 'individual', digits: '3520212345671', formatted: '35202-1234567-1' }
validateSTRN('17-00-3761-882-19'); // true
```
FBR does not publish its check-digit algorithms, so these check the format only.
---
## Raast QR Codes
### createRaastP2PQr() / createMerchantQr() / parseRaastQr()
Builds and reads payment QR codes following the State Bank of Pakistan's QR standard (Circular Letter No. 1 of 2022). Turn the returned string into an image with any QR library.
```ts
// Person-to-person: anyone can pay this IBAN through Raast; the payer enters the amount
createRaastP2PQr({ iban: 'PK36SCBL0000001123456702' });
// With a fixed amount: banking apps require an expiry
const payload = createRaastP2PQr({
iban: 'PK36SCBL0000001123456702',
amount: 1500,
expiresAt: new Date(2026, 10, 13, 23, 59),
});
// …or give how long it works: '30m', '12h', '7d' or milliseconds
createRaastP2PQr({
iban: 'PK36SCBL0000001123456702',
amount: 1500,
expiresIn: '1d',
});
parseRaastQr(payload);
// { kind: 'p2p', dynamic: true, crcValid: true, iban: 'PK36SCBL0000001123456702',
// amount: 1500, expiresAt: '2026-11-13T23:59', ... }
// Merchant QR (EMVCo), with Pakistan-specific bill due dates
createMerchantQr({
merchantAccount: {
tag: '28',
fields: {
/* sub-fields from your bank */
},
},
merchantName: 'Aziz General Store',
city: 'Karachi',
amount: 4250,
billNumber: 'INV-1001',
dueDate: new Date(2026, 10, 25),
amountAfterDueDate: 4500,
});
```
Person-to-person QR codes use the same layout as QR codes from banking apps (Faysal Bank and easypaisa without an amount; JazzCash and Mashreq with one), and scan in the Easypaisa and JazzCash apps. This is handy for accounts whose app cannot generate a receiving QR, such as SadaPay. A QR with an amount must have `expiresAt`: apps reject amount QRs without an expiry, and ignore an amount in a QR that has none.
SBP reserves merchant-account tags 28–30 for Raast but has shared the layout of the fields inside them only with banks, so `createMerchantQr` takes those sub-fields from you. `parseRaastQr` checks the CRC and finds the IBAN in either kind of QR.
### validateRaastQr()
Checks a scanned QR before anyone pays, for example to warn about a QR sticker that was replaced or edited.
```ts
validateRaastQr(scannedText);
// { valid: false, errors: ['expired'], warnings: [], qr: { iban: 'PK36…', amount: 1500, … } }
```
| Errors | Meaning |
| -------------------------- | ------------------------------------------------------------ |
| `not-a-raast-qr` | Not a Pakistani payment QR |
| `bad-checksum` | The QR was damaged or its contents were edited |
| `no-iban` / `invalid-iban` | No IBAN, or one that fails its checksum |
| `expired` | A QR with an amount is past its expiry |
| `missing-expiry` | A QR with an amount has no expiry, so banking apps reject it |
Warnings (payment still works): `amount-ignored` when a static QR carries an amount that apps ignore, and `expires-soon` when it expires within an hour (`expiresSoonMs` changes this). Pass `now` to check against another time.
---
## Public Holidays
### getPakistanHolidays() / isPakistanHoliday() / getHolidaysOn()
Lists Pakistan's federal public holidays. Eid, Ashura and Milad use the dates actually observed after moon sighting (2020 onwards, from Cabinet Division and SBP notices), not a calculation.
```ts
getPakistanHolidays(2025).filter((h) => h.id === 'eid-ul-fitr');
// [{ name: 'Eid ul Fitr', date: '2025-03-31', estimated: false, source: 'https://…' }, … '2025-04-01', '2025-04-02']
isPakistanHoliday('2026-08-14'); // true (Independence Day)
// Future years are estimated and flagged; apply the official announcement when it comes
getPakistanHolidays(2027, {
announced: { 'eid-ul-fitr': ['2027-03-10', '2027-03-11', '2027-03-12'] },
});
```
Fixed holidays follow the year they applied in: Iqbal Day was restored in 2022 and Youm-e-Takbeer added in 2024. Pass `includeChristianHolidays: true` for 26 December. Optional holidays such as Shab-e-Barat and Easter are not included.
---
## Form Validation
Ready-made schemas for [Zod](https://zod.dev), [Valibot](https://valibot.dev) and [Yup](https://github.com/jquense/yup). Install the library you use; PakDataKit does not depend on any of them.
```ts
import { z } from 'zod';
import { pk } from 'pakdatakit/zod';
const CheckoutForm = z.object({
cnic: pk.cnic(),
phone: pk.phone(),
iban: pk.iban({ message: 'Please enter a valid IBAN' }),
});
```
```ts
import * as v from 'valibot';
import { pk } from 'pakdatakit/valibot';
const CheckoutForm = v.object({ cnic: pk.cnic(), phone: pk.phone() });
```
```ts
import { object } from 'yup';
import { pk } from 'pakdatakit/yup';
const CheckoutForm = object({ cnic: pk.cnic().required(), phone: pk.phone() });
```
Available schemas: `pk.cnic()`, `pk.phone()`, `pk.iban()`, `pk.ntn()`, `pk.strn()` and `pk.landline()`. Every factory accepts `{ message }` to replace the default error. The Yup schemas let empty values pass so `.required()` decides whether a field is mandatory.
### As-you-type helpers
Format a field while the user types, and tell a half-typed value apart from a wrong one, so you don't show an error before they've finished.
```ts
import { formatCNICInput, checkCNICInput } from 'pakdatakit/input';
formatCNICInput('352021234'); // "35202-1234"
checkCNICInput('35202-1234'); // "incomplete" — keep typing
checkCNICInput('05202'); // "invalid" — no CNIC starts with 0
checkCNICInput('35202-1234567-1'); // "valid"
```
| Field | Formatter | Checker |
| ------ | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
| CNIC | `formatCNICInput` → `35202-1234567-1` | `checkCNICInput` |
| Mobile | `formatPhoneInput` → `0300-1234567` or `+92 300 1234567` | `checkPhoneInput` (flags an unallocated `03XX` prefix as soon as it's typed) |
| IBAN | `formatIBANInput` → `PK36 SCBL 0000 0011 2345 6702` | `checkIBANInput` |
Checkers return `'empty'`, `'incomplete'`, `'valid'` or `'invalid'`. In React, for example:
```tsx
const [cnic, setCnic] = useState('');
const state = checkCNICInput(cnic);
setCnic(formatCNICInput(e.target.value))}
aria-invalid={state === 'invalid'}
/>;
```
---
# Real-World Example
```ts
import {
validateCNIC,
validatePhone,
formatPhone,
analyzePhone,
createWhatsAppLink,
validateIBAN,
getBankFromIBAN,
getPostalCode,
getCityByPostalCode,
getNetwork,
getProvince,
searchCities,
formatPKR,
formatLakh,
} from 'pakdatakit';
// CNIC validation
const cnicValid = validateCNIC('35202-1234567-1');
// Phone intelligence
const phoneInfo = analyzePhone('03001234567');
// WhatsApp link for ecommerce support
const waLink = createWhatsAppLink('0300 123 4567', 'Assalamualaikum');
// IBAN validation for fintech checkout
const ibanValid = validateIBAN('PK36SCBL0000001123456702');
const bank = getBankFromIBAN('PK36SCBL0000001123456702');
// Postal lookup for shipping forms
const postal = getPostalCode('Multan');
const city = getCityByPostalCode('60000');
// Province lookup
const province = getProvince('Lahore');
// Formatting
const amount = formatPKR(150000);
const budget = formatLakh(2500000);
```
---
# Roadmap
### v0.2 ✅
- Phone Intelligence (`analyzePhone`)
- WhatsApp Link Generator
- IBAN Validation
- Bank Detection from IBAN
- Postal Code Lookup
- Expanded datasets (cities, districts, postal codes, banks)
### v0.3 ✅
- Address Parsing (`parseAddress`, `formatAddress`)
- Expanded Postal Data (`getPostalAreas`, `searchPostalCodes`)
### v0.4 ✅
- Roman Urdu Utilities (`normalizeRomanUrdu`)
- Text Normalization (`normalizeText`, `toUrduDigits`, `toEnglishDigits`)
### v0.5 ✅
- Islamic Calendar Utilities (`toHijri`, `fromHijri`, `formatHijri`)
- Hijri Date Helpers (`getHijriMonthName`, `isRamadan`)
### v1.0 ✅
- CLI Tool (`pakdatakit` command)
- Dataset-backed lookup APIs
- Community Contributions (see CONTRIBUTING.md)
### v1.1 ✅
- Interactive Playground ([pakdatakit-playground.vercel.app](https://pakdatakit-playground.vercel.app/))
### v2.0 ✅
- Bug-fix release; see [CHANGELOG.md](./CHANGELOG.md) for breaking changes
- CommonJS build and subpath imports
- Corrected Hijri dates, datasets and validation
### v2.1 ✅
- CNIC province & gender decoding (`getCNICInfo`)
- Amounts in words in English and Urdu (`amountInWords`, `numberToWords`)
- Zod, Valibot and Yup adapters
- NTN and STRN validation
- Landline, UAN, toll-free and premium numbers
- Raast QR codes
- Public holidays with observed moon-sighting dates
### Planned
- CNIC check-digit validation (pending a publicly documented NADRA algorithm)
- Mobile Number Portability lookup API
---
# Contributing
Contributions are welcome.
Ideas, bug reports, improvements, and pull requests help make PakDataKit better for everyone.
Let's build the standard toolkit for Pakistani developers 🇵🇰
See:
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
---
# License
MIT License
---
Built with ❤️ for Pakistani Developers 🇵🇰
---
# Data sources
PakDataKit's datasets live in `data/` and are generated by
`scripts/generate-data.mjs` (except `networks.json`, `area-codes.json` and
`holidays.json`, which are edited directly). The exported `dataVersion` is the
date the data was last checked.
**Data version: 2026-10-14**
Official bodies (PTA, NADRA, SBP, Pakistan Post) publish few machine-readable
lists, so some entries rest on secondary sources. Each section says how sure we
are. To correct an entry, open an issue or pull request with a source.
## Banks (`banks.json`): IBAN bank codes
SBP does not publish a list of IBAN bank codes, and IBAN codes do not always
match SWIFT BIC prefixes (e.g. Silkbank is `SAUD`). Codes are therefore checked
against real IBANs.
- **Confirmed from real IBANs:** most commercial and microfinance banks, from
SBP's account lists for the 2022 flood relief fund and the 2023 Türkiye–Syria
earthquake relief fund, plus charity donation pages (TCF, Alkhidmat). This
includes `KHBL` (Khushhali), `JCMA` and `WMBL` (Mobilink Bank), `TMFB`
(easypaisa), `FMFB` (HBL Microfinance), `FINC` (ABHI), `UMBL`, `NRSP`,
`APNA`, `BKCH`, `ICBK`, `CITI`, `SUMB` (Bank Makramah), `SAMB`, `SMES` and
`SAUD`.
- **Confirmed from published IBANs on smaller sites:** `SADA` (SadaPay), `NAYA`
(NayaPay).
- **Confirmed from real account IBANs shared by a maintainer:** `RQMI` (Raqami
Islamic Digital Bank) and `MSHQ` (Mashreq Bank Pakistan), both digital banks.
- **Likely, not confirmed by a real IBAN:** `DEUT` (Deutsche Bank), `SBPP`
(State Bank of Pakistan).
- **Not included, because no code could be confirmed:** HugoBank (in pilot,
not yet issuing IBANs), KT Bank (reportedly renamed Buraq Bank), LOLC Microfinance
(formerly Pak Oman), Zarai Taraqiati Bank and Bank of AJK.
- **Merged or renamed** banks keep their code with the current name, e.g.
`SAUD` → "Silkbank Limited (merged into United Bank Limited)".
## Mobile networks (`networks.json`)
`03XX` prefixes and the operator each was originally allocated to. PTA does not
publish an allocation table; prefixes are cross-checked with Google
libphonenumber and operator announcements.
- `0329` (Jazz) and `0355` (SCO) included.
- `0370`–`0371` (Zong) included; `0372`–`0379` are **not**, as no allocation or
use was found.
- `0364` (Instaphone, licence cancelled in 2008) is not included.
- `034x` is labelled Telenor, the original allocation; Telenor Pakistan merged
into PTML (Ufone) in 2026. With number portability, the current operator can
differ for any prefix.
## Landline area codes (`area-codes.json`)
From PTCL's national dialling-code list, cross-checked with libphonenumber's
geocoding data. Muzaffarabad, Mirpur and Gilgit codes are left out because only
third-party sources give them. Number patterns in `numbers.ts` come from
libphonenumber's PK metadata (v9.0.41).
## Districts (`districts.json`)
177 districts as of October 2026: Punjab 41, Sindh 30, Khyber Pakhtunkhwa 40,
Balochistan 41, Gilgit-Baltistan 14, Azad Jammu and Kashmir 10, Islamabad 1.
- **Khyber Pakhtunkhwa** (APP, January 2026: "increased to 40"): Lower and Upper
South Waziristan (2022), Central Dir and Allai (January 2023), Paharpur
(November 2025), Upper Swat (January 2026).
- **Balochistan** (Revenue Department notification, 8 July 2026; Dawn,
ProPakistani): Quetta split into Quetta East and Quetta West; Dera Bugti
became South Dera Bugti and Upper Dera Bugti became North Dera Bugti; new
Barshore, Tump and Wadh districts; Hub and Usta Muhammad (2022). Taftan is not
included: no notification was found and the official count of 41 is reached
without it.
- **Punjab:** Taunsa, Kot Addu, Murree, Wazirabad and Talagang (2022). A 2026
notification of four more districts was confirmed fake.
- **Sindh:** proposed Karachi district renames (2024) were not found notified,
so current names are kept.
- Former names (Quetta, Dera Bugti, Upper Dera Bugti, South Waziristan) still
resolve in `getDistrictProvince`.
## Cities (`cities.json`)
District headquarters and major towns, plus every city that has a postal code
in `postal-codes.json`, each mapped to its province.
## Postal codes (`postal-codes.json`)
Principal post office codes for major cities (109 entries). Pakistan Post has no
downloadable dataset, only a lookup tool, so coverage is limited.
## Public holidays (`holidays.json`)
Eid ul Fitr, Eid ul Adha, Ashura and Eid Milad un Nabi dates actually observed
in 2020–2026, from Cabinet Division notifications and SBP circulars; each entry
has a `source` URL. Later years are estimated from the tabular Hijri calendar
and marked `estimated: true`. Fixed-date holidays follow the years they applied
in (e.g. Iqbal Day restored in 2022, Youm-e-Takbeer from 2024).
## CNIC
First digit = province of registration, per FBR's tax-ID description filed with
the OECD (1 Khyber Pakhtunkhwa, 2 former FATA, 3 Punjab, 4 Sindh, 5
Balochistan, 6 Islamabad, 7 Gilgit-Baltistan). `8` for Azad Jammu and Kashmir
comes from secondary sources only. Last digit odd = male, even = female.
---
# Type declarations
### address
```ts
/** Parsed components of a Pakistani mailing address. */
interface ParsedAddress {
raw: string;
street: string | null;
area: string | null;
city: string | null;
district: string | null;
province: string | null;
postalCode: string | null;
/** Inconsistencies found while parsing, e.g. a city in a different province. */
warnings: string[];
}
/**
* Parses a free-form Pakistani address into structured components.
*
* Extracts postal codes, matches known cities/districts/provinces from datasets,
* and assigns remaining segments to street and area fields. Works with or
* without commas between components.
*
* @param address - Raw address string
* @returns Structured address components
*/
declare function parseAddress(address: string): ParsedAddress;
/**
* Formats structured address components into a single mailing line.
*
* Only the components provided are included; no postal code is invented.
*
* @param parts - Address components to format
* @returns Formatted address string
*/
declare function formatAddress(parts: Partial & {
raw?: string;
}): string;
export { type ParsedAddress, formatAddress, parseAddress };
```
### cnic
```ts
/**
* Validates a Pakistani CNIC number.
*
* Accepts `3520212345671`, `35202-1234567-1`, `35202 1234567 1` and Urdu digits.
* Separators must sit between the 5-7-1 digit groups and be used consistently.
*
* NADRA does not publish a check-digit algorithm, so this validates structure only.
*
* @param cnic - The CNIC string to validate
* @returns `true` if the CNIC is valid, otherwise `false`
*/
declare function validateCNIC(cnic: string): boolean;
/** Information encoded in a CNIC number. */
interface CNICInfo {
/** The CNIC as 13 digits without separators. */
digits: string;
/** The CNIC in `#####-#######-#` form. */
formatted: string;
/**
* Province where the CNIC was first registered (from the first digit).
* This is not necessarily where the holder lives now.
*/
province: string;
/** Gender encoded in the last digit: odd for male, even for female. */
gender: 'male' | 'female';
}
/**
* Reads the province of registration and gender encoded in a CNIC.
*
* @param cnic - CNIC in any format accepted by {@link validateCNIC}
* @returns Decoded information, or `null` if the CNIC is invalid
*/
declare function getCNICInfo(cnic: string): CNICInfo | null;
export { type CNICInfo, getCNICInfo, validateCNIC };
```
### format
```ts
/**
* Formats a numeric amount as Pakistani Rupees with comma separators,
* rounded to whole rupees. Negative amounts are prefixed with `-`.
*
* @param amount - The amount in PKR
* @returns Formatted string (e.g. `"Rs. 150,000"`, `"-Rs. 500"`)
* @throws {RangeError} If `amount` is `NaN` or infinite
*/
declare function formatPKR(amount: number): string;
/**
* Formats a numeric amount using Pakistani lakh/crore conventions.
*
* - Amounts of 1 crore or more render as `"N Crore"` (e.g. `10000000` → `"1 Crore"`)
* - Amounts of 1 lakh or more render as `"N Lakh"` (e.g. `2500000` → `"25 Lakh"`)
* - Smaller amounts fall back to {@link formatPKR}
*
* Values are rounded to two decimals before the unit is chosen, so
* `9999999` renders as `"1 Crore"` rather than `"100 Lakh"`.
* Negative amounts are prefixed with `-`.
*
* @param amount - The amount in PKR
* @returns Human-readable lakh/crore string
* @throws {RangeError} If `amount` is `NaN` or infinite
*/
declare function formatLakh(amount: number): string;
export { formatLakh, formatPKR };
```
### geo
```ts
/**
* Returns the province for a given Pakistani city name.
*
* Matching is case-insensitive and ignores inconsistent spacing.
* Returns `null` when no exact city match is found.
*
* @param city - The city name to look up
* @returns Province name or `null` if not found
*/
declare function getProvince(city: string): string | null;
/**
* Searches cities by partial, case-insensitive name match.
*
* @param query - Partial city name to search for
* @returns Alphabetically sorted list of matching city names
*/
declare function searchCities(query: string): string[];
/**
* Returns the province for a given Pakistani district name.
*
* Matching is case-insensitive and ignores inconsistent spacing.
* Returns `null` when no exact district match is found.
*
* @param district - The district name to look up
* @returns Province name or `null` if not found
*/
declare function getDistrictProvince(district: string): string | null;
/**
* Searches districts by partial, case-insensitive name match.
*
* @param query - Partial district name to search for
* @returns Alphabetically sorted list of matching district names
*/
declare function searchDistricts(query: string): string[];
/**
* Returns all districts for a given province (case-insensitive).
*
* @param province - Province name to filter by
* @returns Alphabetically sorted district names
*/
declare function getDistrictsByProvince(province: string): string[];
export { getDistrictProvince, getDistrictsByProvince, getProvince, searchCities, searchDistricts };
```
### hijri
```ts
/** Islamic (Hijri) date components. */
interface HijriDate {
year: number;
month: number;
day: number;
}
/** Options for Hijri conversions. */
interface HijriOptions {
/**
* Whole days to shift the tabular calendar by, to match local moon sighting.
* For example, `-1` when Pakistan's Ruet-e-Hilal calendar runs a day behind
* the tabular date. Defaults to `0`.
*/
offset?: number;
}
/**
* Converts a Gregorian `Date` to a Hijri (Islamic) date.
*
* Uses the tabular (arithmetic, "islamic-civil") calendar. Actual dates in
* Pakistan follow moon sighting and can differ by a day or two; pass
* `options.offset` to adjust.
*
* @param date - Gregorian date (local date components are used)
* @param options - Optional day offset for moon-sighting adjustment
* @returns Hijri date components
* @throws {RangeError} If `date` is invalid or `offset` is not an integer
*/
declare function toHijri(date: Date, options?: HijriOptions): HijriDate;
/**
* Converts a Hijri date to a Gregorian `Date` (local midnight).
*
* @param hijri - Hijri date components
* @param options - Optional day offset, matching the one used with {@link toHijri}
* @returns Gregorian date
* @throws {RangeError} If the Hijri components are out of range
*/
declare function fromHijri(hijri: HijriDate, options?: HijriOptions): Date;
/**
* Formats a Gregorian date as a Hijri date string.
*
* @param date - Gregorian date
* @param options - Optional day offset for moon-sighting adjustment
* @returns Formatted Hijri string (e.g. `"14 Ramadan 1447"`)
*/
declare function formatHijri(date: Date, options?: HijriOptions): string;
/**
* Returns the English name of a Hijri month (1–12).
*
* @param month - Hijri month number
* @returns Month name, or `null` if `month` is not an integer from 1 to 12
*/
declare function getHijriMonthName(month: number): string | null;
/**
* Returns whether a Gregorian date falls in the month of Ramadan.
*
* @param date - Gregorian date
* @param options - Optional day offset for moon-sighting adjustment
* @returns `true` during Ramadan
*/
declare function isRamadan(date: Date, options?: HijriOptions): boolean;
export { type HijriDate, type HijriOptions, formatHijri, fromHijri, getHijriMonthName, isRamadan, toHijri };
```
### holidays
```ts
/** Islamic public holidays, whose dates depend on moon sighting. */
type IslamicHolidayId = 'eid-ul-fitr' | 'eid-ul-adha' | 'ashura' | 'eid-milad-un-nabi';
/** Identifier of a Pakistani federal public holiday. */
type HolidayId = IslamicHolidayId | 'kashmir-day' | 'pakistan-day' | 'labour-day' | 'youm-e-takbeer' | 'independence-day' | 'iqbal-day' | 'quaid-e-azam-day' | 'day-after-christmas';
/** A public holiday on a specific date. */
interface Holiday {
id: HolidayId;
name: string;
/** Date as `YYYY-MM-DD`. */
date: string;
type: 'national' | 'religious';
/**
* `true` when an Islamic holiday's date is calculated from the tabular Hijri
* calendar because the observed dates are not in the dataset yet. The real
* date depends on the moon-sighting announcement.
*/
estimated: boolean;
/** `true` for holidays that apply to Christian employees only. */
christiansOnly?: boolean;
/** Source of an observed Islamic holiday date. */
source?: string;
}
/** Options for {@link getPakistanHolidays}. */
interface HolidayOptions {
/** Include 26 December, a holiday for Christian employees only. Defaults to `false`. */
includeChristianHolidays?: boolean;
/**
* Observed or announced dates (`YYYY-MM-DD`) for Islamic holidays, used
* instead of the dataset or estimate. Use this to apply a new moon-sighting
* announcement before the library is updated. An empty list removes that
* holiday for the year.
*/
announced?: Partial>;
/** Day offset applied to estimated Islamic dates. Defaults to `0`. */
hijriOffset?: number;
}
/**
* Lists Pakistan's federal public holidays for a year, sorted by date.
*
* Islamic holidays use the dates actually observed after moon sighting
* (2020 onwards, from Cabinet Division and SBP notifications). For years not
* yet in the dataset they are estimated from the tabular Hijri calendar and
* marked `estimated: true`; pass `announced` to use official dates instead.
*
* Optional holidays (e.g. Shab-e-Barat, Easter, Youm-e-Ali) are not included.
*
* @example
* getPakistanHolidays(2025).find((h) => h.id === 'eid-ul-fitr');
* // { id: 'eid-ul-fitr', name: 'Eid ul Fitr', date: '2025-03-31', type: 'religious', estimated: false, ... }
*
* @param year - Gregorian year
* @param options - Christian holidays, announced dates and estimate offset
* @returns Holidays in date order
*/
declare function getPakistanHolidays(year: number, options?: HolidayOptions): Holiday[];
/**
* Returns the public holidays that fall on a date.
*
* @param date - A `Date` (local calendar date) or `YYYY-MM-DD` string
* @param options - Same options as {@link getPakistanHolidays}
* @returns Holidays on that date; empty if it is not a public holiday
*/
declare function getHolidaysOn(date: Date | string, options?: HolidayOptions): Holiday[];
/**
* Returns whether a date is a Pakistani federal public holiday.
*
* @param date - A `Date` (local calendar date) or `YYYY-MM-DD` string
* @param options - Same options as {@link getPakistanHolidays}
* @returns `true` on a public holiday
*/
declare function isPakistanHoliday(date: Date | string, options?: HolidayOptions): boolean;
export { type Holiday, type HolidayId, type HolidayOptions, type IslamicHolidayId, getHolidaysOn, getPakistanHolidays, isPakistanHoliday };
```
### iban
```ts
/** Kind of institution, following the State Bank of Pakistan's grouping. */
type BankType = 'commercial' | 'islamic' | 'microfinance' | 'digital' | 'emi' | 'foreign' | 'specialized' | 'central';
/** A bank (or other institution) that issues Pakistani IBANs. */
interface BankInfo {
/** 4-letter IBAN bank code (characters 5–8 of the IBAN). */
code: string;
name: string;
/**
* `commercial`, `islamic` (full-fledged Islamic bank), `microfinance`,
* `digital` (digital retail bank), `emi` (electronic money institution such
* as a wallet), `foreign` (branch of a foreign bank), `specialized` or
* `central` (State Bank of Pakistan).
*/
type: BankType;
/** `false` for banks that merged into another and no longer exist. */
active: boolean;
/** Code of the bank this one merged into, if it no longer exists. */
mergedInto: string | null;
/** Main code of the same bank, when this is an older code still in use. */
aliasOf: string | null;
}
/**
* Validates a Pakistani IBAN using format and ISO 13616 mod-97 checksum.
*
* Accepts spaced or unspaced input. Only `PK` IBANs of exactly 24 characters
* are supported.
*
* @param iban - IBAN string to validate
* @returns `true` if valid, otherwise `false`
*/
declare function validateIBAN(iban: string): boolean;
/**
* Returns the bank name for a valid Pakistani IBAN based on its 4-letter bank code.
*
* Returns `null` when the IBAN is invalid or the bank code is not in the dataset.
*
* @param iban - IBAN string to look up
* @returns Bank name or `null` if not found
*/
declare function getBankFromIBAN(iban: string): string | null;
/**
* Returns details of the bank that issued an IBAN: name, type (commercial,
* Islamic, microfinance, digital bank, wallet …) and whether it still exists.
*
* Also accepts a 4-letter bank code on its own.
*
* @example
* getBankInfo('PK36SCBL0000001123456702');
* // { code: 'SCBL', name: 'Standard Chartered Bank (Pakistan) Limited',
* // type: 'commercial', active: true, mergedInto: null, aliasOf: null }
* getBankInfo('SADA')?.type; // 'emi'
*
* @param ibanOrCode - A valid Pakistani IBAN, or a bank code such as `MEZN`
* @returns Bank details, or `null` if the IBAN is invalid or the code unknown
*/
declare function getBankInfo(ibanOrCode: string): BankInfo | null;
/**
* Lists every bank code in the dataset, sorted by code. Filter it for a
* bank picker, e.g. `getBanks().filter((b) => b.active && !b.aliasOf)`.
*
* @returns All banks, including merged banks and older alias codes
*/
declare function getBanks(): BankInfo[];
export { type BankInfo, type BankType, getBankFromIBAN, getBankInfo, getBanks, validateIBAN };
```
### input
```ts
/**
* State of a form field while the user is typing.
*
* - `empty`: nothing entered yet
* - `incomplete`: what has been typed so far can still become valid
* - `valid`: complete and valid
* - `invalid`: cannot become valid by typing more (wrong character, prefix or length)
*/
type InputState = 'empty' | 'incomplete' | 'valid' | 'invalid';
/**
* Formats a CNIC as the user types: `35202-1234567-1`.
*
* Keeps up to 13 digits (Urdu digits are converted) and inserts the dashes
* as the groups fill. Other characters are dropped.
*
* @param value - Current input value
* @returns Value to show in the input
*/
declare function formatCNICInput(value: string): string;
/**
* Checks a CNIC while the user is typing. See {@link InputState}.
*
* @example
* checkCNICInput('35202-12'); // 'incomplete'
* checkCNICInput('35202-1234567-1'); // 'valid'
* checkCNICInput('05202'); // 'invalid' (no province starts with 0)
*/
declare function checkCNICInput(value: string): InputState;
/**
* Formats a Pakistani mobile number as the user types: `0300-1234567`, or
* `+92 300 1234567` when the user starts with the country code.
*
* @param value - Current input value
* @returns Value to show in the input
*/
declare function formatPhoneInput(value: string): string;
/**
* Checks a Pakistani mobile number while the user is typing. The `03XX`
* prefix is checked as soon as it is complete. See {@link InputState}.
*
* @example
* checkPhoneInput('0300 12'); // 'incomplete'
* checkPhoneInput('0360'); // 'invalid' (prefix not allocated)
* checkPhoneInput('03001234567'); // 'valid'
*/
declare function checkPhoneInput(value: string): InputState;
/**
* Formats an IBAN as the user types: uppercase, in groups of four
* (`PK36 SCBL 0000 0011 2345 6702`), up to 24 characters.
*
* @param value - Current input value
* @returns Value to show in the input
*/
declare function formatIBANInput(value: string): string;
/**
* Checks a Pakistani IBAN while the user is typing: the `PK` prefix, check
* digits and bank code are checked as they are typed, and the full checksum
* once all 24 characters are in. See {@link InputState}.
*
* @example
* checkIBANInput('PK36 SC'); // 'incomplete'
* checkIBANInput('GB29'); // 'invalid'
* checkIBANInput('PK36 SCBL 0000 0011 2345 6702'); // 'valid'
*/
declare function checkIBANInput(value: string): InputState;
export { type InputState, checkCNICInput, checkIBANInput, checkPhoneInput, formatCNICInput, formatIBANInput, formatPhoneInput };
```
### networks
```ts
/**
* Original issuing operator for a mobile prefix.
*
* `Telenor` is the operator the 034x range was originally allocated to; Telenor
* Pakistan was merged into PTML (Ufone) in 2026. `SCO` is the Special
* Communications Organization (0355, serving AJK and Gilgit-Baltistan).
*/
type NetworkName = 'Jazz' | 'Zong' | 'Telenor' | 'Ufone' | 'SCO' | 'Unknown';
/**
* Detects the mobile network for a Pakistani phone number based on its prefix.
*
* Returns the network the prefix was originally allocated to. Due to Mobile
* Number Portability (MNP) and operator mergers, the current carrier may differ.
*
* @param phone - The phone number to look up
* @returns Network name or `"Unknown"` if the number is not a valid Pakistani mobile number
*/
declare function getNetwork(phone: string): NetworkName;
export { type NetworkName, getNetwork };
```
### numbers
```ts
/** Kind of Pakistani telephone number. */
type NumberType = 'mobile' | 'landline' | 'uan' | 'toll-free' | 'premium' | 'personal';
/** Result of {@link analyzeNumber}. */
interface NumberInfo {
type: NumberType;
/** National format with trunk prefix and separators, e.g. `021-111-825-888`. */
formatted: string;
/** E.164 format, e.g. `+9221111825888`. `null` for a UAN dialled without an area code. */
e164: string | null;
/** Area code with trunk prefix (e.g. `021`) for landlines and UANs. */
areaCode: string | null;
/** Cities served by the area code, when known. */
cities: string[];
}
/**
* Identifies and formats any Pakistani telephone number: mobile, landline,
* UAN (`111`), toll-free (`0800`), premium-rate (`0900`) or personal (`0122`).
*
* @example
* analyzeNumber('021-111-825-888');
* // { type: 'uan', formatted: '021-111-825-888', e164: '+9221111825888',
* // areaCode: '021', cities: ['Karachi'] }
* analyzeNumber('042 35761234');
* // { type: 'landline', formatted: '042-35761234', e164: '+924235761234',
* // areaCode: '042', cities: ['Lahore'] }
*
* @param phone - Number in local or international format
* @returns Number details, or `null` if it is not a valid Pakistani number
*/
declare function analyzeNumber(phone: string): NumberInfo | null;
/**
* Returns the type of a Pakistani telephone number. See {@link analyzeNumber}.
*
* @param phone - Number in local or international format
* @returns Number type, or `null` if it is not a valid Pakistani number
*/
declare function getNumberType(phone: string): NumberType | null;
/**
* Validates a Pakistani landline (fixed-line) number such as `042-35761234`.
*
* @param phone - Number in local or international format
* @returns `true` for a valid landline number
*/
declare function validateLandline(phone: string): boolean;
/**
* Returns the landline area code for a city, with trunk prefix (e.g. `"042"`).
*
* @param city - City name (case-insensitive)
* @returns Area code, or `null` if the city is not in the dataset
*/
declare function getAreaCode(city: string): string | null;
export { type NumberInfo, type NumberType, analyzeNumber, getAreaCode, getNumberType, validateLandline };
```
### phone
```ts
import { NetworkName } from './networks.js';
/**
* Normalizes a phone string to digits with country code (no leading plus).
*
* Accepts local (`0300…`), international (`+92300…`, `0092300…`), short
* (`300…`) and the common `+92 (0) 300…` / `+920300…` forms, with spaces,
* dashes, dots, slashes or parentheses, and Urdu or Arabic-Indic digits.
*
* Returns `null` when the input is not a Pakistani mobile number on an
* allocated `03XX` prefix.
*/
declare function normalizePhoneDigits(phone: string): string | null;
/**
* Returns the local Pakistani mobile prefix (`0XXX`) from a phone number.
* Returns `null` when the phone cannot be normalized.
*/
declare function getPhonePrefix(phone: string): string | null;
/** Combined phone validation, formatting, and network analysis result. */
interface PhoneAnalysis {
valid: boolean;
formatted: string | null;
network: NetworkName;
}
/**
* Validates a Pakistani mobile phone number.
*
* Accepts local (`03001234567`), international (`+923001234567`, `+92 (0) 300…`),
* and short (`3001234567`) formats, with common separators and Urdu digits.
* The `03XX` prefix must be allocated to a mobile operator.
*
* @param phone - The phone number string to validate
* @returns `true` if valid, otherwise `false`
*/
declare function validatePhone(phone: string): boolean;
/**
* Formats a Pakistani mobile number to international E.164 style (`+923001234567`).
*
* @param phone - The phone number string to format
* @returns Formatted phone number, or `null` if invalid
*/
declare function formatPhone(phone: string): string | null;
/**
* Analyzes a Pakistani mobile number in a single call.
*
* Combines validation, E.164 formatting, and network detection.
* Invalid numbers return `{ valid: false, formatted: null, network: "Unknown" }`.
*
* @param phone - Phone number in any supported format
* @returns Structured phone analysis result
*/
declare function analyzePhone(phone: string): PhoneAnalysis;
export { NetworkName, type PhoneAnalysis, analyzePhone, formatPhone, getPhonePrefix, normalizePhoneDigits, validatePhone };
```
### postal
```ts
/**
* Returns the city name for a Pakistani postal code.
*
* @param postalCode - Postal code to look up (whitespace is ignored)
* @returns City name or `null` if not found
*/
declare function getCityByPostalCode(postalCode: string): string | null;
/**
* Returns the primary postal code for a Pakistani city.
*
* Matching is case-insensitive and whitespace-tolerant.
* When multiple postal codes exist, returns the primary GPO code.
*
* @param city - City name to look up
* @returns Postal code or `null` if not found
*/
declare function getPostalCode(city: string): string | null;
/**
* Returns all known postal codes for a city (case-insensitive).
*
* @param city - City name to look up
* @returns Alphabetically sorted postal codes
*/
declare function getPostalCodes(city: string): string[];
/**
* Searches postal entries by city name, area, or postal code prefix.
*
* @param query - Partial search term
* @returns Matching postal code strings, sorted alphabetically
*/
declare function searchPostalCodes(query: string): string[];
/**
* Returns known area names for a city's postal entries.
*
* @param city - City name to look up
* @returns Alphabetically sorted area names
*/
declare function getPostalAreas(city: string): string[];
export { getCityByPostalCode, getPostalAreas, getPostalCode, getPostalCodes, searchPostalCodes };
```
### raast
```ts
/**
* EMVCo Merchant-Presented QR (MPM) encoding: a flat list of
* `ID (2 digits) + length (2 digits) + value` fields, some of which are
* templates containing nested fields, ending with a CRC in field 63.
*/
/** A parsed EMV field. `children` is set for template fields. */
interface EmvField {
id: string;
value: string;
children?: EmvField[];
}
/** A duration such as `'30m'`, `'12h'` or `'7d'` (minutes, hours or days). */
type RaastDuration = `${number}${'m' | 'h' | 'd'}`;
/** Options for {@link createRaastP2PQr}. */
interface RaastP2PQrOptions {
/** Payee's Pakistani IBAN. */
iban: string;
/**
* Scheme identifier (tag 02). Defaults to `'00'`, as used by banking apps.
* `'30'` is the value listed for Raast in SBP's QR standard.
*/
scheme?: '00' | '30';
/**
* Amount in PKR. When set, the QR is dynamic (point of initiation 12) and
* `expiresAt` is required: banking apps reject dynamic QR codes without an
* expiry, and ignore the amount in static ones.
*/
amount?: number;
/**
* When a QR with an amount stops working (tag 07). With `amount`, give this
* or `expiresIn`.
*/
expiresAt?: Date;
/**
* How long from now a QR with an amount works, instead of `expiresAt`:
* milliseconds, or a string such as `'30m'`, `'12h'` or `'7d'`.
*/
expiresIn?: number | RaastDuration;
/** Payee's bank name (up to 25 characters). */
bankName?: string;
/** Payment description (up to 30 characters). */
particulars?: string;
}
/** Account details for a merchant QR, as issued by the merchant's bank. */
interface MerchantAccountInfo {
/** Template tag. SBP reserves `28`–`30` for Raast. Defaults to `28`. */
tag?: string;
/** Sub-fields of the template, keyed by 2-digit ID, in the order given. */
fields: Record;
}
/** Options for {@link createMerchantQr}. */
interface MerchantQrOptions {
/** Raast merchant account template. Its sub-fields are defined by the bank. */
merchantAccount: MerchantAccountInfo;
/** Merchant name (up to 25 characters). */
merchantName: string;
/** Merchant city (up to 15 characters). */
city: string;
/** ISO 18245 merchant category code. Defaults to `0000`. */
mcc?: string;
/** Amount in PKR. When set, the QR is dynamic unless `dynamic` is `false`. */
amount?: number;
/** Overrides the point of initiation (static `11` / dynamic `12`). */
dynamic?: boolean;
/** Bill or invoice number (tag 62-01, up to 25 characters). */
billNumber?: string;
/** Customer mobile number (tag 62-02, up to 15 characters). */
mobileNumber?: string;
/** Reference label (tag 62-05, up to 25 characters). */
reference?: string;
/** Payment due date (Pakistan-specific tag 62-50). Requires `amountAfterDueDate`. */
dueDate?: Date;
/** Amount payable after the due date (Pakistan-specific tag 62-51). */
amountAfterDueDate?: number;
}
/** Result of {@link parseRaastQr}. */
interface ParsedRaastQr {
/** `p2p` for person-to-person QR codes, `merchant` for EMVCo merchant QR codes. */
kind: 'p2p' | 'merchant';
/** Whether the QR carries a fixed amount (point of initiation 12). */
dynamic: boolean;
/** Whether the CRC checksum matches. */
crcValid: boolean;
/** Payee IBAN, if one is present. */
iban: string | null;
/** Whether `iban` passes the IBAN checksum. */
ibanValid: boolean;
amount: number | null;
/** P2P: expiry of a dynamic QR as local `YYYY-MM-DDTHH:mm`. */
expiresAt: string | null;
/** P2P: bank name. */
bankName: string | null;
/** P2P: payment description. */
particulars: string | null;
merchantName: string | null;
city: string | null;
mcc: string | null;
billNumber: string | null;
/** Due date as `YYYY-MM-DD`. */
dueDate: string | null;
amountAfterDueDate: number | null;
/** All top-level fields, with template fields parsed into `children`. */
fields: EmvField[];
}
/**
* Creates a Raast person-to-person QR payload in SBP's P2P QR format (payload
* format `02`), matching the QR codes banking apps generate, so it can be
* scanned by apps such as Easypaisa and JazzCash. Encode the string as a QR
* code with any QR library.
*
* A QR with an `amount` is dynamic and needs `expiresAt`; this layout
* (`01` = `12`, amount in tag 05, expiry in tag 07) matches dynamic QR codes
* from banking apps and scans in Easypaisa and JazzCash.
*
* @example
* createRaastP2PQr({ iban: 'PK36SCBL0000001123456702' });
* createRaastP2PQr({ iban: 'PK36SCBL0000001123456702', amount: 1500, expiresIn: '1d' });
*
* @param options - Payee IBAN and optional amount with expiry, bank name and description
* @returns QR payload string
* @throws {RangeError} If the IBAN is invalid, a field is too long, or an
* amount is given without `expiresAt` or `expiresIn`
*/
declare function createRaastP2PQr(options: RaastP2PQrOptions): string;
/**
* Creates an EMVCo merchant-presented QR payload following SBP's Pakistan QR
* standard (currency `586`, country `PK`, CRC in tag 63), including the
* Pakistan-specific due-date fields.
*
* SBP leaves the layout of the Raast merchant account template to the scheme
* and has shared it only with banks, so pass the template sub-fields your bank
* gives you in `merchantAccount`.
*
* @param options - Merchant account template, name, city and payment details
* @returns QR payload string
* @throws {RangeError} If a field is missing, too long, or out of range
*/
declare function createMerchantQr(options: MerchantQrOptions): string;
/**
* Parses a Raast QR payload: either a person-to-person QR (payload format
* `02`) or an EMVCo merchant QR (payload format `01`), and verifies its CRC.
*
* @param payload - Text decoded from the QR code
* @returns Parsed details, or `null` if the payload is not a well-formed Pakistani QR code
*/
declare function parseRaastQr(payload: string): ParsedRaastQr | null;
/** A problem that stops a Raast QR from working or makes it unsafe to pay. */
type RaastQrError = 'not-a-raast-qr' | 'bad-checksum' | 'no-iban' | 'invalid-iban' | 'expired' | 'missing-expiry';
/** Something worth telling the payer, but which does not stop payment. */
type RaastQrWarning = 'amount-ignored' | 'expires-soon';
/** Result of {@link validateRaastQr}. */
interface RaastQrValidation {
/** `true` when there are no errors. */
valid: boolean;
errors: RaastQrError[];
warnings: RaastQrWarning[];
/** The parsed QR, or `null` if it could not be read. */
qr: ParsedRaastQr | null;
}
/** Options for {@link validateRaastQr}. */
interface ValidateRaastQrOptions {
/** The time to check expiry against. Defaults to now. */
now?: Date;
/** Warn when a QR expires within this many milliseconds. Defaults to 1 hour. */
expiresSoonMs?: number;
}
/**
* Checks whether a Raast QR code can be paid, e.g. before showing a payment
* screen or to warn about a tampered sticker.
*
* Errors: not a Pakistani payment QR, checksum mismatch (damaged or edited),
* no IBAN or an IBAN that fails its checksum, expired, or a fixed-amount QR
* without the expiry that banking apps require.
* Warnings: an amount in a static QR (apps ignore it, so the payer must type
* it), or a QR that expires soon (within an hour by default).
*
* @example
* validateRaastQr(scannedText);
* // { valid: false, errors: ['expired'], warnings: [], qr: { ... } }
*
* @param payload - Text decoded from the QR code
* @param options - Time to check against and the "expires soon" window
* @returns Validation result with the parsed QR
*/
declare function validateRaastQr(payload: string, options?: ValidateRaastQrOptions): RaastQrValidation;
export { type EmvField, type MerchantAccountInfo, type MerchantQrOptions, type ParsedRaastQr, type RaastDuration, type RaastP2PQrOptions, type RaastQrError, type RaastQrValidation, type RaastQrWarning, type ValidateRaastQrOptions, createMerchantQr, createRaastP2PQr, parseRaastQr, validateRaastQr };
```
### rules-GxgPiVws
```ts
/** Options accepted by every adapter factory. */
interface AdapterOptions {
/** Error message to show instead of the default. */
message?: string;
}
export type { AdapterOptions as A };
```
### tax
```ts
/** Kind of taxpayer an NTN belongs to. */
type NTNType = 'individual' | 'business';
/** Result of {@link analyzeNTN}. */
interface NTNInfo {
/** `individual` when the NTN is a CNIC; `business` for a 7-digit company/AOP NTN. */
type: NTNType;
/** Digits without separators (7, 8 or 13 digits). */
digits: string;
/** Display form: `1234567-8` for business NTNs with a check digit, `#####-#######-#` for CNICs. */
formatted: string;
}
/**
* Analyzes a Pakistani National Tax Number (NTN).
*
* - Individuals use their 13-digit CNIC as their NTN (since the Finance Act 2015).
* - Companies and associations of persons have a 7-digit NTN, often written
* with an 8th check digit as `1234567-8`.
*
* FBR does not publish the check-digit algorithm, so the check digit is not
* verified; this validates the format only.
*
* @param ntn - NTN in any common format
* @returns NTN details, or `null` if the format is invalid
*/
declare function analyzeNTN(ntn: string): NTNInfo | null;
/**
* Validates the format of a Pakistani National Tax Number (NTN): a CNIC for
* individuals, or 7 digits (optionally followed by a check digit) for
* businesses. See {@link analyzeNTN}.
*
* @param ntn - NTN to validate
* @returns `true` if the format is valid
*/
declare function validateNTN(ntn: string): boolean;
/**
* Validates the format of a Sales Tax Registration Number (STRN): 13 digits,
* with optional spaces or dashes.
*
* FBR does not publish a check-digit algorithm or the meaning of the digit
* groups, so this validates the format only.
*
* @param strn - STRN to validate
* @returns `true` if the format is valid
*/
declare function validateSTRN(strn: string): boolean;
export { type NTNInfo, type NTNType, analyzeNTN, validateNTN, validateSTRN };
```
### text
```ts
/**
* Normalizes general text: trims, collapses whitespace, and applies Unicode NFC.
*
* @param text - Input text
* @returns Normalized text
*/
declare function normalizeText(text: string): string;
/**
* Normalizes informal Roman Urdu spellings to common canonical forms.
*
* Also applies {@link normalizeText} and converts Urdu/Arabic digits to English.
* A capitalized word keeps its capital; all-caps words (acronyms such as "AP")
* are left unchanged.
*
* @param text - Roman Urdu input
* @returns Normalized Roman Urdu string
*/
declare function normalizeRomanUrdu(text: string): string;
/**
* Converts English/Arabic-Indic digits in a string to Urdu digits (۰–۹).
*
* @param text - Input text
* @returns Text with Urdu digits
*/
declare function toUrduDigits(text: string): string;
/**
* Converts Urdu/Arabic-Indic digits in a string to English digits (0–9).
*
* @param text - Input text
* @returns Text with English digits
*/
declare function toEnglishDigits(text: string): string;
export { normalizeRomanUrdu, normalizeText, toEnglishDigits, toUrduDigits };
```
### valibot
```ts
import * as v from 'valibot';
import { A as AdapterOptions } from './rules-GxgPiVws.js';
/**
* Valibot schemas for Pakistani identifiers.
*
* @example
* import * as v from 'valibot';
* import { pk } from 'pakdatakit/valibot';
*
* const form = v.object({ cnic: pk.cnic(), phone: pk.phone() });
*/
declare const pk: {
cnic: (options?: AdapterOptions) => v.SchemaWithPipe, v.CheckAction]>;
phone: (options?: AdapterOptions) => v.SchemaWithPipe, v.CheckAction]>;
iban: (options?: AdapterOptions) => v.SchemaWithPipe, v.CheckAction]>;
ntn: (options?: AdapterOptions) => v.SchemaWithPipe, v.CheckAction]>;
strn: (options?: AdapterOptions) => v.SchemaWithPipe, v.CheckAction]>;
landline: (options?: AdapterOptions) => v.SchemaWithPipe, v.CheckAction]>;
};
export { AdapterOptions, pk };
```
### whatsapp
```ts
/**
* Creates a WhatsApp click-to-chat link for a Pakistani mobile number.
*
* Accepts local, international, and short phone formats.
*
* @param phone - Pakistani mobile number in any supported format
* @param message - Optional pre-filled message (URL-encoded)
* @returns WhatsApp wa.me link, or `null` if the phone number is invalid
*/
declare function createWhatsAppLink(phone: string, message?: string): string | null;
export { createWhatsAppLink };
```
### words
```ts
/** Language for number words. */
type WordsLanguage = 'en' | 'ur';
/** Options for {@link numberToWords}. */
interface NumberToWordsOptions {
/** `'en'` (default) or `'ur'`. */
language?: WordsLanguage;
}
/** Options for {@link amountInWords}. */
interface AmountInWordsOptions extends NumberToWordsOptions {
/**
* Cheque style: `"Rupees One Lakh Twenty-Five Thousand Only"` in English,
* or the words followed by `"روپے صرف"` in Urdu.
*/
cheque?: boolean;
}
/**
* Converts a whole number to words using the Pakistani numbering system
* (hundred, thousand, lakh, crore, arab, kharab).
*
* @example
* numberToWords(125000); // "one lakh twenty-five thousand"
* numberToWords(125000, { language: 'ur' }); // "ایک لاکھ پچیس ہزار"
*
* @param n - A safe integer
* @param options - Output language
* @returns The number in words
* @throws {RangeError} If `n` is not a safe integer
*/
declare function numberToWords(n: number, options?: NumberToWordsOptions): string;
/**
* Writes a rupee amount in words, with paisa, as used on Pakistani cheques
* and invoices. Amounts are rounded to the nearest paisa.
*
* @example
* amountInWords(125000.5);
* // "one lakh twenty-five thousand rupees and fifty paisa"
* amountInWords(125000, { cheque: true });
* // "Rupees One Lakh Twenty-Five Thousand Only"
* amountInWords(125000, { language: 'ur' });
* // "ایک لاکھ پچیس ہزار روپے"
*
* @param amount - Amount in PKR (must be zero or more)
* @param options - Output language and cheque style
* @returns The amount in words
* @throws {RangeError} If `amount` is negative, not finite, or too large
*/
declare function amountInWords(amount: number, options?: AmountInWordsOptions): string;
export { type AmountInWordsOptions, type NumberToWordsOptions, type WordsLanguage, amountInWords, numberToWords };
```
### yup
```ts
import * as yup from 'yup';
import { A as AdapterOptions } from './rules-GxgPiVws.js';
/**
* Yup schemas for Pakistani identifiers.
*
* @example
* import { object } from 'yup';
* import { pk } from 'pakdatakit/yup';
*
* const form = object({ cnic: pk.cnic().required(), phone: pk.phone() });
*/
declare const pk: {
cnic: (options?: AdapterOptions) => yup.StringSchema;
phone: (options?: AdapterOptions) => yup.StringSchema;
iban: (options?: AdapterOptions) => yup.StringSchema;
ntn: (options?: AdapterOptions) => yup.StringSchema;
strn: (options?: AdapterOptions) => yup.StringSchema;
landline: (options?: AdapterOptions) => yup.StringSchema;
};
export { AdapterOptions, pk };
```
### zod
```ts
import { z } from 'zod';
import { A as AdapterOptions } from './rules-GxgPiVws.js';
/**
* Zod schemas for Pakistani identifiers.
*
* @example
* import { z } from 'zod';
* import { pk } from 'pakdatakit/zod';
*
* const form = z.object({ cnic: pk.cnic(), phone: pk.phone() });
*/
declare const pk: {
cnic: (options?: AdapterOptions) => z.ZodString;
phone: (options?: AdapterOptions) => z.ZodString;
iban: (options?: AdapterOptions) => z.ZodString;
ntn: (options?: AdapterOptions) => z.ZodString;
strn: (options?: AdapterOptions) => z.ZodString;
landline: (options?: AdapterOptions) => z.ZodString;
};
export { AdapterOptions, pk };
```