v0.7.2 released — New: formatPercentage for Indonesian currency formatting. Read changelog
Skip to Content
DocumentationIdentityNIK Validation

NIK

Utilities for working with Indonesian National Identity Number (Nomor Induk Kependudukan).

Overview

The NIK module provides type-safe utilities to validate, parse, format, and mask Indonesian national identity numbers. Built with zero dependencies, fully tested, and optimized for both browser and Node.js environments.

What is NIK?

NIK (Nomor Induk Kependudukan) is Indonesia’s unique 16-digit national identity number issued to all Indonesian citizens. Each NIK contains encoded information about:

  • Location: Province, regency (kabupaten/kota), and district (kecamatan)
  • Birth Date: Year, month, and day of birth
  • Gender: Encoded in the day digit (+40 for females)
  • Unique ID: Serial number for people born on the same date and location

NIK Structure

A valid NIK consists of 16 digits organized as follows:

3201018901310123 ││││││││││││└┴┴┴─ Serial Number (4 digits) ││││││││└┴┴─────── Day (01-31 for male, 41-71 for female) ││││││└┴────────── Month (01-12) ││││└┴──────────── Year (last 2 digits) ││└┴────────────── District code (2 digits) └┴──────────────── Regency code (2 digits) └───────────────── Province code (2 digits)

Breaking down the example 3201018901310123:

PositionValueMeaning
1-232Province: Jawa Barat
3-401Regency: Kab. Bogor
5-601District code
7-889Year: 1989
9-1001Month: January
11-1231Day: 31st (male)
13-160123Serial number

Gender Encoding: For females, the day is encoded as actual_day + 40. For example, a female born on January 15th would have 55 (15

  • 40) in the day position.

Installation

npm install @indodev/toolkit

Quick Start

import { validateNIK, parseNIK, formatNIK, maskNIK, cleanNIK, compareNIK, isAdult, getAge, validateNIKDetailed } from '@indodev/toolkit/nik'; // Validate a NIK const isValid = validateNIK('3201018901310123'); console.log(isValid); // true // Clean NIK (remove separators) const cleaned = cleanNIK('3201-2345-6789-0123'); // '3201234567890123' // Compare two NIKs const isSamePerson = compareNIK('3201018901310123', '3201-01-89-01-31-0123'); // true // Check if adult (17+ by default) const canHaveKTP = isAdult('3201950101950123'); // true // Get detailed age const age = getAge('3201018901310123'); console.log(age); // { years: 35, months: 2, days: 6 } // Or as formatted string const ageStr = getAge('3201018901310123', { asString: true }); console.log(ageStr); // '35 Tahun 2 Bulan 6 Hari' // Detailed validation with errors const result = validateNIKDetailed('1234'); console.log(result.errors[0].code); // 'INVALID_FORMAT' // Extract information from NIK const info = parseNIK('3201018901310123'); console.log(info?.province.name); // 'Jawa Barat' console.log(info?.gender); // 'male' // Format for display const formatted = formatNIK('3201018901310123'); console.log(formatted); // '32-01-01-89-01-31-0123' // Mask for privacy const masked = maskNIK('3201018901310123'); console.log(masked); // '3201********0123'

API Reference

validateNIK()

Validates whether a string is a valid NIK format.

Type Signature:

function validateNIK(nik: string): boolean;

Parameters:

NameTypeDescription
nikstringThe NIK string to validate

Returns:

boolean - Returns true if the NIK is valid, false otherwise.

Validation Rules:

  • Must be exactly 16 digits
  • Province code must be valid (from official Dukcapil data)
  • Date components must form a valid calendar date
  • Day must be 1-31 for males or 41-71 for females

Examples:

// Valid NIK validateNIK('3201018901310123'); // ✅ true // Invalid: wrong length validateNIK('1234'); // ❌ false // Invalid: non-numeric characters validateNIK('320101890131012A'); // ❌ false // Invalid: province code doesn't exist validateNIK('9912345678901234'); // ❌ false // Invalid: impossible date (February 30th) validateNIK('3201018902300123'); // ❌ false

