shields
shields copied to clipboard
Website does not produce expected static badge outputs
Are you experiencing an issue with...
shields.io
🐞 Description
If I go to the website and ask it to generate a static badge, I get a 404 not found badge instead unless I add a hyphen to the badge name.
Additionally, the example states the following is valid:
Label, message and color separated by a dash -. For example:
- https://img.shields.io/badge/any_text-you_like-blue
But if I enter any_text-you_like
for the badge content, and blue
for the color, it generates the following:

which does not render as expected: , despite the docs saying badgeContents consists of a label and optional message.
💡 Possible Solution
Add a hyphen.
This should be clearly validated or if possible, fixed internally where possible. The docs should prevent me from making invalid badges but ideally it would be better if it just fixed it with the intention of making a single-colour badge instead. While the docs state that hyphen separators are acceptable, it is not clear that they are mandatory to stop things breaking.
This is going to be a bit of a long rambly reply
First off, I'll explain the behaviour you're seeing:
With all shields badges, you can override the label
and color
with URL parameters. This is broadly sensible. It allows you to take a badge like
https://img.shields.io/npm/dt/badge-maker and customise it to https://img.shields.io/npm/dt/badge-maker?label=custom%20label&color=white or whatever.
The format of the badgeContent
param for the static badge is either: label-message-color
(label, message and color, seperated by a dash) or message-color
(message and color, seperated by a dash).
e.g:
https://img.shields.io/badge/any_text-you_like-blue or https://img.shields.io/badge/just%20the%20message-8A2BE2
The static badge also supports all of the same standard params as every other shields badge. It is kind of pointless to set the label or color to one value in one part of the badge URL and then override it again in another, but you can do it. So I can do
https://img.shields.io/badge/any_text-you_like-blue?label=custom%20label (which renders the same thing as https://img.shields.io/badge/custom_label-you_like-blue ) or https://img.shields.io/badge/just%20the%20message-8A2BE2?color=white (which renders the same thing as https://img.shields.io/badge/just%20the%20message-white )
You still need to set everything in the badgeContent
part of the route even if you then want to override it with another value in a query param though. As I say, this is mostly pointless but we just blanket support the same customisations on every route.
So that's why you're seeing the behaviour you're seeing. We do try to explain how to construct a valid static badge URL in the screenshot you've posted:
That said, I think we are not communicating this well, or what we're providing users with doesn't match expectations. Here's a couple of similar queries to this on discord:
..and a similar-ish GitHub issue:
https://github.com/badges/shields/issues/9679
So I think at this point it is reasonable to ask: How can we do this better?
One approach would be to try and document what we have more clearly. Is there content we could add to https://shields.io/badges/static-badge to help clarify?
Another thing we could do would be to hide the label
and color
override query params on https://shields.io/badges/static-badge We'd still support them to avoid making a breaking change, but maybe hiding them in the frontend for this route might help make this page less confusing.
Could we improve the error handling so that something like https://img.shields.io/badge/foo gives a more helpful error message than just 404?
All of that possibly amounts to papering over the cracks though.
I think fundamentally, what users really want/expect (and why some people struggle, particularly if they just do what they think is the most obvious thing) is a page with 3 input boxes: 1 for label, 1 for message and 1 for color. This is not unreasonable. On every single other page on our site, there is one textbox for one route param. This is the only situation where we ask the user to mash multiple params into a single input box according to a format. The issue is that the route for the static badge (which pre-dates the current frontend by many many years) https://github.com/badges/shields/blob/8d7258b3440903e03762d92d1c6e9f5aa252b520/services/static-badge/static-badge.service.js#L45-L49 doesn't really lend itself to being expressed as an Open Api route, which is why we've ended up mashing that regex pattern into one param called badgeContent
https://github.com/badges/shields/blob/8d7258b3440903e03762d92d1c6e9f5aa252b520/services/static-badge/static-badge.service.js#L52
I think if we really want to solve this mismatch, we would probably need to make a new static badge route (obviously we'll have to keep the existing syntax working as there are a zillion badges using the existing format out there) that is more conducive to to providing a builder page in the current frontend that matches user expectations. 3 textboxes: 1 for label, 1 for message, 1 for color.
Otherwise, we might just be answering similar questions forever.
I want to give that a try
I've actually thought about this a bit more recently, but I hadn't taken the time to write it all down.
One of the interesting things about working on a codebase that has over 10 years of history to it is that there's all kinds of stuff in here. Sometimes even I can't remember everything that is in here.
As noted above, one of the things that is kind of weird and derpy about the existing badge is that label, message and color can be specified both in the main part of the route and also as query params. So going to back to an example from above, you can do https://img.shields.io/badge/any_text-you_like-blue?label=custom%20label to specify the label once and then override the label.
In this case, I'd completely lost sight of it when I originally wrote the post above but we actually already have a badge route that attempts to solve this problem by just doing everything using query params.
See https://github.com/badges/shields/issues/2673 and https://github.com/badges/shields/pull/3024 for the history.
So using that route, you can construct a static badge like this:
https://img.shields.io/static/v1?label=any%20text&message=you%20like&color=blue
and that gives you one and only one way to specify label
, message
and color
: In the query params.
We seem to have added that route, and then just not documented it 🙃 I don't think it is very widely used.
Conveniently for this situation, query params also already work exactly how we want for documentation/builder purposes.
So.. problem solved, right? Just switch to /static/v1
as the default badge route, document that and we're good? Well, there are a few problems..
One of them is that the /static/v1
route has some slightly strange behaviours. For example:
- https://img.shields.io/static/v1?label=&message=message&color=blue and https://img.shields.io/static/v1?message=message&color=blue don't do the same thing - they probably should
- https://img.shields.io/static/v1?label=label&message=message&color= and https://img.shields.io/static/v1?label=label&message=message don't do the same thing - they probably should
Those are both fix-able. I'd quite like to fix them first if we're going to make this the default, but it is not a big deal.
More of an issue is:
- Required query params are shown by default. Optional query params are hidden.
- If a param is required, you have to put a value in it before the "execute" button is press-able. You can't leave a required param empty.
The only thing that is actually required is message. Both label and color are optional. I feel like the optimal user experience is that label, message and color are all visible (in that order) when you first load the page, but I don't think we can do that while also allowing the label-less badge ( https://img.shields.io/static/v1?label=&message=message&color=blue ) and avoid overriding more frontend components (I really don't want to override more frontend components if we can avoid it).
One thing we can probably do is fiddle with the code we use to assemble the list of standard query params so that for this badge, label and color appear at the top of the list of optional params.
That feels like the least-worst solution I can think of, but I still don't love it.
This just confused me also. I missed some of the instructions at the top because the form looked really simple (I also had the vertical layout for the page, so by the time I scrolled to the form that information wasn't in view)
technically, you could ignore all the logic in the handler for this and just put something like this at the top, right?
badgeContent = (str => str.indexOf('-') >= 0 ? str : str + '-lightgrey')(badgeContent);