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

Getting Started

Get up and running with @indodev/toolkit in minutes.

Installation

npm install @indodev/toolkit
Requirements: Node.js 18+ or any modern browser. Works with TypeScript 5.0+ or JavaScript.

Usage Pattern

The library uses subpath imports for optimal tree-shaking. This ensures your final bundle only includes the code you actually use.

// Recommended: Import from specific submodules import { validateNIK } from '@indodev/toolkit/nik'; import { formatRupiah } from '@indodev/toolkit/currency'; // Supported but not recommended for large apps import { validateNIK, formatRupiah } from '@indodev/toolkit';
Subpath imports not working? Make sure your tsconfig.json has "moduleResolution": "bundler" (or "node16" / "nodenext"). See TypeScript Configuration below.

TypeScript Configuration

For the best developer experience, especially when using subpath imports, ensure your tsconfig.json is configured correctly:

{ "compilerOptions": { "moduleResolution": "bundler", "resolvePackageJsonExports": true } }

This enables proper resolution of subpaths like @indodev/toolkit/nik.

Quick Examples

NIK Validation and Parsing

import { validateNIK, parseNIK } from '@indodev/toolkit/nik'; // Validate format and checksum validateNIK('3273051205900001'); // true validateNIK('1234567890123456'); // false (invalid checksum) // Parse to extract information const info = parseNIK('3273051205900001'); // { // province: { code: '32', name: 'Jawa Barat' }, // city: { code: '73', name: 'Kota Bandung' }, // gender: 'Male', // birthDate: 1990-05-12T00:00:00.000Z, // uniqueCode: 1 // } info.province.name; // 'Jawa Barat' info.gender; // 'Male' info.birthDate.toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' }); // '12 Mei 1990'

Phone Number Handling

import { validatePhone, formatPhoneNumber, parsePhone } from '@indodev/toolkit/phone'; // Validate validatePhone('081234567890'); // true validatePhone('099999999999'); // false (invalid operator prefix) // Format in different styles formatPhoneNumber('081234567890', 'domestic'); // '0812-3456-7890' formatPhoneNumber('081234567890', 'international'); // '+62 812-3456-7890' formatPhoneNumber('081234567890', 'e164'); // '+6281234567890' // Parse to extract details const phone = parsePhone('081234567890'); // { // countryCode: '+62', // operator: { name: 'Telkomsel', code: '812' }, // type: 'mobile', // nationalNumber: '81234567890' // } phone.operator.name; // 'Telkomsel'

Currency Formatting

import { formatRupiah, formatCompact, toWords, splitAmount } from '@indodev/toolkit/currency'; // Standard formatting formatRupiah(1500000); // 'Rp 1.500.000' formatRupiah(-1500000); // '-Rp 1.500.000' formatRupiah(1500000.50, { decimal: true }); // 'Rp 1.500.000,50' // Compact format (dashboard-friendly) formatCompact(1500000); // 'Rp 1,5 juta' formatCompact(1000000); // 'Rp 1 juta' (not 'Rp 1,0 juta') formatCompact(2500000000); // 'Rp 2,5 miliar' // Terbilang (number to words) toWords(1500000); // 'satu juta lima ratus ribu rupiah' toWords(1500000.50, { withDecimals: true }); // 'satu juta lima ratus ribu rupiah koma lima puluh' // Split amounts (installments, split bills) splitAmount(1500000, 3); // [500000, 500000, 500000] splitAmount(10000, 3); // [3334, 3333, 3333] (remainder distributed to first parts) splitAmount(1000000, 2, { ratios: [70, 30] }); // [700000, 300000]

Text Utilities

import { toTitleCase, slugify, maskText, toCamelCase } from '@indodev/toolkit/text'; // Title case with Indonesian grammar toTitleCase('pt bank central asia tbk'); // 'PT Bank Central Asia Tbk' toTitleCase('laskar pelangi: kisah nyata'); // 'Laskar Pelangi: Kisah Nyata' // Slug generation with Indonesian conjunctions slugify('Pria & Wanita'); // 'pria-dan-wanita' slugify('Baju Murah Jakarta'); // 'baju-murah-jakarta' // Mask sensitive data maskText('08123456789', { pattern: 'middle', visibleStart: 4, visibleEnd: 3 }); // '0812****789' maskText('user@example.com', { pattern: 'email' }); // 'us**@example.com' // Case conversion toCamelCase('hello-world'); // 'helloWorld'

DateTime Utilities

import { formatDate, parseDate, toRelativeTime, getAge } from '@indodev/toolkit/datetime'; // Format dates with Indonesian locale formatDate(new Date('2026-01-02'), 'long'); // '2 Januari 2026' formatDate(new Date('2026-01-02'), 'full'); // 'Jumat, 2 Januari 2026' // Parse Indonesian date format (DD-MM-YYYY) parseDate('02-01-2026'); // Date(2026, 0, 2) parseDate('02/01/2026'); // Date(2026, 0, 2) // Relative time in Indonesian toRelativeTime(new Date(Date.now() - 3600000)); // '1 jam yang lalu' toRelativeTime(new Date(Date.now() + 86400000)); // 'Besok' // Calculate age getAge('1990-06-15', { asString: true }); // '35 Tahun 9 Bulan' getAge(new Date('1990-06-15')); // { years: 35, months: 9, days: 0 }