Use Cases:

// Form validation function handleSubmit(formData: FormData) { const nik = formData.get('nik') as string; if (!validateNIK(nik)) { return { error: 'Invalid NIK format' }; } // Process valid NIK return { success: true }; } // Batch validation const niks = ['3201018901310123', '3175031234567890', 'invalid']; const validNiks = niks.filter(validateNIK); console.log(validNiks); // ['3201018901310123', '3175031234567890']

isValidForGender()

Validates whether a NIK matches a given gender.

Type Signature:

function isValidForGender(nik: string, gender: 'male' | 'female'): boolean;

Parameters:

NameTypeDescription
nikstringThe 16-digit NIK string
gender'male' | 'female'The expected gender

Returns:

boolean - Returns true if the NIK matches the gender, false otherwise.

Examples:

isValidForGender('3201018901310123', 'male'); // ✅ true isValidForGender('3201018901310123', 'female'); // ❌ false

isValidForBirthDate()

Validates whether a NIK matches a given birth date.

Type Signature:

function isValidForBirthDate(nik: string, birthDate: Date): boolean;

Parameters:

NameTypeDescription
nikstringThe 16-digit NIK string
birthDateDateThe expected birth date

Returns:

boolean - Returns true if the NIK matches the birth date, false otherwise.

Examples:

const birthDate = new Date(1989, 0, 31); isValidForBirthDate('3201018901310123', birthDate); // âś… true

parseNIK()

Parses a NIK string and extracts all embedded information including location, birth date, and gender.

Type Signature:

function parseNIK(nik: string): NIKInfo | null;

Parameters:

NameTypeDescription
nikstringThe 16-digit NIK string to parse

Returns:

NIKInfo | null - Returns a NIKInfo object if parsing succeeds, or null if the NIK is invalid.

Examples:

// Parse male NIK const male = parseNIK('3201018901310123'); console.log(male); // { // province: { code: '32', name: 'Jawa Barat' }, // regency: { code: '01', name: 'Kab. Bogor' }, // district: { code: '01', name: null }, // birthDate: Date('1989-01-31'), // gender: 'male', // serialNumber: '0123', // isValid: true // } // Parse female NIK (day + 40) const female = parseNIK('3201019508550123'); console.log(female?.gender); // 'female' console.log(female?.birthDate); // Date('1995-08-15') // Day 55 = 15 + 40 (female encoding) // Parse Jakarta NIK const jakarta = parseNIK('3175031234567890'); console.log(jakarta?.province.name); // 'DKI Jakarta' console.log(jakarta?.regency.name); // 'Kota Jakarta Pusat' // Invalid NIK returns null const invalid = parseNIK('invalid-nik'); console.log(invalid); // null

Use Cases:

// Extract age from NIK function getAgeFromNIK(nik: string): number | null { const info = parseNIK(nik); if (!info?.birthDate) return null; const today = new Date(); const birthDate = info.birthDate; let age = today.getFullYear() - birthDate.getFullYear(); // Adjust if birthday hasn't occurred this year const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age--; } return age; } // Display user information function displayUserInfo(nik: string) { const info = parseNIK(nik); if (!info) { return 'Invalid NIK'; } return ` Location: ${info.province.name}, ${info.regency.name} Birth Date: ${info.birthDate?.toLocaleDateString('id-ID')} Gender: ${info.gender === 'male' ? 'Laki-laki' : 'Perempuan'} `; } // Filter by province function filterByProvince(niks: string[], provinceCode: string) { return niks.filter((nik) => { const info = parseNIK(nik); return info?.province.code === provinceCode; }); }

Note on District Names: District names are currently null in the returned object as comprehensive district data is not yet included in the library. Province and regency data are complete and accurate.


getAge()

Calculates age from a NIK as of a given reference date (defaults to today). Returns detailed age breakdown with years, months, and days, or a formatted Indonesian string.

Type Signature:

function getAge( nik: string, options?: { referenceDate?: Date; asString?: boolean; } ): { years: number; months: number; days: number } | string | null;

Parameters:

NameTypeDescription
nikstringThe 16-digit NIK string
optionsobjectOptional configuration object
options.referenceDateDateReference date for age calculation (default: today)
options.asStringbooleanReturn formatted Indonesian string (default: false)

Returns:

{ years, months, days } | string | null - Age object, formatted string, or null if invalid.

Breaking Change in v0.6.0: The return type changed from number | null to { years, months, days } | string | null. Use age.years for year comparison.

Examples:

// Returns object by default const age = getAge('3201018901310123'); console.log(age); // { years: 35, months: 2, days: 6 } // Returns formatted string with asString option const ageStr = getAge('3201018901310123', { asString: true }); console.log(ageStr); // '35 Tahun 2 Bulan 6 Hari' // Custom reference date const ageAtDate = getAge('3201018901310123', { referenceDate: new Date('2025-01-01') }); console.log(ageAtDate); // { years: 35, months: 11, days: 1 }

formatBirthDate()

Formats the birth date encoded in a NIK into a localized string.

Type Signature:

function formatBirthDate(nik: string, options?: Intl.DateTimeFormatOptions, locale?: string): string | null;

Parameters:

NameTypeDefaultDescription
nikstring-The 16-digit NIK string
optionsIntl.DateTimeFormatOptions-Formatting options
localestring'id-ID'The locale to use

Returns:

string | null - Formatted date string, or null if the NIK is invalid.

Examples:

formatBirthDate('3201018901310123'); // '31 Januari 1989' formatBirthDate('3201018901310123', { year: 'numeric' }); // '1989' formatBirthDate('3201018901310123', undefined, 'en-US'); // 'January 31, 1989'

formatNIK()

Formats a NIK string with separators for better readability.

Type Signature:

function formatNIK(nik: string, separator?: string): string;

Parameters:

NameTypeDefaultDescription
nikstring-The 16-digit NIK string to format
separatorstring'-'Character to use as separator between segments

Returns:

string - Formatted NIK string with separators, or the original string if invalid.

Format Pattern:

PP-KK-DD-YY-MM-DD-SSSS PP = Province code KK = Regency code DD = District code YY = Birth year (2 digits) MM = Birth month DD = Birth day (add 40 for female) SSSS = Serial number

Examples:

// Default separator (dash) formatNIK('3201018901310123'); // '32-01-01-89-01-31-0123' // Custom separator: space formatNIK('3201018901310123', ' '); // '32 01 01 89 01 31 0123' // Custom separator: dot formatNIK('3201018901310123', '.'); // '32.01.01.89.01.31.0123' // Custom separator: underscore formatNIK('3201018901310123', '_'); // '32_01_01_89_01_31_0123' // Invalid NIK returns as-is formatNIK('1234'); // '1234' // Empty separator (no formatting) formatNIK('3201018901310123', ''); // '3201018901310123'

Use Cases:

// Display in UI function NIKDisplay({ nik }: { nik: string }) { return <div>NIK: {formatNIK(nik)}</div> } // Custom formatting for different contexts function formatForDocument(nik: string) { return formatNIK(nik, ' ') // Space-separated for documents } function formatForDatabase(nik: string) { return formatNIK(nik, '') // No separator for storage } // Format list of NIKs const niks = ['3201018901310123', '3175031234567890'] const formatted = niks.map(nik => formatNIK(nik)) console.log(formatted) // ['32-01-01-89-01-31-0123', '31-75-03-12-34-56-7890']

maskNIK()

Masks a NIK to protect privacy while maintaining partial visibility for verification purposes.

Type Signature:

function maskNIK(nik: string, options?: MaskOptions): string;

Parameters:

