Skip to content

obsidia-my/malaysia-dev-kit

Repository files navigation

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

About

Malaysian developer utilities for MyKad, SSM, and address parsing — zero dependencies, fully typed

Topics

Resources

License

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors