npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pk-address-parser

v0.0.3

Published

Parse, normalize and standardize messy Pakistani street addresses into structured fields — house, street, block, sector, phase, area, city, province. Zero-dependency TypeScript address parser for Pakistan e-commerce, logistics, delivery, real-estate and C

Readme

pk-address-parser

Parse, normalize and standardize messy Pakistani addresses into structured fields — house, street, block, sector, phase, unit, landmark, area, city and province — with a small geo helper API (province ↔ city ↔ area lookups) backed by a bundled Pakistan gazetteer. Written in TypeScript, ships with type declarations, and has zero runtime dependencies. It works unchanged in TypeScript, in plain JavaScript with import (ESM), and in plain JavaScript with require (CommonJS) — no build step and no TypeScript toolchain required by the consumer. Everything runs offline; there are no network calls at runtime.

Built for Pakistani e-commerce, logistics, delivery, courier, real-estate and CRM systems that need to turn free-text parse / normalize / standardize input into clean, queryable records.

Badges

npm version license MIT types included zero dependencies

Why

Pakistani e-commerce, delivery/logistics, property and CRM systems constantly deal with inconsistent, free-text addresses:

House 23, Street 4, Block B, Johar Town, Lahore
DHA Phase 6 Lahore
Near Emporium Mall, Johar Town, Lahore

There is no widely-used, typed, offline library that turns these into structured records and resolves the locality / city / province. This package fills that gap.

import {parseAddress} from 'pk-address-parser';

parseAddress({address: 'House 23, Street 4, Block B, Johar Town, Lahore'});
// {
//   house: '23', street: '4', block: 'B',
//   sector: null, phase: null, unit: null, landmark: null,
//   area: 'Johar Town', city: 'Lahore', province: 'Punjab',
//   country: 'Pakistan',
//   raw: 'House 23, Street 4, Block B, Johar Town, Lahore',
//   unmatched: [], confidence: 0.95
// }

Install

npm install pk-address-parser
pnpm add pk-address-parser
yarn add pk-address-parser

Requires Node.js >= 18. No peer dependencies, no runtime dependencies.

Quick start

The package publishes a dual ESM + CommonJS build with .d.ts / .d.cts type declarations. Use whichever module system your project already uses — you do not need TypeScript, a bundler, or a build step to consume it.

TypeScript

import {
  parseAddress,
  normalizeAddress,
  type ParsedAddress,
} from 'pk-address-parser';

const parsed: ParsedAddress = parseAddress({
  address: 'House 23, Street 4, Block B, Johar Town, Lahore',
});

normalizeAddress({address: 'lahore johar town'});
// 'Johar Town, Lahore, Punjab, Pakistan'

Plain JavaScript — ESM (import)

// index.mjs  (or "type": "module" in package.json)
import {parseAddress} from 'pk-address-parser';

console.log(parseAddress({address: 'DHA Phase 6 Lahore'}));
// {
//   house: null, street: null, block: null, sector: null,
//   phase: '6', unit: null, landmark: null,
//   area: 'DHA', city: 'Lahore', province: 'Punjab', country: 'Pakistan',
//   raw: 'DHA Phase 6 Lahore', unmatched: [], confidence: 0.85
// }

Plain JavaScript — CommonJS (require)

// index.cjs  (or a plain CommonJS project)
const {parseAddress, normalizeAddress} = require('pk-address-parser');

console.log(parseAddress({address: 'Near Emporium Mall, Johar Town, Lahore'}));
// {
//   house: null, street: null, block: null, sector: null, phase: null,
//   unit: null, landmark: 'Near Emporium Mall',
//   area: 'Johar Town', city: 'Lahore', province: 'Punjab',
//   country: 'Pakistan', raw: 'Near Emporium Mall, Johar Town, Lahore',
//   unmatched: [], confidence: 0.8
// }

Every function takes a single named-parameters object — never positional arguments. Malformed input never throws: a missing or empty address returns an all-null ParsedAddress with confidence: 0.

API

All exports come from the package root (pk-address-parser).