NameTypeDescription
nikstringThe 16-digit NIK string to mask
optionsMaskOptionsOptional configuration for masking behavior

Returns:

string - Masked NIK string, or the original string if invalid.

Options:

interface MaskOptions { visibleStart?: number; // Characters to show at start (default: 4) visibleEnd?: number; // Characters to show at end (default: 4) maskChar?: string; // Mask character (default: '*') separator?: string; // Optional separator (default: none) /** @deprecated Use visibleStart instead. Deprecated in v0.7.0. */ start?: number; /** @deprecated Use visibleEnd instead. Deprecated in v0.7.0. */ end?: number; /** @deprecated Use maskChar instead. Deprecated in v0.7.0. */ char?: string; }

Breaking Change (v0.7.0): Options renamed to visibleStart, visibleEnd, maskChar. Old names still work with deprecation warnings. Will be removed in v1.0.0.

Examples:

// Default masking (first 4, last 4) maskNIK('3201018901310123'); // '3201********0123' // Custom visible characters maskNIK('3201018901310123', { visibleStart: 6, visibleEnd: 4 }); // '320101******0123' maskNIK('3201018901310123', { visibleStart: 2, visibleEnd: 2 }); // '32************23' // Custom mask character maskNIK('3201018901310123', { maskChar: 'X' }); // '3201XXXXXXXX0123' maskNIK('3201018901310123', { maskChar: '•' }); // '3201••••••••0123' // With separator maskNIK('3201018901310123', { separator: '-' }); // '32-01-**-**-**-**-0123' maskNIK('3201018901310123', { separator: ' ' }); // '32 01 ** ** ** ** 0123' // Combined options maskNIK('3201018901310123', { visibleStart: 6, visibleEnd: 4, maskChar: 'X', separator: '-', }); // '32-01-01-XX-XX-XX-0123' // Complete masking maskNIK('3201018901310123', { visibleStart: 0, visibleEnd: 0 }); // '****************' // Invalid NIK returns as-is maskNIK('invalid'); // 'invalid'

Use Cases:

// Display in public listings function UserCard({ user }) { return ( <div> <h3>{user.name}</h3> <p>NIK: {maskNIK(user.nik)}</p> </div> ) } // Different masking levels based on permission function displayNIK(nik: string, userRole: string) { switch (userRole) { case 'admin': return formatNIK(nik) // Full visibility case 'staff': return maskNIK(nik, { start: 6, end: 4 }) // Partial default: return maskNIK(nik, { start: 4, end: 0 }) // Minimal } } // Mask for logging function logUserAction(nik: string, action: string) { console.log(`User ${maskNIK(nik)} performed: ${action}`) } // Privacy-compliant export function exportUserData(users: User[]) { return users.map(user => ({ ...user, nik: maskNIK(user.nik, { separator: '-' }) })) }

Privacy Best Practice: When displaying NIKs in logs, public interfaces, or shared systems, always use masking to protect user privacy while maintaining enough information for verification.


cleanNIK()

Cleans a NIK by removing all non-digit characters (separators like ., -, ).

Type Signature:

function cleanNIK(nik: string): string;

Parameters:

NameTypeDescription
nikstringNIK in any format with separators

Returns:

string - Clean 16-digit NIK, or empty string if invalid.

Examples:

// Clean various formats cleanNIK('32-01-01-89-01-31-0123'); // '3201018901310123' cleanNIK('3201.01.89.01.31.0123'); // '3201018901310123' cleanNIK('3201 01 89 01 31 0123'); // '3201018901310123' cleanNIK('32-01.01.89-01-31-0123'); // '3201018901310123' // Invalid inputs return empty string cleanNIK('1234'); // '' cleanNIK('invalid'); // '' cleanNIK(''); // ''

compareNIK()

Compares two NIKs to check if they belong to the same person.

Type Signature:

function compareNIK(nik1: string, nik2: string): boolean;

Parameters:

NameTypeDescription
nik1stringFirst NIK (any format)
nik2stringSecond NIK (any format)

Returns:

boolean - true if both NIKs belong to the same person, false otherwise.

Comparison Criteria:

