git: tag basics



~11 min read


2010 words

I was recently trying to publish a new version our product when I ran into an issue: the tag already existed and so my command exited with a failure.

This got me wondering what exactly are Git Tags, why do we use them, and how might I get better at using them. The purpose of this page is to answer those questions (and be slightly more concise than the excellent primary source: the Git Handbook’s section on tagging).

The second (and more intuitive) way to delete a remote tag is with:

$ git push origin --delete <tagname>

What are tags, how are they used, and why do we care?

Let’s start by addressing some fundamental questions: What are tags? How are they (typically) used? Why might we care?

From the Handbook:

Like most VCSs, Git has the ability to tag specific points in a repository’s history as being important. Typically, people use this functionality to mark release points (v1.0, v2.0 and so on). In this section, you’ll learn how to list existing tags, how to create and delete tags, and what the different types of tags are.

Tags then are landmarks. They make identifying points in a product’s history straightforward. The most common use is for releases - particularly useful for rollbacks.A lot of CI/CD tooling can be configured to use tags.

How To Create A Tag

Now that we know why we might want to create a tag, the natural next step is to actually do that.


The easiest way to create a tag is with the git command from within a git managed directory:

% git tag vx.yy.zz

This will create a tag for the most recent commit.

% g l
* 0675a9c1 2021-08-30 | Add feature x (HEAD -> main, tag: vx.yy.zz, origin/main) [Stephen]

CLI commands with NPM and Yarn

| major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id

Yarn has a slightly different API using flags instead of arguments to direct the new version.

% yarn version --help

  Usage: yarn [command] [flags]

  Update the version of your package via the command line.


    -v, --version                       output the version number
    --no-default-rc                     prevent Yarn from automatically detecting yarnrc and npmrc files
    --use-yarnrc <path>                 specifies a yarnrc file that Yarn should use (.yarnrc only, not .npmrc) (default: )
    --verbose                           output verbose messages on internal operations
    --offline                           trigger an error if any required dependencies are not available in local cache
    --prefer-offline                    use network only if dependencies are not available in local cache
    --enable-pnp, --pnp                 enable the Plug'n'Play installation
    --disable-pnp                       disable the Plug'n'Play installation
    --json                              format Yarn log messages as lines of JSON (see
    --ignore-scripts                    don't run lifecycle scripts
    --har                               save HAR output of network traffic
    --ignore-platform                   ignore platform checks
    --ignore-engines                    ignore engines check
    --ignore-optional                   ignore optional dependencies
    --force                             install and build packages even if they were built before, overwrite lockfile
    --skip-integrity-check              run install without checking if node_modules is installed
    --check-files                       install will verify file tree of packages for consistency
    --no-bin-links                      don't generate bin links when setting up packages
    --flat                              only allow one version of a package
    --prod, --production [prod]
    --no-lockfile                       don't read or generate a lockfile
    --pure-lockfile                     don't generate a lockfile
    --frozen-lockfile                   don't generate a lockfile and fail if an update is needed
    --update-checksums                  update package checksums from current repository
    --link-duplicates                   create hardlinks to the repeated modules in node_modules
    --link-folder <path>                specify a custom folder to store global links
    --global-folder <path>              specify a custom folder to store global packages
    --modules-folder <path>             rather than installing modules into the node_modules folder relative to the cwd, output them here
    --preferred-cache-folder <path>     specify a custom folder to store the yarn cache if possible
    --cache-folder <path>               specify a custom folder that must be used to store the yarn cache
    --mutex <type>[:specifier]          use a mutex to ensure only one yarn instance is executing
    --emoji [bool]                      enable emoji in output (default: true)
    -s, --silent                        skip Yarn console logs, other types of logs (script output) will be printed
    --cwd <cwd>                         working directory to use (default: /Users/stephen.weiss/code/digital-garden)
    --proxy <host>
    --https-proxy <host>
    --registry <url>                    override configuration registry
    --no-progress                       disable progress bar
    --network-concurrency <number>      maximum number of concurrent network requests
    --network-timeout <milliseconds>    TCP timeout for network requests
    --non-interactive                   do not show interactive prompts
    --scripts-prepend-node-path [bool]  prepend the node executable dir to the PATH in scripts
    --no-node-version-check             do not warn when using a potentially unsupported Node version
    --focus                             Focus on a single workspace by installing remote copies of its sibling workspaces.
    --otp <otpcode>                     one-time password for two factor authentication
    --new-version [version]             new version
    --major                             auto-increment major version number
    --minor                             auto-increment minor version number
    --patch                             auto-increment patch version number
    --premajor                          auto-increment premajor version number
    --preminor                          auto-increment preminor version number
    --prepatch                          auto-increment prepatch version number
    --prerelease                        auto-increment prerelease version number
    --preid [preid]                     add a custom identifier to the prerelease
    --message [message]                 message
    --no-git-tag-version                no git tag version
    --no-commit-hooks                   bypass git hooks when committing new version
    -h, --help                          output usage information
  Visit for documentation about this command.

How To Send Tags To A Remote

By default, tags are not pushed when you push to a remote repository.

To ensure that tags are published to a remote we need to explicitly push them:

% git push origin --tags

Viewing Existing Tags

To see all of the existing tags we can use:

% git tag --list

Deleting Tags

To delete a tag (locally):

% git tag --delete v0.3.0
Deleted tag 'v0.3.0'

If the tags have been pushed to a remote repository already:

% git push origin --delete v0.3.0


GIT-TAG(1)                                                   Git Manual                                                  GIT-TAG(1)

       git-tag - Create, list, delete or verify a tag object signed with GPG

       git tag [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>] [-e]
               <tagname> [<commit> | <object>]
       git tag -d <tagname>...
       git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
               [--points-at <object>] [--column[=<options>] | --no-column]
               [--create-reflog] [--sort=<key>] [--format=<format>]
               [--[no-]merged [<commit>]] [<pattern>...]
       git tag -v [--format=<format>] <tagname>...

       Add a tag reference in refs/tags/, unless -d/-l/-v is given to delete, list or verify tags.

       Unless -f is given, the named tag must not yet exist.

       If one of -a, -s, or -u <keyid> is passed, the command creates a tag object, and requires a tag message. Unless -m <msg> or
       -F <file> is given, an editor is started for the user to type in the tag message.

       If -m <msg> or -F <file> is given and -a, -s, and -u <keyid> are absent, -a is implied.

       Otherwise, a tag reference that points directly at the given object (i.e., a lightweight tag) is created.

       A GnuPG signed tag object will be created when -s or -u <keyid> is used. When -u <keyid> is not used, the committer identity
       for the current user is used to find the GnuPG key for signing. The configuration variable gpg.program is used to specify
       custom GnuPG binary.

       Tag objects (created with -a, -s, or -u) are called "annotated" tags; they contain a creation date, the tagger name and
       e-mail, a tagging message, and an optional GnuPG signature. Whereas a "lightweight" tag is simply a name for an object
       (usually a commit object).

       Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels. For this
       reason, some git commands for naming objects (like git describe) will ignore lightweight tags by default.

       -a, --annotate
           Make an unsigned, annotated tag object

       -s, --sign
           Make a GPG-signed tag, using the default e-mail address's key. The default behavior of tag GPG-signing is controlled by
           tag.gpgSign configuration variable if it exists, or disabled oder otherwise. See git-config(1).

           Override tag.gpgSign configuration variable that is set to force each and every tag to be signed.

       -u <keyid>, --local-user=<keyid>
           Make a GPG-signed tag, using the given key.

       -f, --force
           Replace an existing tag with the given name (instead of failing)

       -d, --delete
           Delete existing tags with the given names.

       -v, --verify
           Verify the GPG signature of the given tag names.

           <num> specifies how many lines from the annotation, if any, are printed when using -l. Implies --list.

           The default is not to print any annotation lines. If no number is given to -n, only the first line is printed. If the
           tag is not annotated, the commit message is displayed instead.

       -l, --list
           List tags. With optional <pattern>..., e.g.  git tag --list 'v-*', list only the tags that match the pattern(s).

           Running "git tag" without arguments also lists all tags. The pattern is a shell wildcard (i.e., matched using
           fnmatch(3)). Multiple patterns may be given; if any of them matches, the tag is shown.

           This option is implicitly supplied if any other list-like option such as --contains is provided. See the documentation
           for each of those options for details.

           Sort based on the key given. Prefix - to sort in descending order of the value. You may use the --sort=<key> option
           multiple times, in which case the last key becomes the primary key. Also supports "version:refname" or "v:refname" (tag
           names are treated as versions). The "version:refname" sort order can also be affected by the "versionsort.suffix"
           configuration variable. The keys supported are the same as those in git for-each-ref. Sort order defaults to the value
           configured for the tag.sort variable if it exists, or lexicographic order otherwise. See git-config(1).

           Respect any colors specified in the --format option. The <when> field must be one of always, never, or auto (if <when>
           is absent, behave as if always was given).

       -i, --ignore-case
           Sorting and filtering tags are case insensitive.

       --column[=<options>], --no-column
           Display tag listing in columns. See configuration variable column.tag for option syntax.--column and --no-column without
           options are equivalent to always and never respectively.

           This option is only applicable when listing tags without annotation lines.

       --contains [<commit>]
           Only list tags which contain the specified commit (HEAD if not specified). Implies --list.

       --no-contains [<commit>]
           Only list tags which don't contain the specified commit (HEAD if not specified). Implies --list.

       --merged [<commit>]
           Only list tags whose commits are reachable from the specified commit (HEAD if not specified), incompatible with

       --no-merged [<commit>]
           Only list tags whose commits are not reachable from the specified commit (HEAD if not specified), incompatible with

       --points-at <object>
           Only list tags of the given object (HEAD if not specified). Implies --list.

       -m <msg>, --message=<msg>
           Use the given tag message (instead of prompting). If multiple -m options are given, their values are concatenated as
           separate paragraphs. Implies -a if none of -a, -s, or -u <keyid> is given.

       -F <file>, --file=<file>
           Take the tag message from the given file. Use - to read the message from the standard input. Implies -a if none of -a,
           -s, or -u <keyid> is given.

       -e, --edit
           The message taken from file with -F and command line with -m are usually used as the tag message unmodified. This option
           lets you further edit the message taken from these sources.

           This option sets how the tag message is cleaned up. The <mode> can be one of verbatim, whitespace and strip. The strip
           mode is default. The verbatim mode does not change message at all, whitespace removes just leading/trailing whitespace
           lines and strip removes both whitespace and commentary.

           Create a reflog for the tag. To globally enable reflogs for tags, see core.logAllRefUpdates in git-config(1). The
           negated form --no-create-reflog only overrides an earlier --create-reflog, but currently does not negate the setting of

           A string that interpolates %(fieldname) from a tag ref being shown and the object it points at. The format is the same
           as that of git-for-each-ref(1). When unspecified, defaults to %(refname:strip=2).

           The name of the tag to create, delete, or describe. The new tag name must pass all checks defined by git-check-ref-
           format(1). Some of these checks may restrict the characters allowed in a tag name.

       <commit>, <object>
           The object that the new tag will refer to, usually a commit. Defaults to HEAD.

Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!