| Export | Signature | | ------------------ | ------------------------------------------------------------------------- | | parseAddress | ({ address, defaultCity?, defaultProvince?, strict? }) => ParsedAddress | | normalizeAddress | ({ address, defaultCity?, defaultProvince?, strict? }) => string | | getProvince | ({ city }) => string \| null | | getCity | ({ province, city }) => string \| null | | listProvinces | () => string[] | | listCities | ({ province? }) => string[] | | listAreas | ({ city }) => string[] | | isProvince | ({ name }) => boolean | | isCity | ({ name }) => boolean |

Plus the exported types ParsedAddress and ParseAddressParams.

parseAddress({ address, defaultCity?, defaultProvince?, strict? })

Parses a raw address into a ParsedAddress. defaultCity / defaultProvince fill in city / province when the input (and the gazetteer) cannot. With strict: true, an unrecognized leftover segment is not guessed as area — it goes to unmatched instead.

parseAddress({address: 'DHA Phase 6 Lahore'});
// -> { phase: '6', area: 'DHA', city: 'Lahore', province: 'Punjab', ... }

parseAddress({address: 'st 4 gulberg', defaultCity: 'Lahore'});
// -> { street: '4', area: 'Gulberg', city: 'Lahore', province: 'Punjab', ... }

parseAddress({address: 'some unknown colony', strict: true});
// -> { area: null, unmatched: ['some', 'unknown', 'colony'], ... }

house, street, block, sector and phase return just the identifying value ('23', 'B', 'F-8/3'), not the label. unit keeps its label, title-cased ('Flat 3', 'Apartment 12-C', '2nd Floor'). landmark keeps its leading preposition ('Near Emporium Mall'). area / city / province are canonical gazetteer names.

normalizeAddress({ address, defaultCity?, defaultProvince?, strict? })

Parses, then re-emits the non-null parts in a fixed specific → general order as a single comma-separated string ending in Pakistan. If parsing resolves nothing, returns the input trimmed and whitespace-collapsed (never an empty string).

normalizeAddress({address: 'lahore johar town'});
// 'Johar Town, Lahore, Punjab, Pakistan'

normalizeAddress({address: 'h#7 st 12 f-8/3 islamabad'});
// 'House 7, Street 12, Sector F-8/3, Islamabad Capital Territory, Pakistan'

getProvince({ city })

Returns the province for a city, or null if unknown. Case-, whitespace- and alias-insensitive.

getProvince({city: 'Lahore'}); // 'Punjab'
getProvince({city: 'khi'}); // 'Sindh'
getProvince({city: 'Gotham'}); // null

getCity({ province, city })

Returns the canonical city name only if that city exists in that province, otherwise null. Both arguments are alias-aware.

getCity({province: 'Punjab', city: 'lhr'}); // 'Lahore'
getCity({province: 'Sindh', city: 'Lahore'}); // null (not in Sindh)

listProvinces()

Returns all 7 canonical province names in a stable order. Takes no arguments.

listProvinces();
// ['Punjab', 'Sindh', 'Khyber Pakhtunkhwa', 'Balochistan',
//  'Islamabad Capital Territory', 'Azad Jammu & Kashmir', 'Gilgit-Baltistan']

listCities({ province? })

Returns canonical city names, sorted. With province (alias-aware) the list is filtered to that province; an unknown province returns []. With no argument or {}, returns every city.

listCities({province: 'Sindh'}); // ['Badin', 'Dadu', 'Ghotki', 'Hyderabad', ...]
listCities(); // every city in the gazetteer

listAreas({ city })

Returns the canonical locality names known for a city, sorted. [] if the city is unknown or has no localities in the dataset.

listAreas({city: 'Lahore'}); // ['A Block', 'Abbas Lines', 'Abbasabad', ...]

isProvince({ name }) / isCity({ name })

Alias-aware boolean membership tests.

isProvince({name: 'KPK'}); // true
isCity({name: 'Faisalabad'}); // true
isCity({name: 'Gotham'}); // false

ParsedAddress

