Skip to content
integration

Optimize SVGs in your CI pipeline with a REST API (no SVGO install needed)

| 5 min read
CI/CD pipeline visualization diagram
Photo by Growtika on Unsplash

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
  }
}
CI/CD pipeline with green checkmarks
Photo by Roman Synkevych on Unsplash

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" and stroke-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.