The Hyphen vs. Underscore Debate Has an Answer: Both

Adapted from Wes Jones’ The Ideal Way to Name Your Files, with adjustments for code/config contexts.

The single most useful insight is that hyphens and underscores aren’t interchangeable: most tools (search engines, IDEs, editors with “match whole word”) treat - as a word separator and _ as a word joiner. That mechanical difference is the whole game — which one you want depends on whether you want the parts of the name to be individually findable or treated as one atomic token.

That gives us two regimes.

Use hyphens for: assets, documents, URLs, public-facing files

This is Wes’s domain — photo/video assets, marketing files, anything that ends up indexed by a search engine or sorted in a finder window. Here you want the parts of the name to be discoverable on their own, because you’ll search for “bmw” or “studio” or “fall-campaign” without knowing the rest.

The convention: YYMMDD-BRAND-PROJECT-HARDWARE-NNN-vN.ext

  • Descending dates first (240614, not 06-14-2024) so chronological sort is free. Two-digit year, zero-padded month and day, all the same width.
  • Brand code — short, 2–4 chars (BMW, TNF, YETI).
  • Project code — short and descriptive (studio, fall-campaign). Keep the whole filename under ~25 chars where you can.
  • Hardware code — only when it matters (multi-camera shoots, separate audio). R51, R52, F3.
  • Asset number — zero-padded to the max width you’ll need (001, not 1, if you’ll have hundreds). Assigned at import time and never re-numbered on partial export.
  • Versionv1, v2, appended after the asset number so the rest of the system doesn’t shift.
  • No spaces, no special characters (" / \ [ ] : ; | = , < ? > & $ # ! ' { } ( ) *). Letters, numbers, and hyphens only.

Use underscores for: code, scripts, configs, anything searched by exact identifier

This is the case my Postgres-upgrade work surfaced. In a codebase you frequently search for an exact filename — upgrade.sh, config.py, auth_handler.go — and you want that search to land only on real matches.

With hyphens, pg-upgrade.sh matches a whole-word search for upgrade.sh (because - is a separator, so upgrade and sh are seen as standalone words). With underscores, pg_upgrade.sh is one token and the search for upgrade.sh correctly ignores it.

Screenshot of VS Code search showing whole-word search for `upgrade.sh` matching inside `pg-upgrade.sh`, `ts-upgrade.sh`, `pg-version-upgrade.sh`, etc.

So in code-adjacent contexts:

  • Underscores between words in filenames: pg_upgrade.sh, test_upgrade_chain.sh, pg_version_upgrade.sh. Each filename is one atomic identifier.
  • Hyphens only for genuine separators between distinct conceptual fields, the same role they play in the asset convention above. e.g. pg_upgrade-v2.sh or 2024-01-config_backup.json — hyphen separates fields, underscore joins words within a field.
  • Same restrictions otherwise — no spaces, no special characters, lowercase.
  • Match the language’s conventions where they exist. Python modules want snake_case.py. Go files want lower_snake.go or lowercase.go. JavaScript/TypeScript is mixed but kebab-case.ts is common — and in JS land that’s usually fine because you rarely whole-word-search bare filenames the way you do in a polyglot shell-script repo.

What’s shared across both

The underlying discipline is the same regardless of regime:

  • Descending dates (YYMMDD or YYYY-MM-DD) when dates are part of the name. ISO-ish ordering means filesystem sort = chronological sort, for free.
  • Zero-pad numbers to a consistent width.
  • Be brief. Every part of the filename contributes context — you don’t need to encode everything in every field.
  • Be consistent. The convention only pays off if it’s applied uniformly; one rogue Upgrade Script (final FINAL).sh undoes the whole system for that directory.

TL;DR

Context Separator within filename Why
Photo/video/document assets, URLs, anything you’ll fuzzy-search - (hyphen) Hyphens are word separators — parts of the name are individually findable
Code, scripts, configs, anything you’ll exact-search _ (underscore) Underscores are word joiners — the filename is one atomic identifier and upgrade.sh won’t false-match in pg_upgrade.sh
Field separators in either regime - (hyphen) Reserve hyphens for boundaries between distinct conceptual fields (date / brand / project / version)
Share: X (Twitter) Facebook LinkedIn