Generates a dynamic progress bar image (SVG) using Python, deployed freely via Vercel β or self-hosted on your own server.
π Preview:
This service is deployed on Vercel and accessible via the domain progress-bar.xyz.
| π§ Parameter | π Description | π― Default Value |
|---|---|---|
title |
Adds a title to the progress bar | None |
scale |
The maximum value that the progress bar represents | 100 |
as_percent |
With scale, show the number as a percentage of the scale |
false |
prefix |
A string to add before the progress number | None |
suffix |
A string to add after the progress number | % |
width |
The width of the progress bar in pixels | 60 with a title, 90 without |
color |
The color of the title area (hex code without #) |
428bca (blue) |
progress_background |
The background color of the progress bar (hex code without #) |
555 (grey) |
progress_number_color |
The color of the progress number (hex code without #) |
fff (white) |
progress_color |
The color of the progress bar (hex code without #) |
Depends on percentage |
show_text |
Whether to display or hide the progress text | true |
show_shadow |
Whether to draw the text shadow | true |
radius |
Corner radius in pixels | 4 |
style |
The style. One of: default, flat, square, plastic, for-the-badge |
default |
origin |
Where the bar fills from: left, center (alias middle) or right. With center, zero is the middle of the bar β positive values grow right and negative values grow left (like Excel data bars) |
left |
negative_color |
Color of the bar for negative values (hex code without #). Overrides progress_color and the style color when the value is below zero |
Depends on percentage |
Below are several examples showcasing different ways to generate progress bars.
Negative numbers are accepted (e.g. /-28/). The bar length is the absolute value relative to scale, and the sign is kept in the text.
Use origin=center to get Excel-style data bars, which are handy for a column of positive and negative values in a Markdown table: each side of the middle axis spans scale.
origin and negative_color also work with /dynamic/json/, so a negative number read from a JSON document renders the same way.
We currently support:
| π¨ Style | π Preview | π URL |
|---|---|---|
| π’ flat (default) | https://progress-bar.xyz/100/?style=flat | |
| π² square | https://progress-bar.xyz/100/?style=square | |
| π‘οΈ plastic | https://progress-bar.xyz/100/?style=plastic | |
| π for-the-badge | https://progress-bar.xyz/100/?style=for-the-badge | |
| π thin-rounded (WIP) | https://progress-bar.xyz/100/?style=thin-rounded | |
| πͺ neo-glass (WIP) | https://progress-bar.xyz/100/?style=neo-glass | |
| π¨ minimal-matte (WIP) | https://progress-bar.xyz/100/?style=minimal-matte |
Besides a fixed value in the path (/75/), you can load the number from a remote JSON file.
Sample JSON file: https://raw.githubusercontent.com/guibranco/progressbar/main/docs/dynamic-json-sample.json
Use query parameters:
| Parameter | Description |
|---|---|
url |
Required. Address of the JSON document (http or https). Must be URL-encoded in the query string. |
query |
Required. JSONPath (e.g. $.items[0].metrics.pct) or dot form from the root (e.g. items.0.metrics.pct). Filter expressions are supported, so you can look up a value by a sibling field instead of a hardcoded index β e.g. $.progress[?(@.data.language.name=='Spanish')].data.translationProgress. |
cache |
Optional. Cache-Control max-age in seconds (alias cacheSeconds), between 0 and 86400. Default: 300. 0 disables caching for that request. |
Any other parameters from the table above (title, scale, style, β¦) apply the same way as on /{number}/.
Badges are fetched by GitHub's image proxy (camo), browsers and CDNs on every README render, so every response carries a Cache-Control header:
| Route | Cache-Control |
|---|---|
/{number}/ |
public, max-age=604800, s-maxage=604800, stale-while-revalidate=604800 β the SVG is fully determined by the URL, so it is safe to cache for a week |
/dynamic/json/ |
public, max-age=300, s-maxage=300 by default; override per request with cache= |
/favicon.ico |
public, max-age=2592000, s-maxage=2592000, stale-while-revalidate=604800 (30 days) |
Errors, /health |
no-store |
The two defaults can be changed per deployment with the environment variables PROGRESSBAR_STATIC_CACHE_SECONDS (0β2592000, default 7 days) and PROGRESSBAR_DYNAMIC_CACHE_SECONDS (0β86400). Keep in mind that changes to a badge's look can take up to the configured static TTL (7 days by default, up to 30 days if PROGRESSBAR_STATIC_CACHE_SECONDS is raised) to show up in READMEs β and a CDN that honours stale-while-revalidate may keep serving the previous version for up to another 7 days while it refreshes the entry in the background.
Deploy this project to Vercel with a single click:
The Hobby plan counts every request (cached or not) against its monthly quota, so a popular badge can hit the limit and get the deployment paused. The Cache-Control headers above keep most renders away from Vercel; if that is still not enough, self-host it.
The same code runs anywhere Python runs. The repository ships everything needed for a small VM (1 vCPU / 1 GB is plenty β the app is stateless and serves ~1.8 KB per badge in about a millisecond):
| File | Purpose |
|---|---|
Dockerfile, compose.yaml |
gunicorn (threaded workers) on 127.0.0.1:8099, non-root, with a Docker healthcheck |
gunicorn.conf.py |
Sizing/logging knobs, all overridable via PROGRESSBAR_* environment variables (deploy/env.example) |
deploy/nginx/ |
NGINX reverse proxy with a response cache (proxy_cache), so Python only runs on cache misses |
deploy/systemd/ |
systemd units for the compose stack, or for bare-metal gunicorn without Docker |
deploy/install.sh, deploy/update.sh |
First-time setup and redeploy scripts for the VM |
.github/workflows/deploy.yml |
Optional CI deploy over SSH after CI passes on main (opt-in via SELF_HOST_DEPLOY=true) |
Quick start on an Ubuntu host with Docker and NGINX installed (DOMAIN= and APP_DIR= can be exported first to override the defaults):
curl -fsSLO https://raw.githubusercontent.com/guibranco/progressbar/main/deploy/install.sh
less install.sh # review before running it as root
sudo bash install.sh
sudo certbot --nginx -d progress-bar.xyz # TLSOr without Docker:
python -m venv venv && venv/bin/pip install -r requirements.txt
venv/bin/gunicorn -c gunicorn.conf.py app:app # http://127.0.0.1:8099/58/GET /health returns {"status": "ok"} for uptime monitors and load balancers. Put the domain behind a CDN/proxy (for example Cloudflare's free plan) to get edge caching and to be able to switch origins without touching the app.
Check out CONTRIBUTING.md to learn how to contribute!
|
Guilherme Branco Stracini |
nimble-turtle |
ztest95 |
David Nwosu |
Eduardo Baptista |
bhosley |
|
dependabot[bot] |
github-actions[bot] |
penify-dev[bot] |
deepsource-autofix[bot] |
Snyk bot |
This project is licensed under the MIT License.
π See LICENSE for details.