How to Bulk Update GitHub Repository Topics (Tags) Using cURL and Bash Scripts

Automate GitHub topic updates! This tutorial shows how to efficiently bulk-update repository topics using cURL and Bash scripts, saving time and streamlining your workflow.

GitHub repository topics (tags) are crucial for discoverability. They help developers find your projects when searching for specific technologies, frameworks, or use cases. However, GitHub's web interface only allows updating topics one repository at a time—which becomes tedious when managing multiple projects.

This guide shows you how to automate topic updates across multiple repositories using the GitHub API, cURL, and a simple Bash script. By the end, you'll be able to bulk-update topics in seconds.


Why Automate GitHub Topics?

Manual Updates (Slow):

  • Go to each repo → Settings → Topics → Paste → Save
  • 2-3 minutes per repository
  • 5 repos = 15 minutes
  • Error-prone (easy to miss repos)

Automated Updates (Fast):

  • Write one script
  • Run once
  • 5 repos = 30 seconds
  • Consistent topics across all repos
  • Scriptable (repeat anytime)

Prerequisites

You'll need:

  1. GitHub Account (with repositories you own or manage)
  2. Terminal/Command Line (Mac, Linux, or Windows WSL)
  3. Personal Access Token (GitHub)
  4. Basic Bash knowledge (just copy-paste!)

All tools are free and built-in to most systems.


Step 1: Generate a GitHub Personal Access Token

Why You Need a Token

GitHub's API requires authentication. A Personal Access Token acts as a secure password for API requests.

How to Create One

  1. Go to GitHub Settings:

  2. Click "Generate new token (classic)"

  3. Fill in the form:

    • Note: Name your token (e.g., github-topics-automation)
    • Expiration: 90 days (or your preference)
    • Select scopes: Check repo (full control of private repositories)
  4. Click "Generate token"

  5. Copy your token (starts with ghp_)

    • ⚠️ Important: GitHub only shows it once. Copy and save it somewhere safe (or regenerate if lost).
    • Never commit this token to Git!
    • Treat it like a password.

Security Note

  • Don't share your token publicly
  • Don't hardcode it in Git repositories
  • Use environment variables or stdin (like the script below)
  • Rotate tokens periodically

Step 2: Understand the GitHub Topics API

The Endpoint

PUT https://api.github.com/repos/{OWNER}/{REPO}/topics

Parameters:

  • {OWNER}: GitHub username or organization (e.g., octocat, facebook, OnDemandWorld)
  • {REPO}: Repository name (e.g., Hello-World, react, erc1400-contracts)

Required Headers

Authorization: token YOUR_GITHUB_TOKEN
Accept: application/vnd.github.mercy-preview+json
Content-Type: application/json

Request Body Format

{
"names": [
  "topic1",
  "topic2",
  "topic3"
  ]
}

Important: Use lowercase for topics (GitHub enforces this). Topics are limited to 50 per repository.


Step 3: Your First Manual cURL Command

Example Setup

Let's say you want to update topics for a repository:

  • Owner: github-user (your GitHub username)
  • Repository: my-awesome-project
  • Topics: javascript, web-development, react, open-source
  • Token: ghp_abc123xyz... (your Personal Access Token)

The cURL Command

Copy and paste this (replace the placeholder values):

curl -X PUT \
  -H "Authorization: token ghp_abc123xyz..." \
  -H "Accept: application/vnd.github.mercy-preview+json" \
  -H "Content-Type: application/json" \
  -d '{"names":["javascript","web-development","react","open-source"]}' \
  https://api.github.com/repos/github-user/my-awesome-project/topics

Breaking It Down

curl -X PUT                                    # HTTP method: PUT (update)
  -H "Authorization: token YOUR_TOKEN"        # Authentication header
  -H "Accept: application/vnd.github.mercy-preview+json"  # API version
  -H "Content-Type: application/json"         # Data format
  -d '{"names":["topic1","topic2"]}'          # Topics to add (JSON)
  https://api.github.com/repos/OWNER/REPO/topics  # GitHub API endpoint

Testing the Command

Replace these 3 things:

  1. ghp_abc123xyz... → Your actual token
  2. github-user → Your GitHub username
  3. my-awesome-project → Your repository name

Run it:

curl -X PUT \
  -H "Authorization: token YOUR_TOKEN_HERE" \
  -H "Accept: application/vnd.github.mercy-preview+json" \
  -H "Content-Type: application/json" \
  -d '{"names":["javascript","web-development","react","open-source"]}' \
  https://api.github.com/repos/github-user/my-awesome-project/topics

Success Response

If it works, GitHub returns:

{
  "names": ["javascript", "web-development", "react", "open-source"],
  "sha": "abc123def456..."
}

Status code: 200 OK


Step 4: Update Multiple Repositories with a Bash Script

The Problem with Manual cURL

Running the command 5+ times is repetitive and error-prone. A Bash script automates this.

Full Bash Script

Save this as update-github-topics.sh:

{
#!/bin/bash

# GitHub Topics Automation Script
# Usage: ./update-github-topics.sh
# Prompts for token, then updates all repositories

# Color output for clarity
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m' # No Color

echo -e "${BLUE}📚 GitHub Topics Automation Script${NC}\n"

# Prompt for token (hidden input)
read -s -p "🔐 Enter your GitHub Personal Access Token (ghp_...): " TOKEN
echo ""

# Verify token is not empty
if [ -z "$TOKEN" ]; then
  echo -e "${RED}❌ Error: Token cannot be empty${NC}"
  exit 1
fi

echo -e "${BLUE}🚀 Starting topic updates...${NC}\n"

# Define repositories and their topics
# Format: "owner:repo:topic1,topic2,topic3"
declare -a REPOS=(
  "github-user:my-awesome-project:javascript,web-development,react,open-source"
  "github-user:data-processor:python,data-science,pandas,automation"
  "github-user:api-wrapper:javascript,typescript,rest-api,nodejs"
  "github-user:cli-tool:bash,command-line,devops,automation"
  "github-user:mobile-app:react-native,mobile,ios,android"
)

# Counter for tracking success/failure
SUCCESS=0
FAILED=0

# Loop through each repository
for repo_info in "${REPOS[@]}"; do
  # Parse the repo info
  OWNER=$(echo $repo_info | cut -d: -f1)
  REPO=$(echo $repo_info | cut -d: -f2)
  TOPICS=$(echo $repo_info | cut -d: -f3)

  # Convert comma-separated topics to JSON array format
  # "topic1,topic2" → "\"topic1\",\"topic2\""
  TOPICS_JSON=$(echo $TOPICS | sed 's/,/","/g' | sed 's/^/"/g' | sed 's/$/"/g')

  echo -e "${BLUE}📝 Updating: ${OWNER}/${REPO}${NC}"
  echo "   Topics: $TOPICS"

  # Make the API request
  RESPONSE=$(curl -s -X PUT \
  -H "Authorization: token $TOKEN" \
  -H "Accept: application/vnd.github.mercy-preview+json" \
  -H "Content-Type: application/json" \
  -d "{\"names\":[${TOPICS_JSON}]}" \
  https://api.github.com/repos/${OWNER}/${REPO}/topics \
  -w "\n%{http_code}")

  # Extract HTTP status code (last line of response)
  HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
  BODY=$(echo "$RESPONSE" | head -n-1)

  # Check if request was successful (HTTP 200)
  if [ "$HTTP_CODE" = "200" ]; then
  echo -e "   ${GREEN}✅ Success${NC}\n"
  ((SUCCESS++))
  else
  echo -e "   ${RED}❌ Failed (HTTP $HTTP_CODE)${NC}"
  echo "   Error: $BODY\n"
  ((FAILED++))
  fi
done

# Summary
echo -e "${BLUE}═══════════════════════════════════════${NC}"
echo -e "${GREEN}✅ Successful: $SUCCESS${NC}"
echo -e "${RED}❌ Failed: $FAILED${NC}"
echo -e "${BLUE}═══════════════════════════════════════${NC}\n"

if [ $FAILED -eq 0 ]; then
  echo -e "${GREEN}🎉 All topics updated successfully!${NC}"
  exit 0
else
  echo -e "${RED}⚠️  Some updates failed. Check errors above.${NC}"
  exit 1
fi
}

How to Use This Script

Step 1: Create the file

# On your terminal, create the file
nano update-github-topics.sh

Step 2: Paste the script above

Step 3: Make it executable

chmod +x update-github-topics.sh

Step 4: Run it

./update-github-topics.sh

Step 5: Enter your token when prompted

📚 GitHub Topics Automation Script

🔐 Enter your GitHub Personal Access Token (ghp_...): ●●●●●●●●●●●●●●●

Expected Output

🚀 Starting topic updates...

📝 Updating: github-user/my-awesome-project
   Topics: javascript,web-development,react,open-source
   ✅ Success

📝 Updating: github-user/data-processor
   Topics: python,data-science,pandas,automation
   ✅ Success

📝 Updating: github-user/api-wrapper
   Topics: javascript,typescript,rest-api,nodejs
   ✅ Success

📝 Updating: github-user/cli-tool
   Topics: bash,command-line,devops,automation
   ✅ Success

📝 Updating: github-user/mobile-app
   Topics: react-native,mobile,ios,android
   ✅ Success

═══════════════════════════════════════
✅ Successful: 5
❌ Failed: 0
═══════════════════════════════════════

🎉 All topics updated successfully!

Step 5: Customize the Script for Your Repositories

Edit the Repository List

Open update-github-topics.sh and find this section:

declare -a REPOS=(
  "github-user:my-awesome-project:javascript,web-development,react,open-source"
  "github-user:data-processor:python,data-science,pandas,automation"
  "github-user:api-wrapper:javascript,typescript,rest-api,nodejs"
  "github-user:cli-tool:bash,command-line,devops,automation"
  "github-user:mobile-app:react-native,mobile,ios,android"
)