| Field | Type | Notes | | ------------ | ---------------- | ------------------------------------------------------------------ | | house | string \| null | Value only — '23', '5-A', '23' from #23 / Plot 5 | | street | string \| null | Value only — '4' from Street 4 / St 4 / St. 4 | | block | string \| null | Value only, upper-cased — 'B', '12-C' | | sector | string \| null | Value only, upper-cased — 'F-8/3', 'G-9' (Islamabad / Karachi) | | phase | string \| null | Value only — '6'; Roman numerals folded to digits (II'2') | | unit | string \| null | 'Flat 3', 'Apartment 12-C', '2nd Floor', 'Shop 4' | | landmark | string \| null | Keeps its preposition — 'Near Emporium Mall', 'Opposite ...' | | area | string \| null | Resolved locality, canonical gazetteer name — 'Johar Town' | | city | string \| null | Resolved / inferred city — 'Lahore' | | province | string \| null | Resolved / inferred province — 'Punjab' | | country | 'Pakistan' | Constant | | raw | string | The original input, untouched | | unmatched | string[] | Tokens that could not be classified (original casing) | | confidence | number | 01 heuristic — advisory only, not a probability |

confidence is advisory. It is a rough [0, 1] score: +0.35 province, +0.30 city, +0.15 area, +0.05 each for house / street / block / sector / phase (capped at +0.20), minus 0.15 × min(unmatched, 3) / 3, clamped and rounded to 2 decimals. Use it to triage / flag addresses for review, not as a hard gate.

Data & coverage

The bundled gazetteer is generated from GeoNames (the Pakistan PK dump plus admin1 / admin2 codes) merged with a curated scripts/build-data/overrides.json (province and city aliases; well-known housing societies and schemes such as DHA, Bahria Town, Gulberg, Model Town, Johar Town, Clifton, PECHS, Blue Area, Hayatabad, Cantt). The current build carries 7 provinces, ~204 cities/districts and ~3,230 localities.

  • Administrative hierarchy (province → district/city) is complete from GeoNames.
  • Locality / street-level coverage is best-effort. There is no authoritative complete source; the parser degrades gracefully — an unknown locality token is kept (in area when it is the only leftover and strict is off, otherwise in unmatched), and city / province still resolve and normalizeAddress still works.

To extend coverage, edit scripts/build-data/overrides.json and re-run the pipeline:

npm run build:data   # fetches GeoNames, rebuilds src/data/*.json (committed output)

npm run build:data is manual and its output (src/data/*.json) is committed to the repo — npm install and npm run build never touch the network.

Known limitations

  • Locality coverage is best-effort. The gazetteer is GeoNames populated-places plus curated societies/schemes. An unknown locality is kept in unmatched or, in non-strict mode, guessed as area with a reduced confidence (no +0.15 area credit and an extra -0.10) so it still flags for review.
  • A leading "St" is expanded to "Street" by abbreviation handling, so "St Johns" normalizes to "Street Johns".
  • One area slot and one unit slot. Extra locality or unit descriptors (e.g. a second "2nd Floor" after a "Flat 3") go to unmatched.
  • A bare multi-city society name ("DHA", "Cantt") with no city token will not resolve a city or province — it would otherwise have to guess one arbitrarily. Pass defaultCity or include the city in the input.

Examples

Runnable scripts covering every public function live in examples/ (not shipped in the npm tarball, but exercised by the test suite so they never rot):

npm run examples

| File | Shows | | ------------------------- | ----------------------------------------------------- | | 01-parse-address.ts | parseAddress on the three canonical inputs | | 02-normalize-address.ts | normalizeAddress before / after pairs | | 03-geo-helpers.ts | all seven geo helpers | | 04-batch-cleanup.ts | batch cleanup of dirty addresses to structured rows | | 05-commonjs.cjs | the CommonJS build via require('pk-address-parser') | | 06-esm.mjs | the ESM build via import in plain JavaScript |

Changelog

All notable changes are recorded in CHANGELOG.md, following Keep a Changelog and Semantic Versioning.

Contributing

Issues and pull requests are welcome at the GitHub repository. After cloning, enable the Git pre-commit hook once:

npm install
npm run hooks:install

The local gate that must pass:

npm run typecheck && npm run lint && npm run format:check && npm run build && npm test && npm run examples

The most useful contribution is expanding scripts/build-data/overrides.json with real localities, societies and aliases, then committing the regenerated src/data/*.json.

License

MIT © Aqsa LogicByte. See LICENSE.

Attribution

Geographic data derived from GeoNames, licensed under CC BY 4.0.