Annotated Tags
Annotated tags are the recommended way to mark releases and important milestones. Unlike lightweight tags, which are nothing more than a named pointer, an annotated tag is a full Git object stored in the object database. It carries a rich set of metadata: who created it, when, and a descriptive message. Annotated tags can also be cryptographically signed with GPG, giving consumers of your software a way to verify authenticity.
What an annotated tag stores
Tagger name — the
user.namefrom your Git config at the time the tag was created.Tagger email — the
user.emailfrom your Git config.Creation date — a full ISO timestamp of when the tag was made (separate from the commit date).
Tag message — a freeform description, just like a commit message. Can be multiline.
Object reference — the SHA of the commit (or tree or blob) the tag points to.
Optional GPG signature — a cryptographic signature that proves the tag was created by the holder of a specific private key.
Creating an annotated tag (inline message)
The most common way to create an annotated tag is with the -a flag and -m to supply the message inline, without opening an editor.
Create an annotated tag on the current commit
# Verify you are on the right commit first git log --oneline -3 # 7e4f1a2 (HEAD -> main) Final tweaks before release # 5c3d2b1 Add release notes to README # b1c2d3e Update version in package.json # Create the annotated tag git tag -a v1.0.0 -m "Release 1.0.0" # With a more detailed message: git tag -a v1.0.0 -m "Release 1.0.0 This is the first stable release of the project. - Complete authentication system - Dashboard with live charts - REST API v1 finalized - Supports Node.js 18+" # Confirm tag was created git tag # v1.0.0
Interactive creation (opens editor)
If you omit the -m flag, Git opens your configured text editor — exactly like git commit — so you can write a longer, formatted message. This is especially useful for detailed release notes.
Open editor to write the tag message
git tag -a v1.0.0 # (your $GIT_EDITOR / $EDITOR opens with a template) # The editor shows: # # Release 1.0.0 # # # Write a message for tag: # # v1.0.0 # # Lines starting with '#' will be ignored. # Save and close — the tag is created with your message.
Viewing annotated tag details with git show
The git show command is the most informative way to inspect an annotated tag. It prints the tag object metadata first, then the commit it points to, then the diff.
Full git show output for an annotated tag
git show v1.0.0 # tag v1.0.0 # Tagger: Alice Smith <alice@example.com> # Date: Thu Nov 16 14:30:00 2023 +0000 # # Release 1.0.0 # # This is the first stable release of the project. # - Complete authentication system # - Dashboard with live charts # - REST API v1 finalized # - Supports Node.js 18+ # # commit 7e4f1a2c3b4d5e6f7a8b9c0d1e2f3a4b (HEAD -> main, tag: v1.0.0) # Author: Alice Smith <alice@example.com> # Date: Thu Nov 16 13:55:00 2023 +0000 # # Final tweaks before release # # diff --git a/src/version.ts b/src/version.ts # index ...
Dereferencing the tag to get the commit SHA
# SHA of the tag object itself
git rev-parse v1.0.0
# a4b9c3e2f1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6
# SHA of the commit the tag points to
git rev-parse v1.0.0^{}
# 7e4f1a2c3b4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f
# These two values are different — that is what makes the
# tag "annotated" (it has its own object in the database).Pushing an annotated tag to a remote
Git does not push tags automatically when you run git push. You must push them explicitly. This is true for both lightweight and annotated tags.
Pushing a single annotated tag
# Push one specific tag git push origin v1.0.0 # Enumerating objects: 1, done. # Counting objects: 100% (1/1), done. # Writing objects: 100% (1/1), 175 bytes | 175.00 KiB/s, done. # Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 # To github.com:alice/myproject.git # * [new tag] v1.0.0 -> v1.0.0
Push all local tags at once
git push origin --tags # (pushes every tag that the remote does not already have) # Push only annotated tags (skip lightweight): git push origin --follow-tags # (safer: pushes annotated tags reachable from HEAD)
Why annotated tags are preferred for releases
Reason | Explanation |
|---|---|
Authorship | You know who created the tag and when — critical for audit trails in professional projects. |
Message | The tag message can describe what changed, acting as a concise changelog entry. |
Tool support | GitHub, GitLab, npm, and most CI/CD tools recognize annotated tags as "real" releases. |
|
|
GPG signing | Only annotated tags can be signed, enabling cryptographic release verification. |
Object permanence | An annotated tag is a first-class object; it cannot be confused with a branch or a commit. |
GPG signing an annotated tag
For high-security projects — especially open-source libraries with many consumers — you can add a GPG signature to an annotated tag. This lets anyone with your public key verify that the tag was really created by you and has not been tampered with.
Create a GPG-signed annotated tag
# Prerequisite: you have a GPG key configured gpg --list-secret-keys --keyid-format LONG # sec rsa4096/ABC123DEF456 2022-01-01 [SC] # Tell Git which key to use (one-time setup) git config --global user.signingkey ABC123DEF456 # Create a signed tag with -s instead of -a git tag -s v1.0.0 -m "Release 1.0.0 (signed)" # Verify the signature git tag -v v1.0.0 # object 7e4f1a2c... # type commit # tag v1.0.0 # tagger Alice <alice@example.com> 1700000000 +0000 # # Release 1.0.0 (signed) # gpg: Signature made Thu 16 Nov 2023 14:30:00 UTC # gpg: using RSA key ABC123DEF456 # gpg: Good signature from "Alice Smith <alice@example.com>" [ultimate]
Creating an annotated tag on a past commit
Tag a commit from history with a message
# Find the commit hash you want to tag git log --oneline --decorate # 7e4f1a2 (HEAD -> main) Post-release cleanup # 5c3d2b1 Final tweaks before v1.0.0 ← this one # b1c2d3e Update README # Create the annotated tag on that specific commit git tag -a v1.0.0 5c3d2b1 -m "Release 1.0.0 (applied retroactively)"
Common errors
Error: empty tag message
# If you open the editor and save without writing anything: # error: no tag message? # (tag creation is aborted) # Fix: always provide a non-empty message
Error: tag already exists
git tag -a v1.0.0 -m "..." # fatal: tag 'v1.0.0' already exists # Fix: delete the existing tag and recreate it git tag -d v1.0.0 git tag -a v1.0.0 -m "Release 1.0.0" # Or force-overwrite (dangerous if already pushed to remote): git tag -a -f v1.0.0 -m "Release 1.0.0"