⚡ CaseCraft

Snake Case Converter

Convert phrases into snake_case with underscores — the standard for Python, SQL, and file names.

0 characters · 0 words · 0 lines

Your converted text will appear here…

What is snake case?

snake_case writes everything in lowercase and separates words with underscores: "user last name" becomes "user_last_name". Unlike camelCase it stays all-lowercase, which makes it easy to read, easy to type, and unambiguous about where each word boundary is. There is no capitalization to decode — total_order_value, order_created_at, max_retry_count all read left to right like plain words with gaps between them.

The convention dominates in Python (variables, functions, module names), SQL (table and column names), environment variables in many stacks, and file naming for data pipelines. This converter applies the full rule set at once: lowercase every letter, convert any run of spaces, hyphens, or symbols into a single underscore, and trim leading and trailing underscores — "Total -- Order Value" becomes "total_order_value".

That last part matters more than it looks. Paste "Order #47 — Status!!" into most tools and you get order__47____status__ back, double underscores and all. This one collapses every run of junk into a single underscore and trims the edges, so the output is always a legal identifier you can paste straight into an editor or a migration file without a cleanup pass first.

The formal rules: what makes a valid snake_case identifier

snake_case has exactly four hard rules, and most of the bugs come from breaking the third one without noticing. First, every letter is lowercase — user_last_name, not User_Last_Name. Second, words are separated by single underscores — user_last_name, not user__last_name. Third, the identifier must not start with a digit — 2024_revenue is illegal in Python, Ruby, and most SQL dialects, so the safe rewrite is revenue_2024. Fourth, only letters, digits, and underscores are allowed — everything else is dropped or collapsed.

The interesting case is converting from PascalCase or camelCase, where there are no spaces to split on. "MyProfileImage" needs to become "my_profile_image", which requires detecting where one word ends and the next begins. Most converters insert an underscore before any uppercase letter that follows a lowercase letter: camelCase becomes camel_case, MyProfileImage becomes my_profile_image. That works cleanly for normal compound words.

The contested case is consecutive capitals: HTTPResponse, XMLParser, URLBuilder. The naive approach splits every uppercase-to-lowercase transition and produces h_t_t_p_response — technically valid but unreadable. The better strategy treats a run of consecutive capitals as a single word unit, inserting an underscore only where the run ends before a new lowercase word: HTTPResponse becomes http_response, XMLParser becomes xml_parser. This is what Python's inflection library, Django's ORM, and most production code generators do. Neither strategy is universal — a team writing HTTP_RESPONSE as a constant would argue the opposite — so the important thing is to pick one, document it, and convert the whole list mechanically rather than field by field.

Seven naming styles, one phrase

The same phrase — "user profile image url" — written in all seven major naming conventions reveals exactly how the styles differ. In snake_case it is user_profile_image_url, the default for Python variables and SQL columns. The all-caps cousin SCREAMING_SNAKE_CASE writes it USER_PROFILE_IMAGE_URL, reserved for constants and environment variables like MAX_RETRIES or DATABASE_URL. kebab-case uses hyphens: user-profile-image-url, the convention for URLs, CSS class names, and HTML data attributes.

Moving to the capitalized family: camelCase joins words with a capital at each boundary except the first — userProfileImageUrl, the JavaScript and Java default for variables and functions. PascalCase (UpperCamelCase) capitalizes the first word too — UserProfileImageUrl — the convention for class names, React components, and TypeScript interfaces. dot.case separates with periods — user.profile.image.url — used in some configuration systems and property key paths. Train-Case uses hyphens with each word capitalized: User-Profile-Image-Url, occasionally seen in HTTP header names.

The pattern: the separator is one variable (nothing, underscore, hyphen, dot, hyphen-with-caps), the capitalization is the other (all lower, all upper, mixed). Every well-designed system uses exactly one column consistently, and whenever two styles appear in the same codebase — the API emits snake_case, the frontend expects camelCase — a mapping step exists somewhere. Paste the server's field list at /camel-case-converter for the JS side, or paste the client's camelCase list here for the Python side.

How the major languages and ecosystems use snake_case

Python is the canonical snake_case language. PEP 8 specifies lowercase_with_underscores for function names, variable names, method names, and module file names. Class names use CapWords (PascalCase). Constants use ALL_CAPS_WITH_UNDERSCORES. This three-way split is strict enough that every major Python linter — flake8, pylint, ruff — flags violations automatically, and most CI pipelines check it. The result is that well-maintained Python codebases look nearly identical in shape, even across teams that have never met.

