Advertisement

JSON Key Naming Best Practices: camelCase vs snake_case

The JSON specification does not say anything about how to name keys. {"userId": 5}, {"user_id": 5}, and {"USER_ID": 5} are all syntactically valid. That freedom is exactly why the question of naming conventions sparks so much debate in API design reviews. This guide lays out the pros and cons of the two main contenders — camelCase and snake_case — and offers concrete recommendations based on what major APIs actually do in production.

JSON Formatter Pretty-print and validate JSON to inspect your key naming.
Try JSON Formatter →

The Two Main Contenders

Almost every JSON API in the wild picks one of two conventions for keys: camelCase (firstName, createdAt) or snake_case (first_name, created_at). A handful use kebab-case (first-name), but this is rare because the hyphen makes the key awkward to access in many languages without bracket notation.

Same response, three styles:

camelCase:
{"firstName": "Ana", "createdAt": "2025-01-15"}

snake_case:
{"first_name": "Ana", "created_at": "2025-01-15"}

kebab-case:
{"first-name": "Ana", "created-at": "2025-01-15"}

The Case for camelCase

JSON was born from JavaScript, and JavaScript uses camelCase for identifiers. When you parse a JSON response in JavaScript, camelCase keys can be accessed with dot notation directly: response.firstName. snake_case keys also work with dot notation, but they feel foreign to the language. Most TypeScript codebases assume camelCase keys.

Other languages that prefer camelCase: Java, C#, Swift, Kotlin, and most modern statically typed languages. If your API is consumed primarily by mobile apps or front-end JavaScript, camelCase is the path of least resistance.

Major APIs that use camelCase: Google Cloud APIs, Microsoft Graph, the Twitter v2 API, most AWS SDKs (in their JSON output), and Stripe (which actually uses snake_case in the wire format but exposes camelCase in its TypeScript bindings).

The Case for snake_case

If your backend is written in Python, Ruby, Rust, or any language that uses snake_case natively, returning snake_case JSON eliminates an entire layer of translation. The keys you read off the wire are the keys you bind to Python variables or Ruby symbols.

snake_case is also easier to read in long key names. customer_account_holder_email scans faster than customerAccountHolderEmail because the underscores act as real word breaks rather than visual case changes.

Major APIs that use snake_case: GitHub REST API, Stripe (wire format), Slack, GitLab, Twilio, Reddit, and most Python-backed services.

What the Major APIs Actually Do

APIConventionBackend language (mostly)
GitHub RESTsnake_caseRuby
Stripesnake_caseRuby
Google CloudcamelCaseMixed (proto3 default)
Microsoft GraphcamelCase.NET
Twitter v2camelCaseScala/Java
Slacksnake_casePHP/Hack
AWSPascalCaseMixed (unusual)
Twiliosnake_casePython
OpenAIsnake_casePython

The takeaway: convention usually follows the language the backend was built in. There is no universal right answer.

camelCase Converter Convert snake_case JSON keys to camelCase for JavaScript clients.
Try camelCase Converter →

What About Mixing Conventions?

The worst thing you can do is mix conventions within a single API. A response that contains {"userId": 5, "first_name": "Ana"} looks like the work of three different teams who never spoke to each other. It forces every client to handle both casing styles and makes the API harder to learn.

If your service evolved over time and you ended up with inconsistent keys, the right fix is a versioned migration: introduce /v2/ that standardizes everything, deprecate /v1/, and remove it after a sunset period.

Handling Translation at the Boundary

When backend and frontend use different conventions, the cleanest pattern is to translate at the network boundary. A Python backend that returns snake_case keys can be consumed by a TypeScript frontend that automatically converts them to camelCase on receipt. Libraries like humps (JavaScript), case-conversion, and the built-in features of Axum (Rust) or FastAPI (Python via Pydantic aliases) make this trivial.

TypeScript boundary conversion (using humps):

import { camelizeKeys } from 'humps';
const response = await fetch('/api/users/5').then(r => r.json());
const user = camelizeKeys(response); // user.firstName, user.createdAt

You can also do quick one-off conversions with our snake_case converter or camelCase converter.

Other Rules That Apply Either Way

Frequently Asked Questions

Does JSON case-sensitivity matter?

Yes. {"User": 1} and {"user": 1} are two different keys. Always be exact about case in client code, especially when parsing JSON manually.

Is camelCase faster to parse than snake_case?

No. Parsing speed is dominated by I/O and string length, not key style. Pick based on readability and consistency, not perceived performance.

What if my consumers use multiple languages?

Pick one wire format (usually based on what's most common in your consumer base) and provide SDKs in each major language that translate automatically. Most large APIs follow this pattern.

Advertisement