Skip to content

Instantly share code, notes, and snippets.

@adamwdennis
Created September 19, 2025 13:31
Show Gist options
  • Select an option

  • Save adamwdennis/74b7aeeab07c5ce5df5a01d9872e125d to your computer and use it in GitHub Desktop.

Select an option

Save adamwdennis/74b7aeeab07c5ce5df5a01d9872e125d to your computer and use it in GitHub Desktop.
List and cleanup deployments on Vercel
#!/bin/bash
###############################################################################
# Title: vercel-api-deployments.sh - Deployment cleanup functions
# Description: This script is used to list and cleanup deployments on Vercel.
# Author: Adam Dennis
# Date: 2025-09-19
# Version: 1.0.0
###############################################################################
# Usage: ./vercel-api-deployments.sh
# Simply set your Vercel API credentials (below) and then run the script to
# see the following menu:
#
# === Deployment Cleanup Menu ===
# 1. List all deployments (basic)
# 1a. List all deployments (detailed)
# 1b. List all deployments (comprehensive)
# 2. List deployments by branch
# 3. Remove failed deployments
# 4. Remove old deployments (30 days)
# 5. Remove old deployments (custom days)
# 6. Remove deployments by branch name
# q. Quit
#
# Choose an option: _
###############################################################################
###############################################################################
# Set your Vercel API credentials here
###############################################################################
VERCEL_TOKEN="YOUR_API_TOKEN"
VERCEL_ORG_ID="YOUR_ORG_ID"
VERCEL_PROJECT_ID="YOUR_PROJECT_ID"
# Function to test Vercel API access
test_vercel_api_access() {
echo "Verifying Vercel API access given your credentials..."
# Test token and org access
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v2/teams/$VERCEL_ORG_ID" | jq '.name // .error'
# Test project access
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v1/projects/$VERCEL_PROJECT_ID?teamId=$VERCEL_ORG_ID" | jq '.name // .error'
# Test deployments endpoint
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID&limit=1" | jq '.deployments[0].uid // .error'
echo ""
echo "=== CLEANUP FUNCTIONS ==="
}
# Function to list all deployments with details including branch name
list_deployments() {
echo "Fetching all deployments..."
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \
| jq -r '.deployments[] | "\(.uid) | \(.state) | \(.meta.githubCommitRef // "N/A") | \(.url) | \(.createdAt)"'
}
# Alternative: More detailed listing with better formatting
list_deployments_detailed() {
echo "Fetching all deployments..."
echo "UID | State | Branch | URL | Created"
echo "----------------------------------------------------"
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \
| jq -r '.deployments[] |
"\(.uid[0:8])... | \(.state) | \(.meta.githubCommitRef // "main/prod") | \(.url) | \(.createdAt | strftime("%Y-%m-%d %H:%M"))"'
}
# Function to show deployment metadata (useful for debugging)
show_deployment_meta() {
local deployment_id="$1"
if [ -z "$deployment_id" ]; then
echo "Usage: show_deployment_meta <deployment_id>"
return 1
fi
echo "Metadata for deployment: $deployment_id"
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" \
| jq '.meta'
}
# Helper function to display deployment info in comprehensive format
show_deployment_info() {
local deployments_json="$1"
local deployment_ids="$2"
printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" "UID" "State" "Branch" "Commit" "URL" "Created"
echo "------------------------------------------------------------------------------------------------------------"
echo "$deployment_ids" | while read deployment_id; do
if [ ! -z "$deployment_id" ]; then
echo "$deployments_json" | jq -r --arg id "$deployment_id" \
'.deployments[] | select(.uid == $id) |
"\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url[0:30]) \(.createdAt | strftime("%m-%d %H:%M"))"' \
| while read line; do
printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" $line
done
fi
done
}
# Function to get all deployments with pagination and filtering
get_all_deployments() {
local state_filter="$1" # Optional: "ERROR", "CANCELED", etc.
local all_deployments="[]"
local until_param=""
while true; do
# Build API URL with optional state filter and pagination
local api_url="https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID&limit=100"
if [ ! -z "$state_filter" ]; then
api_url="${api_url}&state=${state_filter}"
fi
if [ ! -z "$until_param" ]; then
api_url="${api_url}&until=${until_param}"
fi
echo "Fetching deployments... (until: ${until_param:-'latest'})" >&2
local response=$(curl -s -H "Authorization: Bearer $VERCEL_TOKEN" "$api_url")
local deployments=$(echo "$response" | jq '.deployments // []')
local pagination=$(echo "$response" | jq '.pagination // {}')
# Merge deployments
all_deployments=$(echo "$all_deployments $deployments" | jq -s 'add')
# Check if there are more pages
local next_until=$(echo "$pagination" | jq -r '.next // empty')
if [ -z "$next_until" ] || [ "$next_until" = "null" ]; then
break
fi
until_param="$next_until"
done
echo "$all_deployments"
}
# Updated function to remove failed deployments using API filtering
remove_failed_deployments_optimized() {
echo "Finding all failed deployments..."
# Get failed deployments directly from API
local error_deployments=$(get_all_deployments "ERROR")
local canceled_deployments=$(get_all_deployments "CANCELED")
# Combine both arrays
local all_failed=$(echo "$error_deployments $canceled_deployments" | jq -s 'add | unique_by(.uid)')
local failed_count=$(echo "$all_failed" | jq 'length')
if [ "$failed_count" -eq 0 ]; then
echo "No failed deployments found."
return
fi
echo "Found $failed_count failed deployments:"
# Show comprehensive info
printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" "UID" "State" "Branch" "Commit" "URL" "Created"
echo "------------------------------------------------------------------------------------------------------------"
echo "$all_failed" | jq -r '.[] |
"\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url[0:30]) \(.createdAt | strftime("%m-%d %H:%M"))"' \
| while read line; do
printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" $line
done
echo ""
read -p "Delete these $failed_count failed deployments? (y/N): " confirm
if [[ $confirm == [yY] ]]; then
echo "$all_failed" | jq -r '.[].uid' | while read deployment_id; do
echo "Deleting $deployment_id..."
curl -s -X DELETE \
"https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" \
-H "Authorization: Bearer $VERCEL_TOKEN"
echo "✓ Deleted $deployment_id"
done
echo "✓ All failed deployments deleted."
else
echo "Deletion cancelled."
fi
}
# Function to get deployments by state with comprehensive filtering
get_deployments_by_criteria() {
local criteria="$1" # "failed", "old", "branch:name", "state:ERROR"
case "$criteria" in
"failed")
# Get both ERROR and CANCELED states
local error_deps=$(get_all_deployments "ERROR")
local canceled_deps=$(get_all_deployments "CANCELED")
echo "$error_deps $canceled_deps" | jq -s 'add | unique_by(.uid)'
;;
"old")
local days_old=${2:-30}
local cutoff_date=$(date -d "$days_old days ago" -u +%s)000
get_all_deployments | jq --arg cutoff "$cutoff_date" \
'[.[] | select((.createdAt | tonumber) < ($cutoff | tonumber)) |
select(.state != "READY" or .target != "production")]'
;;
branch:*)
local branch_name="${criteria#branch:}"
get_all_deployments | jq --arg branch "$branch_name" \
'[.[] | select(.meta.githubCommitRef == $branch)]'
;;
state:*)
local state="${criteria#state:}"
get_all_deployments "$state"
;;
*)
echo "Unknown criteria: $criteria" >&2
return 1
;;
esac
}
# Enhanced function with better filtering options
remove_deployments_enhanced() {
echo "Enhanced deployment cleanup with API filtering..."
echo "1. Failed deployments (ERROR + CANCELED) - API filtered"
echo "2. Deployments by specific state (ERROR, CANCELED, BUILDING, etc.)"
echo "3. Old deployments (custom days)"
echo "4. Deployments by branch name"
echo "5. Custom criteria"
echo ""
read -p "Choose option (1-5): " option
case $option in
1)
remove_failed_deployments_optimized
;;
2)
read -p "Enter state (ERROR, CANCELED, BUILDING, READY, etc.): " state
local deployments=$(get_deployments_by_criteria "state:$state")
show_and_delete_deployments "$deployments" "deployments with state '$state'"
;;
3)
read -p "Enter number of days old: " days
local deployments=$(get_deployments_by_criteria "old" "$days")
show_and_delete_deployments "$deployments" "deployments older than $days days"
;;
4)
read -p "Enter branch name: " branch
local deployments=$(get_deployments_by_criteria "branch:$branch")
show_and_delete_deployments "$deployments" "deployments for branch '$branch'"
;;
5)
echo "Available criteria: failed, old, branch:name, state:STATE"
read -p "Enter criteria: " criteria
local deployments=$(get_deployments_by_criteria "$criteria")
show_and_delete_deployments "$deployments" "deployments matching '$criteria'"
;;
*)
echo "Invalid option"
;;
esac
}
# Helper function to show and delete deployments
show_and_delete_deployments() {
local deployments_json="$1"
local description="$2"
local count=$(echo "$deployments_json" | jq 'length')
if [ "$count" -eq 0 ]; then
echo "No $description found."
return
fi
echo "Found $count $description:"
printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" "UID" "State" "Branch" "Commit" "URL" "Created"
echo "------------------------------------------------------------------------------------------------------------"
echo "$deployments_json" | jq -r '.[] |
"\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url[0:30]) \(.createdAt | strftime("%m-%d %H:%M"))"' \
| while read line; do
printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" $line
done
echo ""
read -p "Delete these $count $description? (y/N): " confirm
if [[ $confirm == [yY] ]]; then
echo "$deployments_json" | jq -r '.[].uid' | while read deployment_id; do
echo "Deleting $deployment_id..."
curl -s -X DELETE \
"https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" \
-H "Authorization: Bearer $VERCEL_TOKEN"
echo "✓ Deleted $deployment_id"
done
echo "✓ All $description deleted."
else
echo "Deletion cancelled."
fi
}
# Function to list deployments with comprehensive GitHub info
list_deployments_comprehensive() {
echo "Fetching all deployments with GitHub details..."
printf "%-12s %-10s %-20s %-8s %-50s\n" "UID" "State" "Branch" "Commit" "URL"
echo "--------------------------------------------------------------------------------------------------------"
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \
| jq -r '.deployments[] |
"\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url)"' \
| while read line; do
printf "%-12s %-10s %-20s %-8s %-50s\n" $line
done
}
# Function to list deployments for a specific branch
list_deployments_by_branch() {
local branch_name="$1"
if [ -z "$branch_name" ]; then
echo "Usage: list_deployments_by_branch <branch_name>"
return 1
fi
echo "Deployments for branch: $branch_name"
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \
| jq -r --arg branch "$branch_name" \
'.deployments[] | select(.meta.githubCommitRef == $branch) |
"\(.uid) | \(.state) | \(.url) | \(.createdAt)"'
}
# Interactive menu
show_menu() {
echo ""
echo "=== Deployment Cleanup Menu ==="
echo "1. List all deployments (basic)"
echo "1a. List all deployments (detailed)"
echo "1b. List all deployments (comprehensive)"
echo "2. List deployments by branch"
echo "3. Remove failed deployments"
echo "4. Remove old deployments (30 days)"
echo "5. Remove old deployments (custom days)"
echo "6. Remove deployments by branch name"
echo "q. Quit"
echo ""
}
# Main interactive loop
test_vercel_api_access
while true; do
show_menu
read -p "Choose an option: " choice
case $choice in
1) list_deployments ;;
1a) list_deployments_detailed ;;
1b) list_deployments_comprehensive ;;
2)
read -p "Enter branch name: " branch
list_deployments_by_branch "$branch"
;;
3) remove_failed_deployments_optimized ;;
4) remove_old_deployments 30 ;;
5)
read -p "Enter number of days: " days
remove_old_deployments "$days"
;;
6)
read -p "Enter branch name: " branch
remove_branch_deployments "$branch"
;;
q|Q) echo "Goodbye!"; break ;;
*) echo "Invalid option" ;;
esac
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment