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

@obsidia-my/malaysia-dev-kit

v0.1.0

Published

Malaysian developer utilities — MyKad, SSM, Address parsing and validation

Downloads

128

Readme

malaysia-dev-kit

npm version CI Coverage License: MIT Bundle Size

Malaysian developer utilities — MyKad, SSM, Address parsing and validation.

Utiliti pembangun Malaysia — penghurai dan pengesah MyKad, SSM, dan Alamat.


Why this exists

Every Malaysian software company building for government or enterprise clients ends up solving the same problems from scratch — parsing IC numbers, validating SSM registrations, normalising messy address strings. There has been no shared, well-maintained open source library for Malaysian-specific data formats.

malaysia-dev-kit fixes that. It is a small, excellent, zero-dependency TypeScript library that handles the most common Malaysian data parsing and validation tasks so you never have to write this code again.

Built and maintained by Obsidia Systems Sdn Bhd.


Features

| Module | What it does | |--------|-------------| | MyKad | Parse IC numbers — date of birth, gender, birth state, IC type (MyKad/MyPR), with or without dashes | | SSM | Parse company registration numbers — old and new formats, entity type detection, normalisation | | Address | Parse and normalise Malaysian addresses — abbreviation expansion, postcode lookup, state validation |

Design principles:

  • Zero runtime dependencies — installs clean, no supply chain risk
  • Always returns a result — parse functions never throw for bad input; check isValid and errors
  • Strongly typed — rich result objects, never just true/false
  • Meaningful errors — every error tells you exactly what is wrong and why
  • 100% test coverage — on every module

Installation

npm install @obsidia-my/malaysia-dev-kit
# or
yarn add @obsidia-my/malaysia-dev-kit
# or
pnpm add @obsidia-my/malaysia-dev-kit

Requirements: Node.js 18+ (also browser-compatible where possible)


Table of Contents


MyKad — IC Number

Malaysian IC numbers follow the format YYMMDD-PB-XXXG. This module parses any variant — with dashes, without dashes, with extra whitespace — and returns fully structured data.

import { parseMyKad, isValidMyKad, getMyKadDateOfBirth } from '@obsidia-my/malaysia-dev-kit';

// Full parse
const ic = parseMyKad('900101-14-1234');

ic.isValid                      // true
ic.type                         // 'MYKAD'
ic.gender                       // 'MALE'
ic.dateOfBirth.formatted        // '01/01/1990'
ic.dateOfBirth.iso              // '1990-01-01'
ic.dateOfBirth.year             // 1990
ic.birthPlace.code              // '14'
ic.birthPlace.description       // 'Wilayah Persekutuan Kuala Lumpur'
ic.birthPlace.isMalaysia        // true
ic.normalised                   // '900101141234'
ic.formatted                    // '900101-14-1234'

// Input flexibility — all of these work
parseMyKad('900101141234')      // no dashes
parseMyKad('900101 14 1234')    // spaces
parseMyKad('  900101-14-1234 ') // leading/trailing whitespace

// Permanent resident (born abroad)
const pr = parseMyKad('850615-61-5678');
pr.type                         // 'MYPR'
pr.birthPlace.description       // 'Indonesia'
pr.birthPlace.isAbroad          // true

// Quick check
isValidMyKad('900101-14-1234')  // true
isValidMyKad('ABCDEFGHIJKL')    // false

// Just the date
getMyKadDateOfBirth('900101141234')  // Date(1990, 0, 1)

IC types

| Type | Condition | |------|-----------| | MYKAD | Malaysian citizen, born in Malaysia | | MYPR | Permanent resident (foreign birth code) | | MYTENTERA | Armed forces | | MYKID | Malaysian citizen below 12 | | UNKNOWN | Unrecognised birth place code |

Functions

| Function | Returns | |----------|---------| | parseMyKad(input) | MyKadParseResult | | isValidMyKad(input) | boolean | | getMyKadDateOfBirth(input) | Date \| null | | getMyKadGender(input) | 'MALE' \| 'FEMALE' \| null | | getMyKadBirthPlace(input) | MyKadParseResult['birthPlace'] |

Full MyKad documentation →


SSM — Company Registration

Handles both old format (123456-W) and new format (202001234567). Normalises messy input — lowercase suffixes, missing dashes, spaces.

import { parseSSM, isValidSSM, normaliseSSM } from '@obsidia-my/malaysia-dev-kit';

// New format (2017 onwards) — 12 digits, first 4 are the year
const co = parseSSM('202001234567');

co.isValid                      // true
co.format                       // 'NEW'
co.registrationYear             // 2020
co.entityType                   // 'SDN_BHD'
co.entityTypeDescription        // 'Sendirian Berhad'

// Old format — number + suffix letter
const co2 = parseSSM('123456-W');

co2.isValid                     // true
co2.format                      // 'OLD'
co2.entityType                  // 'SDN_BHD'
co2.suffix                      // 'W'

// Input normalisation — all of these resolve to '123456-W'
normaliseSSM('123456w')         // '123456-W'
normaliseSSM('123456W')         // '123456-W'
normaliseSSM('123456 w')        // '123456-W'

Old format suffixes

| Suffix | Entity | |--------|--------| | -W | Sendirian Berhad (Sdn Bhd) | | -X | Berhad (Bhd) | | -H | LLP (Syarikat Perkongsian Liabiliti Terhad) | | -K | Koperasi | | -SA | Persatuan / Society | | -D, -M, -T, -V, -A | Enterprise / Sole Proprietor |