Framework Integration

React / Next.js — Invoice Component

A realistic invoice component combining currency formatting, terbilang, and date display:

import { formatRupiah, toWords, calculateTax } from '@indodev/toolkit/currency'; import { formatDate } from '@indodev/toolkit/datetime'; interface InvoiceProps { items: { name: string; qty: number; price: number }[]; invoiceNumber: string; customerName: string; } export function Invoice({ items, invoiceNumber, customerName }: InvoiceProps) { const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0); const tax = calculateTax(subtotal, 0.11); // 11% PPN const total = subtotal + tax; return ( <div className="invoice"> <h1>Invoice {invoiceNumber}</h1> <p>Customer: {customerName}</p> <table> <thead> <tr> <th>Item</th> <th>Qty</th> <th>Price</th> <th>Subtotal</th> </tr> </thead> <tbody> {items.map((item, i) => ( <tr key={i}> <td>{item.name}</td> <td>{item.qty}</td> <td>{formatRupiah(item.price)}</td> {/* formatRupiah(1500000) => 'Rp 1.500.000' */} <td>{formatRupiah(item.price * item.qty)}</td> </tr> ))} </tbody> </table> <div className="totals"> <p>Subtotal: {formatRupiah(subtotal)}</p> <p>PPN (11%): {formatRupiah(tax)}</p> <p className="total">Total: {formatRupiah(total)}</p> {/* formatRupiah(1110000) => 'Rp 1.110.000' */} </div> <p className="terbilang"> Terbilang: {toWords(total).toUpperCase()} {/* toWords(1110000) => 'SATU JUTA SERATUS SEPULUH RIBU RUPIAH' */} </p> </div> ); }

Vue 3 — User Profile Card

A profile card with NIK parsing, phone formatting, and masked display:

<script setup> import { ref, computed } from 'vue'; import { validateNIK, parseNIK } from '@indodev/toolkit/nik'; import { validatePhone, formatPhoneNumber } from '@indodev/toolkit/phone'; import { maskText } from '@indodev/toolkit/text'; const nik = ref('3273051205900001'); const phone = ref('081234567890'); const nikValid = computed(() => validateNIK(nik.value)); const nikInfo = computed(() => nikValid.value ? parseNIK(nik.value) : null); // nikInfo.value?.province.name => 'Jawa Barat' // nikInfo.value?.gender => 'Male' const phoneValid = computed(() => validatePhone(phone.value)); const phoneFormatted = computed(() => phoneValid.value ? formatPhoneNumber(phone.value, 'international') : 'Invalid' ); // phoneFormatted.value => '+62 812-3456-7890' const phoneMasked = computed(() => phoneValid.value ? maskText(phone.value, { pattern: 'middle', visibleStart: 4, visibleEnd: 3 }) : '' ); // phoneMasked.value => '0812***7890' </script> <template> <div class="profile-card"> <div class="field"> <label>NIK</label> <input v-model="nik" /> <span v-if="nikValid" class="valid"> {{ nikInfo?.province.name }} - {{ nikInfo?.gender }} </span> <span v-else class="error">NIK tidak valid</span> </div> <div class="field"> <label>Phone</label> <input v-model="phone" /> <span v-if="phoneValid" class="valid"> {{ phoneFormatted }} </span> <span class="masked"> Display: {{ phoneMasked }} </span> </div> </div> </template>

Coming From Other Libraries

If you are migrating from common utilities, here is the mapping:

FromUse InsteadExample
Lodash camelCasetoCamelCase from @indodev/toolkit/texttoCamelCase('hello-world') // ‘helloWorld’
Lodash snakeCasetoSnakeCase from @indodev/toolkit/texttoSnakeCase('helloWorld') // ‘hello_world’
numeral.js formatformatRupiah from @indodev/toolkit/currencyformatRupiah(1500000) // ‘Rp 1.500.000’
libphonenumber-jsformatPhoneNumber from @indodev/toolkit/phoneformatPhoneNumber('081234567890', 'international') // ‘+62 812-3456-7890’
slugifyslugify from @indodev/toolkit/textslugify('Pria & Wanita') // ‘pria-dan-wanita’

Environment Support

Works in all modern environments:

  • Browsers: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
  • Node.js: 18.x or newer
  • Runtimes: Deno, Bun, Cloudflare Workers

Next Steps

Explore the API

Dive into the specific documentation for each module:

Real-world Examples

Check out more complex patterns in our GitHub Examples .

Community

Found a bug or have a feature request? Let us know on GitHub Issues .

Last updated on