1. Home
  2. Docs
  3. Github
  4. How to Automatically Create Releases with GitHub Actions

How to Automatically Create Releases with GitHub Actions

📌 Introduction

This tutorial will guide you through setting up a GitHub Actions workflow that automatically creates a release whenever you push a new version tag (for example, v1.0.0) to your repository.

By the end of this guide, you’ll have an automated release process that saves you time and ensures consistency across your project.

✅ Prerequisites

  • A GitHub repository with Actions enabled.
  • Permission to push tags and create releases.
  • Basic knowledge of Git commands.
  • GitHub Token Setup

🛠 Step 1: Create the Workflow File

Inside your project repository:

  1. Create the folder .github/workflows/ if it doesn’t exist.
  2. Add a new file called release.yml.

📝 Step 2: Add the Workflow Code

Paste the following into release.yml:

name: Create Release Archive

on:
  push:
    branches:
      - main
  release:
    types: [published]

jobs:
  build-and-upload-archive:
    runs-on: ubuntu-latest
    steps:
      # Step 1: Checkout code
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Step 2: Install Node dependencies
      - name: Install Node dependencies
        run: yarn install --frozen-lockfile

      # Step 3: Run build
      - name: Run yarn build
        run: yarn build

      # Step 4: Install composer dependencies for production
      - name: Composer install
        run: composer install --no-dev --optimize-autoloader

      # Step 5: Remove unnecessary files and folders
      - name: Clean repo for release
        run: |
          rm -rf .github src node_modules
          rm -f .gitignore LICENSE README.md readme.txt .cursorrc.json
          rm -f *.lock *.js *.xml *.json

      # Step 6: Generate manifest.json and extract $VERSION
      - name: Generate manifest.json
        id: generate_manifest
        run: |
          MAIN_FILE=$(grep -rl "Plugin Name:" --include="*.php" . | head -n 1)
          echo "Main plugin file: $MAIN_FILE"

          parse_field() {
            grep -i "$1:" "$MAIN_FILE" | head -n1 \
              | sed -E "s/^\s*\*\s*//" \
              | sed -E "s|$1:[[:space:]]*||i" \
              | xargs
          }

          PLUGIN_NAME="$(parse_field 'Plugin Name')"
          PLUGIN_URI="$(parse_field 'Plugin URI')"
          DESCRIPTION="$(parse_field 'Description')"
          AUTHOR="$(parse_field 'Author')"
          AUTHOR_URI="$(parse_field 'Author URI')"
          VERSION="$(parse_field 'Version')"
          REQUIRES_PHP="$(parse_field 'Requires PHP')"
          REQUIRES_WP="$(parse_field 'Requires WP')"
          LICENSE="$(parse_field 'License')"
          LICENSE_URI="$(parse_field 'License URI')"
          TEXT_DOMAIN="$(parse_field 'Text Domain')"
          DOMAIN_PATH="$(parse_field 'Domain Path')"
          TESTED_UP_TO="$(parse_field 'Tested up to')"

          echo "VERSION=$VERSION" >> $GITHUB_ENV

          cat > manifest.json <<EOF
          {
            "plugin_name": "$PLUGIN_NAME",
            "plugin_uri": "$PLUGIN_URI",
            "description": "$DESCRIPTION",
            "author": "$AUTHOR",
            "author_uri": "$AUTHOR_URI",
            "version": "$VERSION",
            "requires_php": "$REQUIRES_PHP",
            "requires_wp": "$REQUIRES_WP",
            "license": "$LICENSE",
            "license_uri": "$LICENSE_URI",
            "text_domain": "$TEXT_DOMAIN",
            "domain_path": "$DOMAIN_PATH",
            "tested_up_to": "$TESTED_UP_TO"
          }
          EOF

      # Step 7: Create custom zip archive
      - name: Create custom archive
        run: |
          REPO_NAME=$(echo "${{ github.repository }}" | cut -d'/' -f2)
          CUSTOM_ARCHIVE_NAME="${REPO_NAME}.zip"
          zip -r "$CUSTOM_ARCHIVE_NAME" . -x ".git/*" ".github/*"
          echo "CUSTOM_ARCHIVE_NAME=$CUSTOM_ARCHIVE_NAME" >> "$GITHUB_ENV"

      # Step 8: Get last commit message (push events only)
      - name: Get last commit message
        if: github.event_name == 'push'
        id: last_commit
        run: |
          LAST_COMMIT_MESSAGE="$(git log -1 --pretty=%B)"
          {
            echo "LAST_COMMIT_MESSAGE<<EOF"
            echo "$LAST_COMMIT_MESSAGE"
            echo "EOF"
          } >> $GITHUB_ENV

      # Step 9: Ensure tag exists on remote (push events only)
      - name: Ensure tag exists
        if: github.event_name == 'push'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          TAG=v${{ env.VERSION }}
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git tag "$TAG"
          git push https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git "$TAG"

      # Step 10: Create GitHub release (push events)
      - name: Create GitHub release (push)
        if: github.event_name == 'push'
        id: create_release_push
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.VERSION }}
          name: Version ${{ env.VERSION }}
          body: ${{ env.LAST_COMMIT_MESSAGE }}
          files: |
            ${{ env.CUSTOM_ARCHIVE_NAME }}
            manifest.json
          overwrite_files: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Step 11: Create GitHub release (release events)
      - name: Create GitHub release (release)
        if: github.event_name == 'release'
        id: create_release_event
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ github.event.release.tag_name }}
          name: Release ${{ github.event.release.tag_name }}
          body: ${{ github.event.release.body }}
          files: |
            ${{ env.CUSTOM_ARCHIVE_NAME }}
            manifest.json
          overwrite_files: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

⚙️ Step 3: How It Works

  • Trigger: Runs whenever you push a tag starting with v (e.g., v1.2.0).
  • Checkout: Fetches your repository code into the runner.
  • Release: Uses the softprops/action-gh-release action to create a GitHub Release with auto-generated notes.

🚀 Step 4: Using the Workflow

  1. Commit and push release.yml to your repository’s default branch.
  2. Create and push a new version tag: git tag v1.0.0 git push origin v1.0.0
  3. Check your repository’s Releases page — a new release will appear automatically.

🔧 Optional Customization

  • Trigger on branch pushes instead of tags: on: push: branches: - main
  • Manual trigger from GitHub UI: on: workflow_dispatch:
  • Attach build artifacts: Add build steps before the release action and upload compiled files or zips to the release.

🎯 Conclusion

With this workflow, you no longer need to manually create releases. Every time you push a version tag, a new release is automatically generated with notes.

This makes your release process faster, consistent, and less error-prone.