  • Province code (pos 1-2)
  • Regency code (pos 3-4)
  • District code (pos 5-6)
  • Birth date (year + month + day)
  • Gender (derived from day encoding)
  • Serial number (pos 13-16)

Examples:

// Same person, same format compareNIK('3201018901310123', '3201018901310123'); // true // Same person, different format compareNIK('3201018901310123', '3201-01-89-01-31-0123'); // true // Different serial number compareNIK('3201018901310123', '3201018901310124'); // false // Invalid inputs return false compareNIK('invalid', '3201018901310123'); // false

isAdult()

Checks if a person is an adult based on their NIK. Default threshold is 17 years (Indonesian KTP eligibility age).

Type Signature:

function isAdult(nik: string, minAge?: number): boolean;

Parameters:

NameTypeDefaultDescription
nikstring-The 16-digit NIK string
minAgenumber17Minimum age threshold

Returns:

boolean - true if the person is at least minAge years old, false otherwise.

Examples:

// Born 1995-01-01, reference 2026-04-06 (age 31) isAdult('3201950101950123'); // true (31 >= 17) isAdult('3201950101950123', 21); // true (31 >= 21) isAdult('3201950101950123', 35); // false (31 < 35) // Born 2010-01-01, reference 2026-04-06 (age 16) isAdult('3210010101950123'); // false (16 < 17) isAdult('3210010101950123', 16); // true (16 >= 16) // Invalid NIK returns false isAdult('invalid'); // false

validateNIKDetailed()

Validates a NIK and returns detailed error information for form validation.

Type Signature:

function validateNIKDetailed(nik: string): NIKValidationResult; interface NIKValidationResult { isValid: boolean; errors: NIKValidationError[]; nik: string | null; } interface NIKValidationError { code: NIKErrorCode; message: string; } type NIKErrorCode = | 'INVALID_FORMAT' | 'INVALID_PROVINCE' | 'INVALID_MONTH' | 'INVALID_DAY' | 'INVALID_DATE' | 'FUTURE_DATE';

Parameters:

NameTypeDescription
nikstringNIK to validate (any format)

Returns:

NIKValidationResult - Structured validation result with isValid, errors[], and nik.

Examples:

// Valid NIK const result = validateNIKDetailed('3201018901310123'); // { isValid: true, errors: [], nik: '3201018901310123' } // Invalid NIK const result = validateNIKDetailed('1234'); // { // isValid: false, // errors: [{ code: 'INVALID_FORMAT', message: 'NIK must be exactly 16 digits' }], // nik: null // } // Multiple errors const result = validateNIKDetailed('9999999999999999'); // { // isValid: false, // errors: [{ code: 'INVALID_PROVINCE', message: 'Province code 99 not found' }], // nik: null // }

Type Reference

NIKInfo

Information extracted from a valid NIK.

interface NIKInfo { province: { code: string; // Two-digit province code (e.g., '32') name: string; // Full province name (e.g., 'Jawa Barat') }; regency: { code: string; // Two-digit regency code (e.g., '01') name: string; // Full regency name (e.g., 'Kab. Bogor') }; district: { code: string; // Two-digit district code (e.g., '01') name: string | null; // District name (currently null) }; birthDate: Date | null; // Parsed birth date gender: 'male' | 'female' | null; // Derived from day encoding serialNumber: string | null; // Four-digit serial number isValid: boolean; // Overall validity flag }

Property Details:

