Naming conventions for feature branches Jump to heading
Branch names look like a matter of taste until automation starts reading them. CI decides what to deploy by branch prefix. Issue trackers link branches by key. Cleanup jobs skip anything starting with release/. Merge request templates fill in the issue from the branch. When names are inconsistent — johns-fix, Feature/PAY_812, new-export-stuff-v2 — every one of those tools needs exceptions, and some simply fail. A convention that encodes the type of work, the issue it belongs to and a short description, in a fixed shape, makes branches readable for people and parseable for scripts. This page sets one up, enforces it at the right points and puts it to use, within feature branch isolation.
When to use this approach Jump to heading
- Branch names in your repositories have no consistent shape.
- CI, deploy or cleanup automation keys off branch names and keeps needing special cases.
- You want pull requests and commits linked to issues without manual effort, as in requiring linked issues on pull requests.
- Long-lived branches — releases, environments — need names that are never confused with feature work.
Step 1 — Pick a shape: type, key, slug Jump to heading
A practical convention has three parts: a type prefix that says what kind of work it is, the issue key, and a short lower-case slug. Long-lived branches use separate, reserved prefixes.
feat/PAY-812-export-scheduling
fix/PAY-907-credit-note-rounding
chore/OPS-221-upgrade-node-20
docs/DOC-44-signing-guide
release/2.4 # reserved: release branches
hotfix/2.4.1 # reserved: urgent fixes on a release
env/staging # reserved: deployment branches Keep types few — four to six — and aligned with your commit message types if you use Conventional Commits, so the branch and its commits tell the same story.
Step 2 — Write the rule as one regular expression Jump to heading
A single pattern, kept in one place, is what every enforcement point reads. Make it strict enough to be useful and simple enough to explain in one sentence.
# .git-branch-pattern — the single source of the naming rule
^((feat|fix|chore|docs|refactor|test)/[A-Z][A-Z0-9]+-[0-9]+-[a-z0-9]+(-[a-z0-9]+)*|release/[0-9]+\.[0-9]+|hotfix/[0-9]+\.[0-9]+\.[0-9]+|env/[a-z]+|main)$ # Verification: test the pattern against real and invented names
pattern=$(cat .git-branch-pattern)
for b in feat/PAY-812-export-scheduling fix/pay-1-x johns-fix release/2.4; do
printf '%s\t' "$b"; printf '%s\n' "$b" | grep -Eq "$pattern" && echo ok || echo REJECT
done Step 3 — Enforce early on the client, finally on the server Jump to heading
Check the name when someone pushes a new branch, with a clear message showing the expected shape. A pre-push hook catches it before the push; the server rule catches pushes that skip hooks.
# pre-push: reject new remote branches whose names break the convention
pattern=$(cat "$(git rev-parse --show-toplevel)/.git-branch-pattern")
zero=$(git hash-object --stdin </dev/null | tr '0-9a-f' '0')
while read -r lref loid rref roid; do
case "$rref" in refs/heads/*) ;; *) continue ;; esac
[ "$roid" = "$zero" ] || continue # only check new branches
name=${rref#refs/heads/}
printf '%s\n' "$name" | grep -Eq "$pattern" || {
echo "Branch name '$name' does not match the convention, e.g. feat/PAY-812-export-scheduling"; exit 1; }
done On the server, use a ruleset restricting branch names, GitLab’s branch name push rule, or an update hook, as covered in writing an update hook for per-branch policy.
Step 4 — Make the right name the easy name Jump to heading
Most bad names come from typing. A small helper that builds the name from a type, a key and a title removes the friction.
# git-new <type> <KEY-123> <title words…>
git config --global alias.new '!f() {
t=$1 k=$2; shift 2
slug=$(printf "%s" "$*" | tr "[:upper:]" "[:lower:]" | tr -cs "a-z0-9" "-" | sed "s/^-//; s/-$//" | cut -c1-40)
git switch -c "$t/$k-$slug"; }; f'
git new feat PAY-812 Export scheduling per account
# Switched to a new branch 'feat/PAY-812-export-scheduling-per-account' Many trackers and forges can also create a branch from an issue with the key already in the name; encourage that path.
Step 5 — Use the structure in automation Jump to heading
With predictable names, automation becomes simple pattern matching.
# Deploy previews only for feature and fix branches
on:
push:
branches: ["feat/**", "fix/**"]
# Label pull requests by branch type
- run: gh pr edit "$PR" --add-label "type:${BRANCH%%/*}"
env: { BRANCH: "${{ github.head_ref }}", PR: "${{ github.event.number }}", GH_TOKEN: "${{ github.token }}" } # Prefill the issue key in commit messages from the branch name
key=$(git symbolic-ref --short HEAD | sed -n 's#^[a-z]*/\([A-Z][A-Z0-9]*-[0-9]*\)-.*#\1#p') The commit-message side of this is covered in generating a commit message template per branch.
Step 6 — Document the convention where people create branches Jump to heading
The rule only helps if people know it at the moment they create a branch. Put a one-line summary and an example in the contributing guide, in the hook’s error message and in the pull request template, so the convention is visible at every step where a name is chosen.
<!-- CONTRIBUTING.md -->
Branch names: `type/KEY-123-short-slug`, e.g. `feat/PAY-812-export-scheduling`.
Types: feat, fix, chore, docs, refactor, test. Create one with `git new feat PAY-812 Export scheduling`. Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Should usernames be part of branch names? Jump to heading
Rarely. They duplicate information the forge already records, and branches often change hands. Use them only for personal scratch namespaces, such as u/alice/…, and exclude those from CI and deploy rules.
What about existing branches that break the convention? Jump to heading
Leave them; only new branches are checked. They will be merged or deleted in time. Renaming active branches disrupts open pull requests for little gain.
Are slashes in branch names a problem? Jump to heading
They work everywhere modern, and they let tools group branches as if in folders. The one constraint is that feat and feat/x cannot both exist, because Git stores refs as paths — another reason to keep type prefixes fixed.
Related Jump to heading
- Feature Branch Isolation — the parent topic.
- Deleting Merged Branches Automatically — cleanup that relies on reserved prefixes.
- Limiting Feature Branch Lifetime — reporting on branches by type and age.
- Enforcing Issue Keys in Commit Messages — the commit-side link to the tracker.