Functions

| Function | Returns | |----------|---------| | parseSSM(input) | SSMParseResult | | isValidSSM(input) | boolean | | normaliseSSM(input) | string | | getSSMEntityType(input) | SSMEntityType \| null | | getSSMFormat(input) | 'OLD' \| 'NEW' \| 'UNKNOWN' |

Full SSM documentation →


Address

Parses freeform Malaysian address strings into structured fields. Includes a complete static postcode dataset for all 16 states and federal territories.

import { parseAddress, normaliseAddress, validatePostcode } from '@obsidia-my/malaysia-dev-kit';

// Parse a messy real-world address string
const addr = parseAddress('No. 5, Jln Ampang, Tmn Ampang, 55000 KL, Selangor');

addr.isValid                    // false — postcode/state mismatch detected
addr.unit                       // 'No. 5'
addr.street                     // 'Jalan Ampang'   ← Jln expanded
addr.area                       // 'Taman Ampang'   ← Tmn expanded
addr.postcode                   // '55000'
addr.city                       // 'Kuala Lumpur'   ← from postcode dataset
addr.state                      // 'Wilayah Persekutuan Kuala Lumpur'
addr.stateCode                  // 'WP KL'
addr.postcodeValid              // true
addr.postcodeStateMatch         // false  ← 55000 is WP KL, not Selangor
addr.errors[0].code             // 'POSTCODE_STATE_MISMATCH'
addr.warnings[0].code           // 'ABBREVIATION_EXPANDED'

// Normalise only — quick clean-up
normaliseAddress('No. 5, Jln Ampang, Tmn Ampang, 55000 KL, Selangor')
// → 'No. 5, Jalan Ampang, Taman Ampang, 55000 Kuala Lumpur, Wilayah Persekutuan Kuala Lumpur'

// Postcode lookup
validatePostcode('50000')
// → { postcode: '50000', city: 'Kuala Lumpur', state: 'Wilayah Persekutuan Kuala Lumpur', stateCode: 'WP KL' }

validatePostcode('88000')
// → { postcode: '88000', city: 'Kota Kinabalu', state: 'Sabah', stateCode: 'SBH' }

// Postcode/state validation
postcodeMatchesState('50000', 'Wilayah Persekutuan Kuala Lumpur')  // true
postcodeMatchesState('50000', 'KL')                                // true  ← normalised
postcodeMatchesState('50000', 'Selangor')                          // false

Abbreviation expansion

| Input | Expanded | |-------|----------| | Jln | Jalan | | Tmn | Taman | | Kg / Kpg | Kampung | | Bdr | Bandar | | Bkt | Bukit | | Pst | Pusat | | KL (standalone) | Kuala Lumpur | | PJ | Petaling Jaya | | JB | Johor Bahru | | KK | Kota Kinabalu |

Functions

| Function | Returns | |----------|---------| | parseAddress(input) | AddressParseResult | | normaliseAddress(input) | string | | validatePostcode(postcode) | PostcodeEntry \| null | | getStateFromPostcode(postcode) | string \| null | | postcodeMatchesState(postcode, state) | boolean | | formatAddress(parts) | string |

Full Address documentation →


Error Handling

Parse functions never throw for invalid input — they always return a result object. Check isValid and errors.

const result = parseMyKad('not-an-ic');

result.isValid   // false
result.errors
// [
//   { code: 'INVALID_LENGTH', field: 'LENGTH', message: 'IC number must be 12 digits...' },
//   { code: 'INVALID_CHARACTERS', field: 'FORMAT', message: 'IC number must contain only numeric digits.' }
// ]

Functions do throw a MalaysiaDevKitError if you pass a non-string (programmer error):

import { MalaysiaDevKitError } from '@obsidia-my/malaysia-dev-kit';

try {
  parseMyKad(null);
} catch (e) {
  if (e instanceof MalaysiaDevKitError) {
    console.log(e.code);    // 'INVALID_INPUT_TYPE'
    console.log(e.module);  // 'MYKAD'
  }
}

TypeScript Types

All types are exported and fully documented.

import type {
  // MyKad
  MyKadParseResult,
  MyKadType,
  MyKadError,
  MyKadErrorCode,
  Gender,

  // SSM
  SSMParseResult,
  SSMFormat,
  SSMEntityType,
  SSMError,
  SSMErrorCode,

  // Address
  AddressParseResult,
  PostcodeEntry,
  AddressError,
  AddressWarning,
  AddressErrorCode,
  AddressWarningCode,
} from '@obsidia-my/malaysia-dev-kit';

Roadmap

Planned additions for future releases:

  • Phone number — Malaysian mobile and landline parsing (+601X, 03-XXXX)
  • Bank account — format validation for Maybank, CIMB, Public Bank, etc.
  • Vehicle plate — Malaysian plate number parsing (state prefix, year)
  • KWSP/EPF number — member number validation
  • Business prefix lookup — Sdn Bhd, Bhd, LLP name suffix normalisation

PRs welcome. See CONTRIBUTING.md.


Contributing

See CONTRIBUTING.md for full guidelines.

Quick rules:

  • Zero runtime dependencies — always
  • 100% test coverage required for all new code
  • Strict TypeScript — no any, no non-null assertions

Licence

MIT © 2026 Obsidia Systems Sdn Bhd