  • province: Province information from first 2 digits
  • regency: Regency (Kabupaten/Kota) from digits 3-4
  • district: District (Kecamatan) from digits 5-6
  • birthDate: JavaScript Date object from digits 7-12
  • gender: Determined by day value (1-31 = male, 41-71 = female)
  • serialNumber: Unique identifier from last 4 digits
  • isValid: true if all validation checks passed

MaskOptions

Configuration options for masking NIKs.

interface MaskOptions { visibleStart?: number; // Characters to show at start (default: 4) visibleEnd?: number; // Characters to show at end (default: 4) maskChar?: string; // Mask character (default: '*') separator?: string; // Optional separator (default: none) /** @deprecated Use visibleStart instead. Deprecated in v0.7.0. */ start?: number; /** @deprecated Use visibleEnd instead. Deprecated in v0.7.0. */ end?: number; /** @deprecated Use maskChar instead. Deprecated in v0.7.0. */ char?: string; }

Option Details:

  • visibleStart: Number of characters visible at the beginning
  • visibleEnd: Number of characters visible at the end
  • maskChar: Character used for masking (single character)
  • separator: If provided, formats the NIK with separators before masking
Constraint: visibleStart + visibleEnd must be less than 16, otherwise the NIK is returned unmodified.

NIKValidationResult

Result of detailed NIK validation with structured error reporting.

interface NIKValidationResult { isValid: boolean; // Whether the NIK is valid errors: NIKValidationError[]; // List of validation errors nik: string | null; // Cleaned 16-digit NIK if valid, null otherwise }

NIKValidationError

A single validation error with code and message.

interface NIKValidationError { code: NIKErrorCode; // Error code for programmatic handling message: string; // Human-readable error message }

NIKErrorCode

Error codes for detailed NIK validation.

type NIKErrorCode = | 'INVALID_FORMAT' // Not 16 digits | 'INVALID_PROVINCE' // Province code not found | 'INVALID_MONTH' // Month not in 01-12 | 'INVALID_DAY' // Day value invalid | 'INVALID_DATE' // Date doesn't exist (e.g., Feb 30) | 'FUTURE_DATE'; // Birth date in the future

Age

Age result object with years, months, and days.

interface Age { years: number; // Full years months: number; // Remaining months after full years days: number; // Remaining days after full months }

GetAgeOptions

Options for getAge function.

interface GetAgeOptions { referenceDate?: Date; // Date to calculate age from (default: today) asString?: boolean; // Return formatted Indonesian string }

Common Use Cases

Age Verification

import { parseNIK } from '@indodev/toolkit/nik'; function isAdult(nik: string): boolean { const info = parseNIK(nik); if (!info?.birthDate) return false; const age = new Date().getFullYear() - info.birthDate.getFullYear(); return age >= 17; // Indonesian adult age } // Usage if (isAdult(userNIK)) { // Allow access } else { // Require parent consent }

Regional Filtering

import { parseNIK } from '@indodev/toolkit/nik'; // Find all users from Jakarta function getUsersFromJakarta(niks: string[]) { return niks.filter((nik) => { const info = parseNIK(nik); return info?.province.code === '31'; // DKI Jakarta }); } // Group by province function groupByProvince(niks: string[]) { return niks.reduce( (acc, nik) => { const info = parseNIK(nik); if (!info) return acc; const province = info.province.name; if (!acc[province]) acc[province] = []; acc[province].push(nik); return acc; }, {} as Record<string, string[]> ); }

Form Validation

import { validateNIK, parseNIK } from '@indodev/toolkit/nik'; function validateUserForm(data: FormData) { const nik = data.get('nik') as string; const birthDate = new Date(data.get('birthDate') as string); // Validate NIK format if (!validateNIK(nik)) { return { error: 'NIK tidak valid' }; } // Verify birth date matches NIK const info = parseNIK(nik); if (info?.birthDate?.toDateString() !== birthDate.toDateString()) { return { error: 'Tanggal lahir tidak sesuai dengan NIK' }; } return { success: true, data: info }; }

Privacy-Safe Display

import { maskNIK, formatNIK } from '@indodev/toolkit/nik'; // Different views based on context function NIKDisplay({ nik, context }: { nik: string; context: string }) { switch (context) { case 'admin': return formatNIK(nik); // Full: '32-01-01-89-01-31-0123' case 'profile': return maskNIK(nik, { separator: '-' }); // Partial: '32-01-**-**-**-**-0123' case 'public': return maskNIK(nik, { visibleStart: 2, visibleEnd: 2 }); // Minimal: '32************23' default: return '****-****-****-****'; // Hidden } }

Batch Processing

import { validateNIK, parseNIK } from '@indodev/toolkit/nik'; // Process CSV import function processNIKBatch(niks: string[]) { const results = { valid: [] as string[], invalid: [] as string[], duplicates: [] as string[], }; const seen = new Set<string>(); for (const nik of niks) { if (!validateNIK(nik)) { results.invalid.push(nik); continue; } if (seen.has(nik)) { results.duplicates.push(nik); continue; } seen.add(nik); results.valid.push(nik); } return results; }

Best Practices

Error Handling

Handle parsing failures gracefully:

function safeParseNIK(nik: string): NIKInfo { const info = parseNIK(nik); if (!info) { throw new Error('Invalid NIK format'); } return info; } // Usage with try-catch try { const info = safeParseNIK(userNIK); console.log(info.province.name); } catch (error) { console.error('Failed to parse NIK:', error.message); }

Privacy Compliance

Always mask NIKs in logs and public displays:

// ❌ BAD: Exposing full NIK in logs console.log(`User ${nik} logged in`); // ✅ GOOD: Masked NIK in logs console.log(`User ${maskNIK(nik)} logged in`); // ✅ GOOD: Masked in public API responses function getUserPublicProfile(nik: string) { return { nik: maskNIK(nik), // other fields... }; }

Type Safety

Use TypeScript for type safety:

import type { NIKInfo, MaskOptions } from '@indodev/toolkit/nik'; // Type-safe function function processNIK(nik: string): NIKInfo | null { return parseNIK(nik); } // Type-safe options const options: MaskOptions = { visibleStart: 4, visibleEnd: 4, maskChar: '*', separator: '-', };

Performance

For large datasets, validate once and cache results:

// Cache validation results const validationCache = new Map<string, boolean>(); function cachedValidate(nik: string): boolean { if (validationCache.has(nik)) { return validationCache.get(nik)!; } const isValid = validateNIK(nik); validationCache.set(nik, isValid); return isValid; }

Troubleshooting

Common Issues

Q: Why does parseNIK() return null?

A: This can happen if:

  • NIK length is not exactly 16 digits
  • Province code is invalid
  • Date components form an invalid date (e.g., February 30th)
// Check each component const nik = '3201018902300123'; // February 30th (invalid) const info = parseNIK(nik); console.log(info); // null

Q: Why is district.name always null?

A: Currently, the library only includes province and regency data. District data will be added in future versions.

Q: How do I handle female NIKs?

A: The library automatically handles the +40 encoding for females:

const female = parseNIK('3201019508550123'); console.log(female?.gender); // 'female' console.log(female?.birthDate); // 1995-08-15 (day 55 - 40 = 15)

Q: Can I use this with React/Vue/etc?

A: Yes! The library is framework-agnostic and works in any JavaScript environment:

// React example import { useState } from 'react'; import { validateNIK, formatNIK } from '@indodev/toolkit/nik'; function NIKInput() { const [nik, setNIK] = useState(''); const isValid = validateNIK(nik); return ( <div> <input value={nik} onChange={(e) => setNIK(e.target.value)} className={isValid ? 'valid' : 'invalid'} /> {isValid && <p>Formatted: {formatNIK(nik)}</p>} </div> ); }

Data Source

Province and regency codes are based on official data from:

  • Dukcapil Kemendagri (Ministry of Home Affairs - Population and Civil Registration)
  • Updated to include all 38 Indonesian provinces (including new Papua provinces)
For the most up-to-date administrative codes, always refer to official government sources.
Last updated on