Optimize SVGs in your CI pipeline with a REST API (no SVGO install needed)
Icon SVGs exported from Figma are 3-5x larger than they need to be. Design tools embed
metadata, empty <defs> blocks, editor-specific attributes, and
verbose path data that browsers never use. A 24px icon that should be 800 bytes ships
at 3,200 bytes. Multiply that by 50 icons in a design system and you're sending 120 KB
of unnecessary SVG data on every page load.
The standard fix is SVGO, a Node.js tool that strips this bloat. But SVGO brings 30+ npm dependencies into your project or CI environment. If your frontend is built with Go, Rust, Python, or plain HTML, installing Node.js and npm solely to minify SVGs is overkill.
The botoi SVG optimizer API does the same work with a single HTTP request. No installs, no dependencies, no version conflicts. Send the raw SVG, get back the optimized version with size metrics.
One curl command to optimize an SVG
Send your SVG string to the /v1/svg/optimize endpoint:
curl -s -X POST https://api.botoi.com/v1/svg/optimize \
-H "Content-Type: application/json" \
-d '{
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><!-- Figma metadata --><defs></defs><g id=\"Layer_1\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z\" fill=\"#1a73e8\"/></g></svg>"
}' The API returns the optimized SVG along with before/after size data:
{
"success": true,
"data": {
"optimized": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z\" fill=\"#1a73e8\"/></svg>",
"original_size": 12450,
"optimized_size": 5180,
"savings_percent": 58.4
}
} The response includes the byte count of the original and optimized SVG, plus the percentage saved. Use these numbers to track optimization impact across your asset library.
GitHub Actions workflow for auto-optimization
This workflow runs on every pull request that modifies SVG files. It finds changed SVGs, optimizes each one through the API, and commits the results back to the PR branch. Reviewers see optimized SVGs without any manual step.
Create .github/workflows/optimize-svgs.yml in your repository:
name: Optimize SVGs
on:
pull_request:
paths:
- '**/*.svg'
jobs:
optimize-svgs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Find changed SVG files
id: changed
run: |
FILES=$(git diff --name-only --diff-filter=ACM origin/${{ github.base_ref }} HEAD -- '*.svg')
echo "files=$FILES" >> $GITHUB_OUTPUT
echo "Found SVGs: $FILES"
- name: Optimize SVGs
if: steps.changed.outputs.files != ''
run: |
TOTAL_SAVED=0
for FILE in ${{ steps.changed.outputs.files }}; do
SVG_CONTENT=$(cat "$FILE")
PAYLOAD=$(jq -n --arg svg "$SVG_CONTENT" '{"svg": $svg}')
RESPONSE=$(curl -s -X POST https://api.botoi.com/v1/svg/optimize \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.BOTOI_API_KEY }}" \
-d "$PAYLOAD")
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
if [ "$SUCCESS" = "true" ]; then
echo "$RESPONSE" | jq -r '.data.optimized' > "$FILE"
SAVINGS=$(echo "$RESPONSE" | jq -r '.data.savings_percent')
ORIGINAL=$(echo "$RESPONSE" | jq -r '.data.original_size')
OPTIMIZED=$(echo "$RESPONSE" | jq -r '.data.optimized_size')
echo "Optimized $FILE: $ORIGINAL -> $OPTIMIZED bytes ($SAVINGS% saved)"
TOTAL_SAVED=$((TOTAL_SAVED + ORIGINAL - OPTIMIZED))
else
echo "::warning::Failed to optimize $FILE"
fi
done
echo "Total bytes saved: $TOTAL_SAVED"
- name: Commit optimized SVGs
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add '*.svg'
git diff --cached --quiet || git commit -m "chore: optimize SVG assets"
git push
The workflow only processes SVGs that changed in the current PR, keeping API calls
to a minimum. The --diff-filter=ACM flag catches added, copied, and
modified files while skipping deletions.
Before and after: real-world size comparison
Here are results from running the API on SVGs exported from Figma for a typical web application's icon set and illustrations:
| File | Before | After | Savings |
|-----------------------|----------|----------|---------|
| logo.svg | 14,280 B | 5,840 B | 59.1% |
| icon-search.svg | 3,420 B | 1,180 B | 65.5% |
| illustration-hero.svg | 48,900 B | 21,300 B | 56.4% |
| icon-menu.svg | 2,100 B | 890 B | 57.6% |
| chart-bar.svg | 8,750 B | 3,620 B | 58.6% | Average savings across these files: 59.4%. The largest gains come from illustration SVGs that contain embedded metadata and verbose path definitions. Simple icons still see 55-65% reductions because Figma exports decimal precision and attributes that browsers ignore.
Batch optimization script
For local use or integration into other CI systems (GitLab CI, CircleCI, Jenkins), this shell script optimizes every SVG in a directory:
#!/bin/bash
# optimize-svgs.sh
# Optimize all SVGs in a directory using the botoi API
SVG_DIR="${1:-.}"
API_KEY="${BOTOI_API_KEY:-}"
TOTAL_ORIGINAL=0
TOTAL_OPTIMIZED=0
FILE_COUNT=0
AUTH_HEADER=""
if [ -n "$API_KEY" ]; then
AUTH_HEADER="-H \"Authorization: Bearer $API_KEY\""
fi
find "$SVG_DIR" -name "*.svg" -type f | while read -r FILE; do
SVG_CONTENT=$(cat "$FILE")
PAYLOAD=$(jq -n --arg svg "$SVG_CONTENT" '{"svg": $svg}')
RESPONSE=$(curl -s -X POST https://api.botoi.com/v1/svg/optimize \
-H "Content-Type: application/json" \
$AUTH_HEADER \
-d "$PAYLOAD")
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
if [ "$SUCCESS" = "true" ]; then
OPTIMIZED=$(echo "$RESPONSE" | jq -r '.data.optimized')
ORIGINAL_SIZE=$(echo "$RESPONSE" | jq -r '.data.original_size')
OPTIMIZED_SIZE=$(echo "$RESPONSE" | jq -r '.data.optimized_size')
SAVINGS=$(echo "$RESPONSE" | jq -r '.data.savings_percent')
echo "$OPTIMIZED" > "$FILE"
echo " $FILE: $ORIGINAL_SIZE -> $OPTIMIZED_SIZE bytes ($SAVINGS% saved)"
TOTAL_ORIGINAL=$((TOTAL_ORIGINAL + ORIGINAL_SIZE))
TOTAL_OPTIMIZED=$((TOTAL_OPTIMIZED + OPTIMIZED_SIZE))
FILE_COUNT=$((FILE_COUNT + 1))
else
echo " SKIP $FILE: optimization failed"
fi
done
echo ""
echo "Processed $FILE_COUNT files"
echo "Total: $TOTAL_ORIGINAL -> $TOTAL_OPTIMIZED bytes" Run it on your assets directory:
chmod +x optimize-svgs.sh
./optimize-svgs.sh src/assets/icons
Set the BOTOI_API_KEY environment variable to bypass the anonymous
rate limit of 5 requests per minute. Without a key, the script pauses between
requests to stay under the limit.
Alternative: pre-commit hook
If you prefer to optimize SVGs before they reach your repository, use a Git pre-commit hook. This approach catches bloated SVGs at the developer's machine instead of in CI:
#!/bin/bash
# .git/hooks/pre-commit
# Optimize staged SVGs before committing
STAGED_SVGS=$(git diff --cached --name-only --diff-filter=ACM -- '*.svg')
if [ -z "$STAGED_SVGS" ]; then
exit 0
fi
echo "Optimizing staged SVGs..."
for FILE in $STAGED_SVGS; do
SVG_CONTENT=$(cat "$FILE")
PAYLOAD=$(jq -n --arg svg "$SVG_CONTENT" '{"svg": $svg}')
RESPONSE=$(curl -s -X POST https://api.botoi.com/v1/svg/optimize \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
if [ "$SUCCESS" = "true" ]; then
echo "$RESPONSE" | jq -r '.data.optimized' > "$FILE"
SAVINGS=$(echo "$RESPONSE" | jq -r '.data.savings_percent')
echo " Optimized $FILE ($SAVINGS% saved)"
git add "$FILE"
fi
done The hook only processes SVGs staged for commit, so it won't touch files you haven't changed. Each optimized file is re-staged automatically.
What gets removed during optimization
The API targets data that has zero visual impact on the rendered SVG:
- Editor metadata: Figma, Illustrator, and Sketch embed tool-specific
attributes (
data-name,sketch:type) that browsers skip. - XML comments: Design tools add comments like
<!-- Generator: Adobe Illustrator 28.0 -->that serve no purpose in production. - Empty groups and defs: Exported SVGs contain empty
<g>and<defs>elements left over from layer structures in the design tool. - Redundant attributes: Default values like
fill-opacity="1"andstroke-miterlimit="4"match browser defaults and can be removed. - Excess precision: Path coordinates with 8 decimal places
(
12.34567890) get rounded to 2 (12.35) without visible difference at screen resolution.
When to optimize SVGs elsewhere
The API approach works best for icon sets, logos, and UI illustrations. A few cases where a different strategy makes more sense:
- SVGs with embedded fonts: If your SVG contains
<text>elements with custom fonts, convert text to outlines in your design tool before optimizing. The API doesn't subset or embed fonts. - Animated SVGs: SMIL animations and CSS animations inside SVGs are preserved, but test the output to confirm timing attributes stay intact.
- SVGs above 2 MB: These typically contain base64-encoded raster images. Extract those as separate PNG/JPEG files and reference them instead.
Frequently asked questions
- Do I need an API key to use the SVG optimizer endpoint?
- No. The botoi API allows 5 anonymous requests per minute with no key. For CI pipelines that process many SVGs per commit, add your API key as a GitHub secret and pass it in the Authorization header to remove rate limits.
- What optimizations does the API apply to SVGs?
- The API removes metadata, editor artifacts, comments, empty groups, and redundant attributes. It collapses unnecessary transforms, merges paths where possible, and rounds numeric values. The result is equivalent to running SVGO with its default preset.
- Will the optimized SVG look different from the original?
- No. The API only removes data that has no visual effect. The rendered output is pixel-identical to the original. If an SVG uses features that depend on metadata (like Illustrator layer names), those strings are stripped but the visual rendering stays the same.
- Can I process SVGs larger than 1 MB?
- The API accepts SVG payloads up to 2 MB. If your SVGs are larger than that, they likely contain embedded raster images (base64-encoded PNGs or JPEGs inside the SVG). Extract those images as separate files and reference them with an image tag instead.
- How does this compare to installing SVGO locally?
- SVGO requires Node.js and npm in your CI environment, adds 30+ transitive dependencies, and needs version management. The API approach is a single curl command with zero dependencies. Trade-off: it requires network access during CI, while SVGO runs offline.
Try this API
HTML Sanitize API — interactive playground and code examples
More integration posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.