Change the Values

For each repository, follow this format:

"GITHUB_USERNAME:REPO_NAME:topic1,topic2,topic3,topic4"

Example 1: A Python project

"john-doe:machine-learning-toolkit:python,machine-learning,tensorflow,deep-learning"

Example 2: A React library

"company:react-components:react,javascript,ui-library,frontend"

Example 3: An organization repository

"facebook:react:javascript,react,frontend,facebook"

Add More Repositories

Just add more lines to the REPOS array:

declare -a REPOS=(
  "user:repo1:topic1,topic2"
  "user:repo2:topic3,topic4"
  "user:repo3:topic5,topic6"
  "org:repo4:topic7,topic8"
)

Step 6: Troubleshooting

Common Errors

Error: "Invalid request"

message: "Invalid request.\n\nFor 'links/0/schema'..."

Cause: JSON format is wrong.
Fix: Ensure topics are lowercase and in the correct JSON format: {"names":["topic1","topic2"]}

Error: "Problems parsing JSON"

message: "Problems parsing JSON"

Cause: Quotes or escaping issue.
Fix: Use single quotes for the entire JSON: '{"names":["topic1"]}'

Error: "Requires authentication"

message: "Requires authentication"

Cause: Token is invalid, expired, or missing.
Fix: Generate a new token at https://github.com/settings/tokens

Error: "Not Found"

message: "Not Found"

Cause: Repository owner or name is wrong.
Fix: Verify your GitHub username and repository names.

Test Your Token

Before running the full script, test your token:

curl -H "Authorization: token YOUR_TOKEN" \
  https://api.github.com/user

If it works, you'll see your GitHub profile info. If it fails, your token is invalid.


Advanced: Real-World Example

Scenario: Open-Source Blockchain Project

Let's say you have 4 blockchain repositories and want to update topics with a consistent theme:

declare -a REPOS=(
  "myorg:erc1400-contracts:solidity,smart-contracts,ethereum,erc-1400,security-token"
  "myorg:sto-backend-api:typescript,express,rest-api,blockchain,backend"
  "myorg:sto-web-client:react,next-js,web3,wallet-integration,frontend"
  "myorg:erc1400-dashboard:react,next-js,dashboard,admin-panel,blockchain"
)

This ensures:

  • Consistent branding across all repos
  • Proper discoverability (developers searching "ethereum" find all 4)
  • Easy maintenance (update once, applies everywhere)
  • Version control (script is in Git, editable by team)

Best Practices

Topic Guidelines

  1. Use lowercase only: solidity ✅, Solidity ❌
  2. Use hyphens for multi-word: smart-contracts ✅, smart contracts ❌
  3. Be specific: machine-learning ✅, ml ❌
  4. Limit to 50 topics per repository (GitHub's hard limit)
  5. Aim for 5-15 topics per repo (sweet spot for discoverability)

Recommended Topics by Technology

Frontend: react, vue, angular, next-js, typescript, javascript, html-css

Backend: nodejs, express, python, django, rest-api, microservices, backend

Blockchain: ethereum, solidity, smart-contracts, web3, blockchain, erc-20, defi

DevOps: docker, kubernetes, ci-cd, devops, automation, bash, terraform

Data: python, data-science, machine-learning, pandas, numpy, tensorflow, analytics


Automate Further: GitHub Actions

If you want to update topics on every commit, use GitHub Actions:

Create .github/workflows/update-topics.yml:

name: Update GitHub Topics

on:
  push:
    branches: [main]

jobs:
  update-topics:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Update Topics
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          ./update-github-topics.sh

This runs your script automatically whenever you push to the main branch.


Verification

After running the script, verify topics were updated:

Method 1: Browser

  1. Go to: https://github.com/OWNER/REPO
  2. Scroll to About section
  3. Check Topics badge shows your topics

Method 2: API

curl -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/OWNER/REPO | jq '.topics'

Output:

[
  "javascript",
  "react",
  "web-development"
]

Method 3: GitHub's Topic Pages

Visit: https://github.com/topics/YOUR-TOPIC
Your repositories should appear in the list.


Conclusion

You now have a reusable, automated system to manage GitHub topics across multiple repositories. This approach:

Saves time: Update 10 repos in 30 seconds
Improves consistency: Same topics across all projects
Increases discoverability: Better SEO on GitHub
Scales easily: Add more repos to the script
Version controlled: Keep the script in Git


Quick Reference

One-Time Manual cURL

curl -X PUT \
  -H "Authorization: token YOUR_TOKEN" \
  -H "Accept: application/vnd.github.mercy-preview+json" \
  -H "Content-Type: application/json" \
  -d '{"names":["topic1","topic2"]}' \
  https://api.github.com/repos/OWNER/REPO/topics

Run the Automation Script

chmod +x update-github-topics.sh
./update-github-topics.sh

Get Your Token

https://github.com/settings/tokens → Generate new token (classic) → Select repo scope


Happy topic-updating! 🚀