Git Tags and Signed Releases for WordPress Plugins

Semantic versioning and signed Git tags are the professional standard for releasing WordPress plugins. A signed tag proves the release came from a trusted key, while a structured version number tells users and automated update systems exactly what kind of change to expect.

Problem: A WordPress plugin has a release process that relies on manually copying files and writing release notes — version numbers drift between readme.txt, the main PHP file header, and the changelog, and it is easy to forget a step.

Solution: Use annotated Git tags as the single source of truth for releases — create a tag with git tag -a v1.2.3 -m "Release notes", push it with git push origin v1.2.3, and trigger a GitHub Actions workflow on on: push: tags: ['v*'] to build, zip, and publish the plugin automatically.

The examples below create annotated and GPG-signed tags, automate version bumping in plugin headers, push releases to GitHub, and configure a GitHub Action that creates a release whenever a tag is pushed.

# Semantic versioning: MAJOR.MINOR.PATCH
# MAJOR — breaking change (incompatible API)
# MINOR — new feature (backward-compatible)
# PATCH — bug fix (backward-compatible)

# Create a lightweight tag (no message — avoid for releases)
git tag v1.2.3

# Create an annotated tag (recommended — stores tagger, date, message)
git tag -a v1.2.3 -m "Release v1.2.3: fix cart nonce expiry on checkout"

# Create a GPG-signed tag (requires a GPG key configured in git)
git config user.signingkey YOUR_GPG_KEY_ID
git tag -s v1.2.3 -m "Release v1.2.3: fix cart nonce expiry"

# Verify a signed tag
git tag -v v1.2.3

# List all tags sorted by version
git tag --sort=-version:refname | head -10

# Push a single tag to origin
git push origin v1.2.3

# Push all local tags at once
git push origin --tags

Automate version bumping in plugin headers and create a GitHub release via Actions:

#!/usr/bin/env bash
# release.sh — bump version, tag, and push
set -euo pipefail

NEW_VERSION="${1:?Usage: $0 }"   # e.g., 1.2.3
PLUGIN_FILE="my-plugin.php"
README="readme.txt"

# Update version in plugin header
sed -i "s/^ \* Version:.*/ * Version:           ${NEW_VERSION}/" "${PLUGIN_FILE}"
sed -i "s/^define( 'MY_PLUGIN_VERSION'.*/define( 'MY_PLUGIN_VERSION', '${NEW_VERSION}' );/" "${PLUGIN_FILE}"

# Update readme.txt Stable tag
sed -i "s/^Stable tag:.*/Stable tag: ${NEW_VERSION}/" "${README}"

git add "${PLUGIN_FILE}" "${README}"
git commit -m "chore: bump version to ${NEW_VERSION}"
git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}"
git push origin HEAD --tags
echo "Released v${NEW_VERSION}"

# .github/workflows/release.yml
# Creates a GitHub Release with a zip file when a v* tag is pushed
name: Create Release
on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build plugin zip
        run: |
          mkdir -p dist
          rsync -r --exclude='.git' --exclude='node_modules'                 --exclude='tests' --exclude='.github'                 ./ dist/my-plugin/
          cd dist && zip -r ../my-plugin-${{ github.ref_name }}.zip my-plugin/
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: my-plugin-${{ github.ref_name }}.zip
          generate_release_notes: true  # auto-generates notes from commits

NOTE: Tag before merging to main — create the tag on the release commit, not on a work-in-progress commit. Use git describe --tags --abbrev=0 in your build scripts to read the current version without parsing the plugin header file.

Leave Comment

Your email address will not be published. Required fields are marked *