snakecase-keys
Convert an object's keys to snake_case, recursively and with full TypeScript type inference.
Repository Health
Technical Analysis
snakecase-keys is a small, focused utility that converts the keys of a JavaScript object (or an array of objects) to snake_case. It handles nested objects recursively by default, exposes fine-grained controls for excluding specific keys, customizing the recursion strategy, and swapping in a fully custom case-conversion function.
Beyond the runtime behavior, the package ships hand-written TypeScript types that mirror the transformation at the type level: calling snakecaseKeys on a typed object returns a type with the actual snake_case key names inferred, not a generic Record. This makes it a common choice for normalizing API payloads (e.g. converting a camelCase JS object into the snake_case shape a backend or third-party API expects) without losing type safety.
What You Get
- Recursive snake_case conversion for objects and arrays of objects, with a
deepflag to opt out of recursion - An
excludeoption accepting strings or regular expressions to skip specific keys from conversion - A
shouldRecurse(key, val)callback for fine-grained control over which nested values get traversed (e.g. skipping Date instances) - A
parsingOptionsobject passed straight through to change-case’s snakeCase for custom word-splitting, delimiters, and locale handling - A
snakeCaseoverride so callers can fully replace the default conversion function with their own - Hand-written TypeScript definitions that infer the actual snake_case key names in the returned type, validated by tsd type tests
Common Use Cases
- Converting a camelCase JavaScript object into the snake_case payload shape expected by a backend API or database layer
- Normalizing request bodies before sending them to services (e.g. Python/Ruby backends) that use snake_case conventions
- Transforming arrays of records returned from an ORM or API client into a consistent snake_case shape for downstream consumers
- Selectively converting keys while excluding specific fields (like already-snake_case IDs or third-party keys) via the
excludeoption
Under The Hood
Architecture
The entire runtime is a single ~60-line ES module (index.js) built around one exported function, snakecaseKeys, that dispatches on whether the input is an array or a plain object and then delegates the actual key/value walking to map-obj, passing a mapper closure that converts each key (unless excluded) via change-case’s snakeCase and optionally attaches shouldRecurse behavior per key. Input validation is strict: both the array and single-object paths check obj.constructor against the plain-object constructor and throw immediately on class instances, primitives, or functions. There are no internal module boundaries beyond this single dispatch — the type definitions in index.d.ts carry equivalent complexity on the compile-time side, implementing the same deep/exclude/shouldRecurse semantics as recursive conditional and mapped types.
Tech Stack
The package is pure ESM (type: module) targeting Node >=22, with change-case (^5.4.4) doing the actual string case conversion, map-obj (^5.0.2) providing the recursive key/value mapping primitive, and type-fest (^5.0.0) supplying the SnakeCase utility type consumed by the hand-written .d.ts. Dev tooling is minimal: standard for zero-config linting and tsd for type-level assertions, run together via a single test script (standard && node --test test.js && tsd) that also exercises Node’s built-in test runner — no bundler, transpiler, or custom build step is needed since the package ships ESM source directly.
Code Quality
test.js uses node:test and node:assert to cover the runtime surface thoroughly for a package this size: basic and nested conversion, repeated-capital key names (fooID -> foo_id), shallow vs. deep mode, arrays and nested arrays of objects, string and regex exclude patterns, custom parsingOptions, shouldRecurse short-circuiting (including around Date instances), custom snakeCase overrides, and explicit error paths for non-plain-object inputs (primitives, functions, class instances) in both the single-object and array forms. Type correctness is separately enforced by index.test-d.ts via tsd, asserting the inferred snake_case key types match expectations. Linting is enforced by standard and CI runs the full test script on GitHub Actions per the badge referenced in the README.
API Design
The public surface is intentionally minimal — one function, one options object — which keeps the learning curve low, but the options are unusually well thought out for a case-conversion utility: shouldRecurse gives callers a value-aware escape hatch from blanket deep conversion (useful for stopping at Date or other non-plain objects), and the fully custom snakeCase override lets consumers replace the conversion algorithm entirely while the type layer still infers a sensible (if generic) return type. The standout is that the type definitions track the runtime behavior closely enough that TypeScript consumers get literal snake_case key names back, not just Record<string, unknown>, which most sibling case-conversion packages don’t attempt.
Used by 4 apps in this directory
Chatwoot
Customer Support
Open-source omnichannel customer support platform with AI-powered agents, live chat, and self-hosting — a full Intercom and Zendesk alternative.
Kibana
Analytics · Monitoring
Your open source window into the Elastic Stack — query, visualize, and act on data stored in Elasticsearch with real-time dashboards, AI-assisted search, and automated alerting.
Logto
Authentication
Open-source auth infrastructure for SaaS and AI apps with OIDC, SAML, and RBAC
Midday
Invoicing Finance · Productivity
All-in-one AI-powered business operations platform for freelancers and solo entrepreneurs to manage invoicing, time tracking, banking, and financial intelligence.