Overview

A filename is a tiny piece of API. It is the thing people type, grep, and link to. The rules below pick one casing per ecosystem, ban domain-noun abbreviations, anchor dates in ISO, and resolve filename collisions. They follow the same logic as general-principles: a good name kills its own comment.

kebab-case for files unless the language says otherwise

Use lowercase, hyphen-separated filenames by default. They survive case-insensitive filesystems, URL slugs, and shell tab completion without quoting.

  • Markdown, HTML, config: naming-conventions.md, index.html, pre-commit-config.yaml.
  • Static assets: hero-image.png, favicon-32.png.
  • Top-level project files keep their canonical names: README.md, LICENSE, Dockerfile.

The exceptions are languages whose tooling expects a specific case. Honor the toolchain; do not fight it.

  • Python: snake_case.py. Module names map to import paths; hyphens are illegal in identifiers.
  • JavaScript / TypeScript files: camelCase.ts for modules, PascalCase.tsx for React components. Some teams use kebab-case.ts for non-component files; pick one and document it.
  • Rust: snake_case.rs. Crate names use kebab-case in Cargo.toml, snake_case as the import.
  • Go: lowercase.go or snake_case_test.go. No camelCase, no hyphens.
  • Swift: PascalCase.swift for type files (UserProfile.swift).

Name describes intent, not implementation

The filename should tell the reader what role the file plays, not how it does it.

  • invoice-parser.ts, not xml-utils.ts. Two months from now the implementation might be JSON; the role does not change.
  • auth-middleware.py, not decorator.py. “Decorator” describes how, not what.
  • daily-report.sql, not query-3.sql. The role survives a rewrite.

A filename that names the implementation is a refactor away from being a lie. Name the responsibility.

Never abbreviate domain nouns

The five-character savings cost a lookup every time a new reader hits the file. Spell it out.

  • customer.ts, not cust.ts.
  • subscription-renewal.py, not sub-ren.py.
  • pull-request-review.md, not pr-rvw.md.

The exceptions are abbreviations the field already settled on:

  • id, url, uri, http, db, api, dto, json, xml, csv.

If a reader has to ask “what does mgr stand for?” once, the name failed. Match the spelling to the domain language used in tickets and docs.

ISO dates in filenames

Dates in filenames are sortable when they use ISO 8601: YYYY-MM-DD.

posts/2026-05-14-quartz-release-notes.md
migrations/2026-03-01-add-user-index.sql
backups/db-2026-05-14T03-00-00Z.sql.gz

Avoid 05-14-2026, 14-may-2026, or today.sql. None of them sort. ISO times use T and either - or : (filesystems on Windows ban :); pick - and keep the format consistent. See folder-hierarchy for date-partitioned directories.

Resolve filename collisions with paths, not numbers

Two files named utils.ts in different folders are fine until someone greps. Add a path-distinct prefix or rename to something specific.

  • Prefer renaming: string-utils.ts and date-utils.ts instead of two utils.ts.
  • When a rename is impossible (framework convention), keep the names and rely on the folder path for disambiguation: pages/blog/index.tsx and pages/about/index.tsx.
  • Never resolve a collision with a numeric suffix (utils-2.ts). The number tells the reader nothing about which utils it holds.

In Obsidian and Quartz, slug collisions force folder-prefixed wikilinks ([[seo/technical]] vs [[frontend/technical]]). See obsidian.

Plural vs singular: pick by what the file holds

A file holds one thing or many. Match the noun number.

  • user.ts exports a User type; users.ts holds a collection or a CRUD module for users.
  • route.ts defines one route; routes.ts registers many.
  • React component files are singular by convention: UserCard.tsx, not UserCards.tsx, even if the component renders a list.

Folders follow the same rule: migrations/ (a folder of many migrations), config/ (a folder of one config split into pieces).

Test files mirror the source name

A test file’s name should make the source file obvious.

  • Python: test_invoice.py for invoice.py. Pytest discovers it automatically.
  • JS/TS: invoice.test.ts or invoice.spec.ts next to invoice.ts. Pick one suffix and stick with it.
  • Go: invoice_test.go next to invoice.go. The _test.go suffix is mandatory; Go’s toolchain uses it for collection.
  • Swift: InvoiceTests.swift for Invoice.swift.

A test file that does not mirror its source name will eventually drift away from it. See testing.