Summit Themes
Blog

Git Tags: A Complete Guide

Every time you ship a version of something, you want a permanent marker in your repository's history — something that says "this exact commit is v2.1.0." That's what a git tag is. Unlike a branch, a tag never moves. It points to one commit and stays there, making it the right tool for marking releases, milestones, and anything else that needs a stable, human-readable label.

This guide covers everything you need: the two tag types, all the commands for creating, listing, deleting, and pushing tags, how to check one out safely, semantic versioning conventions, and how tags connect to GitHub releases.

Lightweight vs Annotated Tags

Git has two fundamentally different kinds of tags, and the difference matters more than most people realize.

Lightweight tags

A lightweight tag is nothing more than a named pointer to a commit — essentially a file in .git/refs/tags/ containing a commit SHA. No metadata, no message, no author information.

git tag v1.4

That's the whole command. Lightweight tags are useful for local bookmarks or temporary labels you don't intend to share. When you run git show v1.4 on a lightweight tag, you see only the commit itself — no tag metadata.

Annotated tags

An annotated tag is a full object stored in the Git database. It contains the tagger's name, email, and timestamp; a tagging message; and optionally a GPG signature. This is almost always what you want for anything shared with a team or published as a release.

git tag -a v1.4.0 -m "Release v1.4.0 — adds dark mode support"

Run git show v1.4.0 and you'll see the tag object itself (who tagged it, when, and the message) followed by the commit it points to.

The practical rule: use annotated tags for releases and anything going to a remote. Use lightweight tags for personal, temporary, or local-only markers.

Signed tags

A third variant — signed tags — adds a GPG or SSH signature to an annotated tag. The flag is -s instead of -a. Signed tags are worth knowing about for security-sensitive projects but aren't necessary for most workflows.

git tag -s v1.4.0 -m "Signed release v1.4.0"

Creating Tags

Tag the current HEAD

# Annotated (recommended for releases)
git tag -a v1.0.0 -m "Initial public release"

# Lightweight
git tag v1.0.0

Tag a previous commit

You can tag any commit by providing its hash. You only need enough of the SHA to be unambiguous — usually seven characters:

git tag -a v0.9.0 -m "Beta release" 9fceb02

Listing Tags

# All tags, alphabetical
git tag

# Filter by pattern (the -l flag is required when using wildcards)
git tag -l "v1.8.*"

# Sort by version number, newest first
git tag -l --sort=-version:refname

# Sort by creation date, newest first
git tag -l --sort=-creatordate

# Show the first line of each tag's annotation (-n1) alongside the name
git tag -n1

Deleting Tags

Deleting a local tag and deleting a remote tag are two separate operations. You need to do both if the tag has already been pushed.

# Delete from your local repository
git tag -d v1.0.0

# Delete from the remote (both forms do the same thing)
git push origin --delete v1.0.0
git push origin :refs/tags/v1.0.0

Avoid deleting a tag that others have already pulled. If a published tag pointed to the wrong commit, create a corrected tag with a new name (e.g., v1.0.1) rather than force-replacing the old one. Replacing a published tag rewrites history for everyone who has it.

Pushing Tags to a Remote

git push does not transfer tags by default. You have to push them explicitly.

# Push a single tag
git push origin v1.0.0

# Push all local tags at once (both lightweight and annotated)
git push origin --tags

# Push only annotated tags (generally the safer default)
git push origin --follow-tags

--follow-tags is the recommended option for release workflows: it pushes annotated tags reachable from the commits you're pushing, without flooding the remote with every lightweight tag you've created locally.

Checking Out a Tag

Checking out a tag puts you in a detached HEAD state. HEAD points directly to a commit rather than to a branch, which means any commits you make here will not belong to any branch and can be lost when you switch away.

# Detached HEAD — fine for reading, risky for making changes
git checkout v2.0.0

If you need to make changes based on a tagged version (say, to backport a fix), create a branch from the tag instead:

git checkout -b hotfix/v2.0.1 v2.0.0
# or with the newer syntax
git switch -c hotfix/v2.0.1 v2.0.0

You now have a real branch starting at that commit, and any new commits are tracked normally.

Semantic Versioning with Git Tags

The most widely used tagging convention for software projects is Semantic Versioning (SemVer): MAJOR.MINOR.PATCH.

  • MAJOR — breaking changes that are incompatible with the previous public API.
  • MINOR — new features that are backwards-compatible.
  • PATCH — backwards-compatible bug fixes.

Tag names conventionally include a v prefix:

# First public release
git tag -a v1.0.0 -m "First stable release"

# Backwards-compatible new feature
git tag -a v1.1.0 -m "Add dark mode"

# Bug fix
git tag -a v1.1.1 -m "Fix contrast ratio in dark mode"

# Breaking change
git tag -a v2.0.0 -m "Rewrite configuration API"

Pre-release versions use a hyphen separator: v2.0.0-alpha.1, v2.0.0-rc.1. Build metadata goes after a + sign: v1.0.0+20260622, though build metadata is rarely used in tag names in practice.

A full release workflow

# 1. Finish your work on main (or merge your PR)
git checkout main
git pull origin main

# 2. Create the annotated tag
git tag -a v1.2.0 -m "v1.2.0 — service area filtering, performance pass"

# 3. Push the tag to the remote
git push origin v1.2.0

GitHub Releases

When you push a tag to GitHub, it appears in the repository's Tags tab immediately. You can turn any tag into a formal Release — which adds a title, release notes, and downloadable source archives — either through the GitHub web UI or via the gh CLI.

# Create a release from an existing tag, auto-generating notes from merged PRs
gh release create v1.2.0 --generate-notes

# Create a release using the annotation message from the tag itself
gh release create v1.2.0 --notes-from-tag

# Create a release with custom notes and attach build artifacts
gh release create v1.2.0 \
  --notes "See CHANGELOG.md for details." \
  ./dist/theme-v1.2.0.zip

The --notes-from-tag flag is particularly useful: if you write a meaningful message in your annotated tag, GitHub will pull that directly into the release body with no extra work.

Draft and pre-release flags

# Create as a draft (not visible publicly until published)
gh release create v2.0.0-rc.1 --draft --generate-notes

# Mark as a pre-release (visible but flagged as not production-ready)
gh release create v2.0.0-rc.1 --prerelease --generate-notes

Quick Reference

  • git tag -a v1.0.0 -m "..." — create an annotated tag
  • git tag v1.0.0 — create a lightweight tag
  • git tag -l "v1.*" — list tags matching a pattern
  • git tag -d v1.0.0 — delete a local tag
  • git push origin --delete v1.0.0 — delete a remote tag
  • git push origin v1.0.0 — push a single tag
  • git push origin --follow-tags — push commits plus annotated tags
  • git checkout -b fix/v1.0.1 v1.0.0 — branch from a tag
  • git show v1.0.0 — inspect a tag
  • gh release create v1.0.0 --generate-notes — publish a GitHub release

Tags are a small part of the git surface area but a load-bearing one. Getting into the habit of creating annotated tags with meaningful messages — and pushing them explicitly alongside your commits — gives you a clean, permanent history of every version you've ever shipped, and keeps your release notes mostly written for you.