Open-source project
badges/shields avatar
badges/shields

badges/shields: self-hosting the badge server behind shields.io

Concise, consistent, and legible badges in SVG and raster format

27,225 stars5,618 forksJavaScriptApache-2.0

At a glance

What is it?
Shields.io is the service that renders SVG status badges for GitHub readmes. The badges/shields repository holds that server, the badge-maker NPM library and the badge design spec. This article covers what the code actually does, how to run it locally on port 3000, and when a hosted badge URL is the better answer.
Who is it for?
Adopt badges/shields when you need badge rendering inside your own infrastructure or you are adding a service definition to the upstream project. Do not adopt it as a general-purpose image service: the server is built around a fixed catalogue of badge definitions, not arbitrary image generation.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem badges/shields solves, and who it is for

A README that claims a build passes is worth nothing unless the claim is generated. Shields.io exists to turn a fact held somewhere else (a CI result, a package registry version, a coverage percentage) into a small SVG image you can embed with a single URL. The repository is the source of that service, not a client library. It contains the frontend and server code, an NPM library called badge-maker for generating badges outside the service, and the badge design specification that fixes how a badge should look.

The audience splits cleanly. The first group is maintainers who want a badge in a README and never touch this repository at all; they use the hosted service. The second group is people who want to run the badge server themselves, add a new service definition, or generate badge images programmatically in their own tooling. The README is explicit that the project is community-run and that a backlog of suggestions exists, with a labelled good first issue queue for newcomers and a tutorial document for adding a badge. If you are in the first group, most of this repository is irrelevant to you. If you are in the second or third, it is the whole subject.

How the badge server turns a URL into an SVG

The architecture visible in the repository is a request pipeline. A URL like /npm/v/nock is matched against a service definition, the definition fetches data from the upstream provider, and the result is rendered as an image. The services directory holds those definitions, and the core directory holds the shared machinery. The badge-maker package, referenced from package.json as a file dependency rather than a published version, is what actually produces the SVG or raster output.

Several dependencies reveal the shape of the work. got handles outbound HTTP, graphql and graphql-tag support providers with GraphQL APIs, jsonpath-plus and js-yaml suggest configuration-driven extraction, and parse-link-header exists because paginated provider APIs need link following. joi and joi-extension-semver are used for validation, including version strings. The presence of pg and node-pg-migrate means the server can talk to PostgreSQL and has a migration directory, so a deployment is not purely stateless. prom-client and @sentry/node-core are wired in for metrics and error reporting, and the README notes that both are configurable.

The build step matters for anyone running this in production. Badge definitions are compiled before the server first starts, and the README states that changing a service definition does not trigger a rebuild by itself: you either run npm run prestart or restart the server manually. That is a real operational detail, not a footnote, because it means editing a service and watching the dev server reload will not show your change.

Running the badge server locally with npm start

The README gives a five-step development setup. Node 24 is the required runtime, and the .node-version file in the repository root pins it for version managers. After cloning, dependencies install with npm ci, which is the lockfile-respecting variant rather than npm install.

bash
# after cloning the repository
npm ci
npm start

With the server up, the frontend is served at http://localhost:3000/. The README states that the badge server restarts itself through nodemon when server source files change, and that the frontend dev server reloads on its own, but that badge definitions are built only before the server first starts. To pick up a definition change you run the prestart script or restart the server.

bash
npm run prestart

For checking a single badge without opening a browser, the README documents a CLI wrapper that accepts either a path or a full URL.

bash
npm run badge -- /npm/v/nock
npm run badge -- https://img.shields.io/npm/v/nock

There is also a debug mode entry point, npm run debug:server, and the README links a recipe for attaching VS Code to the Node process. If you want the container route instead, the Dockerfile is a two-stage build on node:24-alpine: it installs dependencies with NODE_ENV=development and CYPRESS_INSTALL_BINARY=0, runs npm run build, prunes dev dependencies, and the final stage runs node server with NODE_ENV=production and exposes ports 80 and 443.

Snapshot tests and the cost of changing badge output

The repository carries a __snapshots__ directory and a Cypress setup, and the README describes snapshot tests as the mechanism that prevents inadvertent changes to SVG or JSON output. This is the part of the project that most affects contributors. Any change that alters rendered output will fail those tests, and the README prescribes two environment variables for handling it: SNAPSHOT_DRY=1 npm run test:package previews the diff against saved snapshots, and SNAPSHOT_UPDATE=1 npm run test:package writes the new ones.

That design has a clear trade-off. It makes silent visual regressions hard to introduce, which is exactly what you want from a service whose entire output is an image consumed by third parties. It also means that a legitimate formatting improvement to a badge touches a large number of snapshot files, and a reviewer has to read that diff rather than trust it. If you only consume badge URLs, none of this reaches you. If you fork the server and modify rendering, budget for the snapshot churn before you start.

When a hosted badge URL beats self-hosting

The strongest argument against running this yourself is that the hosted service already does it, and does it at a scale the README quantifies: over 1.6 billion images served every month. A self-hosted instance inherits none of that. It inherits the outbound HTTP calls to every provider you enable, the rate limits those providers impose on your IP rather than on a shared service, and the operational work of keeping the process alive.

Self-hosting is the wrong tool when your only goal is a version badge in a README. Point the image tag at the hosted URL and stop. It is also a poor fit if you expect it to render arbitrary images or act as a general templating service; the server is built around a catalogue of badge definitions, and the badge-maker library is the supported path for generating badges programmatically outside the request pipeline.

Self-hosting becomes reasonable when the badge data cannot leave your network, when you need badges for an internal service that no public provider knows about, or when you are developing a new service definition and need a local instance to iterate against. The Dockerfile and the fly.toml and Procfile in the repository root indicate the project itself expects container or platform deployment, not a hand-rolled process supervisor.

Alternatives: static badge services and hand-written SVG

The closest alternative for many users is a static badge URL, which shields.io itself supports. The README gives the pattern https://img.shields.io/badge/left-right-f39f37 and links a static badge builder. The difference in approach is fundamental: a static badge encodes fixed text and colour in the URL, so there is no upstream fetch, no provider rate limit and no possibility of a stale value being mistaken for a live one. If your badge says coverage-80%25-yellowgreen, it says that forever until you edit the README. A dynamic badge such as the npm version example pulls the value at request time. Choosing between them is choosing whether you want the badge to be a fact or a label.

The other alternative is generating the SVG yourself. The badge-maker package is published on NPM and documented separately, with its own changelog, and the repository also holds the badge design specification under spec/. Using badge-maker directly gives you the same visual output without running the server or depending on a third-party image host. The cost is that you now own the rendering call in your own build or CI, and you get whatever version of the library you pinned rather than the hosted service's current output. That is a reasonable trade for a documentation site that builds its own assets, and a bad one for a README that just wants an image tag.

Licence, maintenance and upgrade cost

The repository is dual-licensed. The LICENSE-APACHE and LICENSE-MIT files sit at the top level, and package.json declares the licence as "(MIT OR Apache-2.0)". The GitHub metadata lists Apache-2.0 for the repository, but the package manifest is the more precise statement for the code you would redistribute. The badge-maker package is published separately and carries its own changelog, so its versioning is not the same as the server's; the server pins it as a file dependency, which means a fork of the server and a published badge-maker release can drift apart. This is a description of the licensing and packaging arrangement, not legal advice; if you redistribute, read both licence files.

On maintenance: the last push to the default branch was on 2026-09-19, and the repository is not archived. The README links a daily test workflow and a coverage badge, and the project describes itself as community-run with a substantial backlog. The practical upgrade cost for a self-hosted instance comes from the provider side rather than the server side. Every service definition depends on an external API, and those APIs change on their own schedule. Running the server means tracking upstream API changes across whichever subset of providers you enable, plus the Node major version the project requires, which the Dockerfile fixes at node:24-alpine.

Editorial conclusion

Adopt badges/shields when you need badge rendering inside your own infrastructure or you are adding a service definition to the upstream project. Do not adopt it as a general-purpose image service: the server is built around a fixed catalogue of badge definitions, not arbitrary image generation. Before committing, verify three things: that Node 24 matches your runtime, that you are willing to carry the badge-maker package from the repo rather than a published version, and that the built-in Sentry and Prometheus hooks cover your observability needs, because the README points at separate configuration docs for both.

Frequently asked questions

How do I use shields.io to add a badge to a README?

Browse the badge list on shields.io, use the search bar or categories to find a badge type, and click it to fill in the required data such as your username or repository. The page lets you customise the label and colours, and a button at the bottom copies the badge URL or snippet for your README or web page.

What is the badge-maker package in the badges/shields repository?

It is an NPM library for generating badges, published on NPM and documented with its own README and changelog in the repository. The server depends on it as a file dependency, so it is both the rendering layer of the service and a standalone library you can use directly.

What Node version does badges/shields require?

The README's development steps say to install Node 24, and the repository root contains a .node-version file. The Dockerfile builds on node:24-alpine in both stages.

Why does my edited badge definition not show up in the dev server?

The README states that badge definitions are built only before the server first starts, so a running instance will not pick up definition changes automatically. Run npm run prestart or restart the server to regenerate them.

What is the licence of the badges/shields code?

package.json declares the licence as "(MIT OR Apache-2.0)", and both LICENSE-APACHE and LICENSE-MIT are present at the repository root. The GitHub metadata lists Apache-2.0.

Official sources

  1. badges/shields on GitHub
  2. Issues
  3. License: Apache-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/badges-shields.svg)](https://hysenlabs.com/projects/badges-shields)