English | Português
A collection of small, tree-shakeable TypeScript utility functions. Shipped as ESM + CJS with type definitions included.
Some helpers are tailored to Brazil (CPF, CNPJ, CEP, BR phone masks, PT-BR formatting), while the rest are locale-agnostic.
npm install @julianobazzi/utilsimport { formatDate, contains, omitFields } from "@julianobazzi/utils";
formatDate("2024-01-02"); // "02/01/24"
contains("a", ["a", "b"]); // true
omitFields({ a: 1, b: 2 }, ["b"]); // { a: 1 }CommonJS:
const { onlyNumbers } = require("@julianobazzi/utils");
onlyNumbers("(11) 98765-4321"); // "11987654321"All functions are exported flat from the package root, grouped internally by purpose.
formatDate(value?, { simplified?, fallback? })—DD/MM/YY(orDD/MM/YYYY); invalid dates →fallbackformatDateTime(date?, { simplified?, showSeconds?, fallback? })—DD/MM/YY HH:mm(optional 4-digit year and/or:ss); invalid dates →fallbackformatMonth(value?, { fallback? })—MM/YYYY; invalid dates →fallbackgetMonthName(value?, { short?, fallback?, casing? })— PT-BR month name, e.g.Julho/JulgetWeekDayName(value?, { short?, fallback?, casing? })— PT-BR weekday name, e.g.Sábado/SábformatHour(value?, { simplified?, fallback? })—HH:mm(orHH:mm:ss); invalid dates →fallbackformatMinutesToDuration(minutes?, { fallback?, spaced? })— human-readable duration, e.g.1h e 30 min(rounded to whole minutes/seconds first; negatives treated as positive)formatSecondsToDuration(seconds?, { fallback?, spaced? })— same, from seconds (≥60s rounded to minutes)formatDuration(minutes?, { fallback?, spaced? })— deprecated alias offormatMinutesToDurationformatCurrency(value?, divisor = 100, { fallback? })— BRL currency, e.g.R$ 19,90formatCompactNumber(value?, { decimals?, fallback? })— compact notation (EN), e.g.1.5M,100KformatPercentage(value?, round = false, { fallback? })— percentage, e.g.12,50%formatBoolean(value?, { casing? })—Sim/Não(PT-BR yes/no)formatPhone(phone?, { fallback? })— BR phone mask (10 or 11 digits)formatBytes(bytes?, round = false, { casing? })— human-readable size, e.g.1.50 KBformatSecondsToTime(value?, showSeconds = true)—HH:mm:ss(orHH:mm); pure arithmetic, hours keep counting past 24 (90000→25:00:00)formatTimeAgo(date?, { fallback?, casing? })— elapsed time in PT-BR, e.g.5 dias; invalid dates →fallback, future dates →1 minutoformatAddress(address, { fallback? })— builds a single-line addressformatCityAndState(city?, state?, { fallback?, separator?, casing? })—"City - UF"(empty when both missing)formatWeekDay(date?, { fallback?, casing?, dateFormat? })— date + abbreviated weekday, e.g.15/6 - Sáb(dateFormatdefaultD/M); invalid dates →fallbackgetAge(birthDate?)— age in full years (today); missing/invalid/future →0formatAge(birthDate?, { fallback? })— age as PT-BR text, e.g."36 anos"/"1 ano"formatCPF(value?, { fallback? })—000.000.000-00(pads with zeros; > 11 digits → plain digits)formatCNPJ(value?, { fallback? })—00.000.000/0000-00(supports alphanumeric CNPJ)formatDocument(value?, { fallback? })— formats as CPF or CNPJ based on lengthformatPostalCode(value?, { fallback? })— BR postal code (CEP)00000-000(> 8 digits → plain digits)formatPlate(value?, { fallback? })— BR license plate: legacy →ABC-1234, Mercosul keepsABC1D23formatPIS(value?, { fallback? })— PIS/PASEP000.00000.00-0(> 11 digits → plain digits)formatLongDate(value?, { fallback?, casing? })— date in full PT-BR, e.g.1º de julho de 2026numberToWords(value?, { fallback? })— integer spelled in PT-BR up to the trillions, e.g.mil duzentos e trinta e quatrocurrencyToWords(value?, divisor = 100, { fallback? })— BRL amount spelled in PT-BR (mirrorsformatCurrency), e.g.dezenove reais e noventa centavosappendValue(base?, value?, { separator?, fallback?, casing? })— joins two texts (each trimmed), e.g."a; b"applyCasing(value, casing?)—lowercase/uppercase/titlecase(titlecase keeps the rest of each word, so"KB"survives)removeAccents(value?)— strips accents, e.g.João→JoaoonlyNumbers(value?)— removes everything that is not a digitonlyAlphanumeric(value?)— removes non-alphanumerics + uppercase ("12.abc"→"12ABC")formatWithPattern(value?, pattern?)— char-agnostic mask (#= next char), e.g.'12345678900'+'###.###.###-##'→123.456.789-00truncate(value?, length = 40)— trims text and appends...getLastCharacter(value?)— last character of a stringabbreviateName(name?, { casing? })—"John Smith"→"John S."(titlecase normalizes:"JOAO SILVA"→"Joao S."; blank input →"")getFirstAndLastWord(value?, { fallback?, casing? })— keeps only the first and last word:"Analista de Sistemas"→"Analista Sistemas"joinByKey(values, key, dividerOrOptions?)— joins one property from each object, skipping empty values; 3rd arg is a divider string or{ divider?, sort?, unique? }, wheresort(true | "asc" | "desc") orders bykeyfirst anduniquedrops duplicatesmaskSecret(value?, { visibleStart = 5, visibleEnd = 5, mask = '••••••' })— partially masks a secret, keeping the ends visible, e.g.$2y$1••••••lMnOp(short values → mask only;visibleStart/visibleEndare clamped to ≥ 0, so0hides that side entirely)slugify(value?)— URL-safe slug (accent-free, lowercase, hyphenated), e.g."São Paulo"→sao-paulosanitizeSpreadsheetCell(value?)— guards CSV/Excel formula injection: prefixes'when the value starts with= + - @(tab/CR)buildWhatsAppUrl(phone?, message?, { countryCode = '55', fallback? })—wa.melink;countryCodeacceptsnullto omit, e.g.https://wa.me/5511987654321?text=...buildPhoneUrl(phone?, { countryCode = '55', fallback? })—tel:link, e.g.tel:+5511987654321(countryCode: null→tel:11987654321)buildEmailUrl(email?, { subject?, body?, fallback? })—mailto:link with optional encodedsubject/body; the address is URL-encoded too (no header injection via?/&)buildInstagramUrl(username?, { fallback? })—https://instagram.com/<handle>(strips a leading@; handle is URL-encoded)buildFacebookUrl(username?, { fallback? })—https://facebook.com/<handle>(strips a leading@; handle is URL-encoded)buildLinkedInUrl(handle?, { type = 'profile', fallback? })—https://linkedin.com/in/...or/company/...(viatype; handle is URL-encoded)formatEnvironment(value?)— normalizes aNODE_ENVstring toproduction | homologation | development | test(unknown →production)
Date-window helpers for filters and pickers. All dates in/out use YYYY-MM-DD; ranges are inclusive on both ends.
getDateRange(days, { reference?, offset? })— window ofdaysdays ending atreference(default today), e.g.getDateRange(7)→ last 7 days;offsetshifts it back ({ offset: 1 }ends yesterday)getMonthRange(month, { reference? })— full range of aYYYY-MMmonth; an ongoing month is clamped toreferenceinstead of a future last daygetMonthOptions({ count = 12, reference?, min? })— month picker list, newest first, as{ id: 'YYYY-MM', name: 'Julho/2026' }(works withfindOptionById/getLabelById);mindrops earlier months
buildQueryParams(params?, { ignore?, prefix? })— serializes an object into a query string; skipsnull/undefined, expands arrays into repeated keys, returns""when empty, e.g.{ id: [1, 2] }→?id=1&id=2parseQueryParams(value?)— the inverse; repeated keys become arrays (whichObject.fromEntries(new URLSearchParams(...))loses). Accepts the string with or without a leading?; prototype keys (__proto__,constructor,prototype) are skipped
Input mask patterns (react-input-mask convention: 9 = digit, a = letter, * = alphanumeric).
CPF_MASK—999.999.999-99CNPJ_MASK—99.999.999/9999-99CNPJ_ALPHANUMERIC_MASK—**.***.***/****-99PHONE_MASK—(99) 9999-9999(landline)CELLPHONE_MASK—(99) 99999-9999(mobile)POSTAL_CODE_MASK—99999-999(CEP)PLATE_MASK—aaa-9*99(license plate; the*slot covers legacy and Mercosul)PIS_MASK—999.99999.99-9(PIS/PASEP)
contains(value, items)—trueifvalueis initemsisOdd(value)—truefor odd numbers (handles negatives)isValidJson(value?)—trueif the string is valid JSONisValidBarcode(value)—truefor a valid EAN/GTIN check digitisValidUrl(value)—truefor a validhttp/httpsURLisDateString(value?)—truefor an ISO dateYYYY-MM-DD(no time)isDateTimeString(value?)—truefor a date-time (Tor space separator,HH:mm[:ss])isValidPhone(value?)—truefor a valid BR phone (landline or mobile)isBirthday(value?)—trueif the date falls on today's day/monthisWeekend(value?)—trueif the date falls on a weekend (Saturday or Sunday)isWeekday(value?)—trueif the date falls on a weekday (Monday to Friday)isSunday(value?),isMonday(value?),isTuesday(value?),isWednesday(value?),isThursday(value?),isFriday(value?),isSaturday(value?)—trueif the date falls on that day of the week (accepts ISO string orDate)isValidCPF(value?)—truefor a CPF with valid check digitsisValidCNPJ(value?)—truefor a valid CNPJ (numeric or alphanumeric)isValidDocument(value?)— validates as CPF or CNPJ based on lengthisValidPostalCode(value?)—truefor an 8-digit CEPisValidEmail(value?)—truefor a valid email (TLDs of 2–63 letters)isValidUF(value?)—truefor a valid BR state abbreviation (case-insensitive)isValidPlate(value?)—truefor a BR license plate (legacyAAA9999or MercosulAAA9A99)isValidPIS(value?)—truefor a PIS/PASEP with a valid check digitisValidRenavam(value?)—truefor a valid RENAVAM (11 digits or legacy 9–10)isValidCNH(value?)—truefor a CNH with valid check digits (Denatran algorithm)isValidVoterId(value?)—truefor a valid voter registration number (título de eleitor)isValidBoleto(value?)—truefor a valid boleto digitable line (bank slip or collection)
precisionRound(value?, precision = 2)— rounds to N decimal places without float surprises (1.005→1.01)formatInteger(value?, { fallback? })— rounds to the nearest integertoPositive(value?)— clamps to a non-negative valuegetRandomInt(min = 1, max = 100)— random integer in range (inclusive)safeDivide(value1, value2?)— divides; returns 0 when the divisor is ≤ 0 or missingtoCents(value?)— amount → integer cents, e.g.19.9→1990(inverse offormatCurrency)parseCurrencyToCents(value?)— BRL string → integer cents, e.g."R$ 1.234,56"→123456
getProperty(obj, key)— type-safe property accessomitFields(obj, keys)— shallow copy withoutkeysgetOptionId(option?)— extracts theidfrom an option/entitygetListIds(list?)— maps a list of entities to theiridsfindOptionById(options?, value?)— finds the option whose id matchesvalue(string compare), ornullfindOptionsByIds(options?, value?)— maps each id invalueto its option, dropping non-matchesgetLabelById(options?, value?, key = "name", fallback = "")— option's field as a string by id, orfallbackmaskFields(value, fields, { mask? })— deep-clones a value, masking the string fields listed at any depth (defaults tomaskSecret)isEqualIgnoringKeys(a, b, keys)— structural comparison ignoringkeysat any depth (key order irrelevant, array order significant)swapAtIndex(list, index, offset)— swaps two items immutably; out of bounds returns the same referenceupdateAtIndex(list, index, updater)— copy oflistwith only the item atindexreplaced
parseIds(...ids)— comma-separated id strings →number[](trimmed; empty and non-integer entries dropped, so"1,,2"→[1, 2])resolveIdsToObjects(ids?, resolver, params?)— resolves an id list into objects via an async resolver (in parallel)resolveList(value?, resolver, params?)—parseIds+resolveIdsToObjects; accepts a string or string arrayresolveId(value?, resolver, params?)— resolves the first valid id into an object, ornullparseJson<T>(value?)—JSON.parsereturningundefinedinstead of throwing (a parsed"null"staysnull)toStringArray(value)— narrows an unknown value tostring[], dropping entries of other types; non-arrays →[]getFileExtension(value?)— extension of a path/URL without the dot; ignores?query/#hashand dotted hosts, dotfiles →""parseInlineMarkup(value?, { boldMarker?, italicMarker? })— WhatsApp-style*bold*/_italic_(nestable) →{ text, bold?, italic? }[]
diffLines(before?, after?)— line diff (longest common subsequence) returning{ beforeLines, afterLines }, each line flaggedchangedstripInlineMarkup(value?, { boldMarker?, italicMarker? })— plain text without the*/_markers, ready fortruncate, meta tags or e-mail
The rest are dependency-free transforms with the (value, originalValue) => string shape (matches yup.transform); they wrap the base helpers.
onlyNumbersTransform(_value, originalValue)— wrapsonlyNumbers, e.g.yup.string().transform(onlyNumbersTransform)onlyAlphanumericTransform(_value, originalValue)— wrapsonlyAlphanumeric, e.g.yup.string().transform(onlyAlphanumericTransform)
Every generator produces random values that pass the matching validator. They are backed by Math.random and meant for tests and seeds — except generateOTP, which draws from the Web Crypto API and is safe for production use.
generateOTP({ length? })— numeric one-time password (default 6 digits), leading zeros preserved; cryptographically secure, works in Node and the browsergenerateCPF({ formatted? })— valid CPF;formatted: true→000.000.000-00generateCNPJ({ formatted?, alphanumeric? })— valid CNPJ (branch0001);alphanumeric: true→ 2026 formatgeneratePIS({ formatted? })— valid PIS/PASEP;formatted: true→000.00000.00-0generateRenavam({ legacy? })— valid RENAVAM;legacy: true→ old 9-digit formatgenerateCNH()— valid CNH (Denatran algorithm)generateVoterId()— valid voter registration number (random state code 01–28)generatePlate({ mercosul?, formatted? })— valid plate; Mercosul by default,mercosul: false→ legacygenerateBarcode({ length? })— valid EAN/GTIN barcode (8/12/13/14 digits, default EAN-13)generateBoleto({ type?, formatted? })— valid boleto digitable line;bankby default (47 digits),type: 'collection'→ arrecadação (48 digits)generatePhone({ mobile?, formatted? })— valid BR phone; mobile by default,mobile: false→ landlinegeneratePostalCode({ formatted? })— 8-digit CEP;formatted: true→00000-000generateUF()— random state code from the 27 federative units, e.g.SPgenerateEmail({ domain?, length? })— valid email; defaults to the RFC 2606 reservedexample.com, which never routes
loadImageFromBlob(blob)—Promise<HTMLImageElement>getImageDimensions(file)—Promise<{ width, height, extension }>isPhotoLandscape(fileOrUrl)—Promise<boolean>(width > height)isNotificationsSupported()— checks web push support (falseduring SSR)openInNewTab(url?, { target?, features? })—window.openwithnoopener,noreferreralways applied (nullduring SSR or when blocked)downloadBlob(blob, fileName)— triggers a file download and revokes the object URLdownloadJson(data, fileName, { space? })— serializes to JSON and downloads it (indented by default)
| Script | Description |
|---|---|
npm run build |
Bundles into dist/ (ESM + CJS + .d.ts) via tsup |
npm run dev |
Build in watch mode |
npm run test |
Runs the tests once (Vitest) |
npm run test:watch |
Runs the tests in watch mode |
npm run test:coverage |
Runs the tests with coverage (thresholds enforced) |
npm run typecheck |
Type-checks with tsc --noEmit |
npm run lint |
Lint + format check (Biome) |
npm run lint:fix |
Applies safe lint/format fixes |
- Create
src/<group>/<name>.tswith a named export (export function <name>). - Add
src/<group>/<name>.test.tswith Vitest tests. - Re-export it from the group barrel
src/<group>/index.ts. - New group? Create
src/<group>/index.tsand include it insrc/index.ts.
MIT © Juliano Bazzi