Ruby follows almost the same rules. Methods and local variables are lowercase_with_underscores, constants are ALL_CAPS, and class and module names are CamelCase (Ruby's constant syntax). File names for libraries conventionally mirror the module name: my_library.rb for module MyLibrary. Rails builds the snake_case-to-PascalCase mapping into the framework itself — a model file named user_profile.rb is expected to define a class called UserProfile, and deviation breaks auto-loading silently.

Rust uses snake_case for variables, functions, and modules, and PascalCase for types, traits, and enum variants. The compiler itself warns on non-conforming names — a function named doSomething triggers a rustc warning without any linter configured. Go takes the opposite approach for exported names: it does not use underscores at all, preferring MixedCaps (GetUser, not get_user). Unexported identifiers use lowercase-first mixed caps: getUser. File names in Go are conventionally lowercase with underscores: my_handler_test.go.

In SQL, table names and column names are almost universally written in lowercase snake_case on Postgres, MySQL, BigQuery, and Snowflake. The technical reason: Postgres folds unquoted identifiers to lowercase automatically. A column created as "CreatedAt" without quotes becomes createdat the moment you stop quoting it everywhere, which is why nobody uses camelCase column names in SQL — fighting the folding costs quotes on every reference.

JSON API naming is a genuine industry argument with no settled answer. The Google JSON Style Guide and most JavaScript-first APIs (Stripe, GitHub v3) use camelCase. Django REST Framework and Python-first APIs emit snake_case by default. The JSON:API specification uses kebab-case member names. The practical resolution is to pick one canonical wire format and configure the serializer at the boundary rather than converting by hand on every response handler.

Python: one convention, two temperatures

Python is the loudest snake_case shop in software, and its style guide PEP 8 draws a line people constantly trip over. Variables, functions, and module names use lower_snake_case — user_created_at, send_invoice, date_utils. Constants use UPPER_SNAKE_CASE — MAX_RETRIES, DEFAULT_TIMEOUT, API_BASE_URL. Same words, same underscores, different temperature, and linters like flake8 or ruff will flag every violation in a pull request.

In practice that means a typical config block holds both: DEFAULT_PAGE_SIZE = 25 sitting three lines above page_size = request.query_params.get("page_size"). The converter handles both directions from the same input — type "default page size" and you have DEFAULT_PAGE_SIZE in one click via the UPPERCASE mode, then lower_snake_case again in the snake mode. No re-typing, no hunting for the caps-lock slip.

The other Python trap is copy-pasted prose. Pull a column description out of a ticket ("number of failed login attempts in the last 24 hours") and try to name a variable from it, and you are deleting spaces by hand — including the two spaces that hide between "in the". Paste it in and it comes back as failed_login_attempts_last_24_hours, one paste instead of thirty backspace presses.

Edge cases: abbreviations, numbers, and non-ASCII characters

Abbreviations are where snake_case gets opinionated. Three strategies exist for handling GPS, URL, and ID: keep them uppercase as a block (user_GPS_coordinates), lowercase them completely (user_gps_coordinates), or lowercase only when they appear as a standalone word in the name (gps_coordinates but user_id not user_i_d). The third strategy — the Python community convention — is the most common: GPS becomes gps, URL becomes url, ID becomes id. This avoids the awkward underscore-per-letter explosion that a naive split creates.

Numbers are straightforward: digits are legal in identifiers everywhere except the leading position. "mp3 player" becomes mp3_player — the digit stays adjacent to the preceding letters because there is no separator between them in the original. "page 2 results" becomes page_2_results. "2024 revenue" cannot start with the digit in Python or most SQL dialects, so the convention is revenue_2024 or report_2024.

Non-ASCII characters — accented letters like é, ñ, ü; Arabic, Chinese, or Cyrillic scripts; full-width Unicode — are technically permitted in Python 3 identifiers but almost never used in practice. The universal safe convention is to transliterate or drop non-ASCII before converting: "café name" should become cafe_name, "über product" should become uber_product. Identifiers that cross a language, database, or file system boundary need to be safe in all of them simultaneously, and pure ASCII is the only option that satisfies every constraint at once.

Writing the converter yourself: how the regex logic works

Understanding the conversion logic makes the edge-case behavior less surprising. The transformation has three steps, and the order matters. Step one detects camelCase boundaries: find any point where a lowercase letter or digit immediately precedes an uppercase letter and insert an underscore there. This turns getUserById into get_User_By_Id. Step two handles runs of consecutive capitals: find where a sequence of uppercase letters ends immediately before a new capitalized word — as in "HTTP" followed by "Response" — and insert an underscore. Step three lowercases everything, replaces remaining non-alphanumeric characters with a single underscore, and trims the edges.

The key reason to run the boundary detection before the lowercase step is that case information is destroyed by lowercasing. Run step three first and every capital letter becomes an indistinguishable lowercase letter — you can no longer tell where getUserById ended and its words began. Steps one and two must come first.

A single-pass regex that only looks for lowercase-before-uppercase transitions handles simple camelCase correctly but fails on abbreviations: it turns HTTPResponse into H_T_T_P_Response rather than HTTP_Response, because it inserts an underscore before every uppercase letter that follows a lowercase one, and each letter in "HTTP" becomes its own segment. The two-step approach treats the run as a unit, inserting the underscore only between the run and the next word — which is why http_response reads correctly instead of h_t_t_p_response. This is the failure mode that catches most one-liner regex attempts, and why libraries like Python's inflection or npm's change-case use two-stage boundary detection internally.

For most use cases — converting prose phrases, spreadsheet headers, and API field names — the converter on this page handles the job without writing a line of code. The regex explanation matters when building a migration script, a code generator, or a serializer layer that converts names programmatically at scale. In Python, import re and apply re.sub three times in the order described. In JavaScript, chain three .replace() calls. The structure is identical across languages; only the regex syntax differs slightly.

Database columns: from messy CSV headers to user_created_at

Nobody names a database column "Order Created At". Columns live in lower_snake_case — user_created_at, order_total_cents, is_subscription_active — on every serious stack: Postgres, MySQL, Snowflake, BigQuery, Django models, Rails migrations. But the data that feeds those tables usually starts as a spreadsheet somebody on the sales team built, and spreadsheet headers are wild territory.

Export a real CSV and the header row reads like "Order ID", "order-id", "ORDER TOTAL ", "Customer#Email", maybe "2024 Revenue (USD)". Every one of those needs to become a column name, and pasting them raw into a CREATE TABLE breaks the migration the moment the parser hits the hash or the parenthesized currency. The usual workflow is one header at a time, retyped by hand — for a 60-column extract, that is an afternoon.

The batch route: paste the whole header row (or the whole column of names) with one phrase per line, and all 60 come back clean in about a second — order_id, order_total_usd, customer_email — each one valid on the first try. Copy the block straight into your migration. While you are at it, run the same column through the Remove Extra Spaces tool (/remove-extra-spaces), because headers copied out of Excel love to carry invisible trailing spaces that survive case conversion and then bite you in a JOIN six weeks later.

ORM auto-mapping is worth understanding before trusting it. Django derives column names from model field names using a simple lowercase pass — a field called orderTotal creates a column called ordertotal, not order_total. Rails uses an underscore inflector that is usually correct but has documented exceptions for irregular plurals and abbreviations. If you are migrating a schema and relying on the ORM to derive column names, verify the mapping on a staging migration before hitting production.

JSON APIs: camelCase in the browser, snake_case in the backend

A lot of API integration work is quietly a case-conversion job. JavaScript and TypeScript teams write camelCase — userId, createdAt, orderTotal — but the backends they talk to are Django, Rails, or a Python service, and those ecosystems serialize JSON in snake_case: user_id, created_at, order_total. So every field crossing the boundary gets renamed somewhere, and the manual way is typing the mapping by hand into a transform layer.

When you are wiring up a response handler, paste the JSON keys — one per line, quotes and all — into the converter. user_id, shipping_address_2, and subscription_cancelled_at all survive intact because underscores are already the target format, and a camelCase batch like userId, shippingAddress2 flips to snake in one pass. Working the other direction, the Camel Case Converter at /camel-case-converter is the same tool pointed the other way, and the Case Converter at /case-converter shows every style from one input so you can see both sides of the contract at once.

When an API version upgrade requires renaming fields — say order_total_cents gets renamed to subtotal_amount_cents in v2 — the rename affects every client that parses the old name. Convert the full old-field list and the new-field list side by side, use the comparison to generate a compatibility shim, and document both names in the API changelog before cutting the new version. The teams that do this methodically before the version ships spend minutes; the teams that do not spend their first week after launch debugging silent field mismatches in client libraries they do not own.

The boring but real payoff: fewer KeyError bugs. A field that arrives as orderTotal when the backend sends order_total is a 500 in production and a two-hour dig through logs. Teams that standardize the mapping — one paste, one copy, one mapping file — stop shipping that class of bug, because the names stopped being retyped by hand.

Airflow, Elasticsearch, and the tools that only accept one shape

Some systems do not merely prefer snake_case — they refuse anything else. Apache Airflow requires DAG task_id and DAG id values to match a strict pattern, and underscores are the only word separator allowed; name a task "sendWeeklyReport" or "send weekly report" and the scheduler rejects the DAG before it runs a single task. Since an analytics org routinely runs 100–300 tasks across its DAGs, the naming gets standardized early: extract_orders, transform_orders, load_orders.

Elasticsearch and OpenSearch field names have the same flavor. Dots and weird characters carry special meaning in queries and aggregations, so fields end up in snake_case — event_type, session_duration_ms, page_view_count — and the mapping is much happier for it. kubectl labels want key/value pairs built from alphanumeric characters plus underscores, dashes, and dots, and a label like cost_center_eu from "Cost Center EU" fits cleanly.

The pattern across all three: the tool's validator, not your preference, picks the casing. When the constraint is underscores, convert once at the boundary instead of debugging a rejected DAG or a dropped index at 11 p.m. And if the cleanup goes beyond casing — smart quotes, zero-width characters from a doc export — run the text through Strip Formatting (/strip-formatting) before converting, so invisible characters do not sneak into a task_id.

Shell scripts and Makefiles: two dialects worth knowing

Bash variables are traditionally UPPER_SNAKE_CASE — INPUT_DIR, OUTPUT_FILE, RETRY_COUNT — and the convention exists for a hard technical reason: the shell's own environment variables (PATH, HOME, USER) are uppercase, so keeping your script's variables uppercase-and-underscored avoids colliding with them. Write LOG_FILE=./logs/run.log once at the top and the rest of the script stays readable.

Makefiles flip it: variables are customarily lowercase_with_underscores for anything the user might override on the command line (build_dir, output_name), and UPPER_SNAKE for the ones they should not touch. make knows nothing about any of this — it is a human convention — but every open-source Makefile you will ever read follows it, so matching it keeps your diff looking native.

The converter earns its keep in both, because shell names often start as comments or ticket titles. "Path to the raw export folder" becomes path_to_the_raw_export_folder in one paste, and you trim the words you do not need in the editor rather than typing the rest by hand.

Excel and Power Query: cleaning column names at the source

Power Query will happily import a sheet whose headers are "Sales Amount", "SALES AMOUNT", and "sales amount " (with a trailing space) — and then three of your measures break because the headers are not actually identical. Renaming every column in the Applied Steps pane is click-click-type per column, which on a 40-column finance extract is a slow afternoon of careful typing.

The faster loop: copy the header row, paste it here one name per line, and get sales_amount, sales_amount, sales_amount back — the duplicates now announce themselves instantly, which is exactly the point. Fix the two that differed in the sheet, re-import, and the model stops complaining. The same loop works for Power BI, Tableau column aliases, and dbt model column names, where the convention is lower_snake_case throughout.

One habit worth stealing from data engineers: convert the headers before the data ships, not after. A sheet whose headers are already snake_case passes through every downstream tool — warehouse, BI layer, Python script — with zero renames, and nobody has to remember which of the four staging tables spelled the column customer_email.

The batch workflow: one line per name

A single phrase converts in the time it takes to paste it. The real speedup is batches: paste one name per line — a CSV header row, a list of event names, a whole table schema — and every line converts independently in order. Line 1 and line 60 get identical treatment, and the output keeps your input's order, so row 12 is still row 12 when you paste it back into the spreadsheet or the migration.

Nothing gets deduplicated, sorted, or "helpfully" reordered behind your back, so the output drops straight over the input. That makes the paste-convert-paste loop safe for generated lists: a 200-line list of metric names from a config file converts as reliably as a 3-line todo.

Spot-check the two or three messiest lines — the one with the parenthesis, the one with the double space — and if those came out right, the rest did too. For text that needs a deeper scrub before casing, Remove Extra Spaces handles doubled spaces and trailing whitespace, and Strip Formatting (/strip-formatting) removes invisible characters that would otherwise become mystery underscores.

snake_case versus every other style on this site

snake_case has three neighbors it gets confused with, and picking the wrong one is a one-character bug. kebab-case (order-status) uses hyphens — it is the standard for URLs, CSS class names, and HTML data attributes, because hyphens are URL-safe and CSS is case-insensitive. But hyphens are illegal in Python variable names and awkward in most SQL dialects, so kebab-case does not survive contact with code. SCREAMING_SNAKE_CASE is the same underscores in caps, reserved for constants and environment variables.

camelCase (orderStatus) is the JavaScript and Java side of the fence — the Case Converter at /case-converter keeps all of them side by side from one input if you are translating a schema between two worlds. The Title Case Converter at /title-case-converter handles the editorial styles when you need to go the other direction — from identifier back to a readable headline or label.

A quick rule of thumb that holds up: code identifiers in Python, SQL, and data tooling → snake_case; identifiers in JavaScript and Java → camelCase; anything in a URL or CSS selector → kebab-case; constants and environment variables → UPPER_SNAKE. When a name has to cross two of those worlds, convert it at the boundary rather than letting each layer improvise.

Mistakes the converter quietly prevents

The classic hand-made snake_case bug is the double underscore. Deleting spaces one at a time from "user created at" — with two real spaces between words, as copied out of a PDF — leaves user__created__at, which is a different identifier from user_created_at and will fail a lookup that took ten minutes to write. Collapsing every space run to a single underscore is the whole reason to automate this.

The runner-up is the symbol that survives. "Revenue (USD)" hand-converted becomes revenue_(usd) — parentheses are not legal in a Python variable name and most SQL dialects, and the error only surfaces when the migration runs. Stripping non-alphanumeric characters during the conversion catches it before the editor does.

Third is the invisible one: trailing spaces. "order_total " with a trailing space looks identical in an editor and in a diff, but a column name with a trailing space is a bug that hides until a JOIN compares it against a clean column and matches nothing. Converting trims both ends of every name, so what you paste is exactly what the database stores.

FAQ

How do I convert text to snake_case?

Paste your text into the box above — it converts to snake_case instantly: every letter lowercased, spaces and symbols replaced by single underscores, leading and trailing underscores trimmed. "Order Created At!" becomes order_created_at. Multiple names work too: paste one phrase per line and each line converts independently, so a 60-column CSV header row comes back clean in about a second. Click Copy to grab the result or Download to save it as a .txt file. Nothing leaves your browser.

What is the difference between snake_case and kebab-case?

Both use lowercase words and a separator between them, but the separator differs. snake_case uses underscores — order_status, user_created_at — and is the standard for Python variables, SQL columns, and environment variables. kebab-case uses hyphens — order-status, user-created-at — and is the standard for URLs, CSS class names, and HTML data attributes. Hyphens are illegal in Python variable names and parse as subtraction in JavaScript, which is why code uses underscores while URLs use hyphens. Pick based on where the name will live.

How are abbreviations like URL, ID, or HTTP handled?

The standard Python convention lowercases abbreviations completely: URL becomes url, HTTP becomes http, ID becomes id. So "user URL" becomes user_url, not user_URL or user_u_r_l. When splitting PascalCase identifiers like HTTPResponse or URLParser, the recommended strategy is to treat consecutive capitals as a single unit: HTTPResponse becomes http_response, URLParser becomes url_parser. A naive per-character split produces the unreadable h_t_t_p_response — that is the failure mode this two-stage approach is designed to avoid.

What is SCREAMING_SNAKE_CASE and when do I use it?

SCREAMING_SNAKE_CASE is snake_case with all letters uppercase: MAX_RETRIES, DEFAULT_TIMEOUT_MS, API_BASE_URL. It signals "constant — do not reassign at runtime." PEP 8 mandates it for Python constants, and it is the convention for environment variables in virtually every language and shell. To get the constant form from this converter, convert to snake_case first and then click the UPPERCASE mode button: default_timeout_ms becomes DEFAULT_TIMEOUT_MS in two clicks, with the same input still in the box.

Will numbers survive the conversion?

Yes — digits are kept exactly where they are. "shipping address 2" becomes shipping_address_2, "mp3 player" becomes mp3_player, and "page 2 results" becomes page_2_results. The one restriction is that a valid identifier cannot start with a digit in Python, Ruby, and most SQL dialects — "2024 revenue" converts to 2024_revenue, which is valid in SQL but illegal as a Python variable name. In that case, rewrite the phrase before converting: revenue_2024 or report_2024 both work cleanly.

Can I convert a whole list of column names at once?

Yes — paste one name per line and every line converts independently in your original order. A CSV header row, a table schema, a list of API field names — the whole batch comes back in about a second, ready to paste straight into a migration file or a spreadsheet. Nothing is deduplicated, sorted, or reordered, so row 12 stays row 12. For text that needs a deeper scrub before converting — double spaces, invisible characters — run it through Remove Extra Spaces first and the output will be clean on the first try.

Why do Python, SQL, and Ruby use snake_case while JavaScript uses camelCase?

Language heritage, mostly. Python and Ruby were designed with readability as an explicit goal, and their early communities settled on lowercase_with_underscores because it mirrors written language most naturally — words separated by spaces, all lowercase by default. SQL predates both and uses underscores because spaces are illegal in identifiers. JavaScript inherited its conventions from Java, which adopted camelCase from Sun's coding standards in the early 1990s. The split is baked in at language level, which is why the mapping step at an API boundary is so common — it is not a team preference problem, it is a language heritage problem.

Related Tools