networkrailbusinesssystems/badges
A Laravel package from Network Rail Business Systems for defining, managing, and rendering reusable badges. Use it to keep status labels and visual indicators consistent across your application’s Blade views and interfaces.
Install
composer require networkrailbusinesssystems/badges --dev
It's a build/readme tool, so --dev is usually right — you don't want badge generation in your production dependency graph. Laravel's package discovery should register the service provider automatically; if you're on an older skeleton, add NetworkRailBusinessSystems\Badges\BadgesServiceProvider::class to config/app.php.
Publish the config
php artisan vendor:publish --tag=badges-config
# or, if no tag is defined:
php artisan vendor:publish --provider="NetworkRailBusinessSystems\Badges\BadgesServiceProvider"
Where to look first, in order
README.md — the public API surface. This package has near-zero adoption, so the README is the spec; treat it as the source of truth over any guess about method names.config/badges.php — defaults for colours, style (flat / flat-square / plastic), output path, and any badge "sets" you can declare.src/ — find the main generator class and any Console/Commands directory. The command is what you'll actually wire into CI.resources/views/ — if badges are Blade-rendered SVGs, this tells you how customisable they are.First use case
Generate a single badge and drop it at the top of a package README:
use NetworkRailBusinessSystems\Badges\Badge;
Badge::make('build', 'passing')
->color('brightgreen')
->save(public_path('badges/build.svg'));
Then in README.md:

If the package ships an artisan command, prefer that over hand-rolled PHP — it will already handle paths, escaping, and writing:
php artisan badges:generate
Check php artisan list | grep badge after install to see what's actually registered.
Config-driven badge sets (the main day-to-day pattern). Rather than calling the generator in ad-hoc scripts, declare your badges once in config/badges.php and regenerate them all in one pass. This keeps badge labels, colours, and filenames in one place and makes the CI step a single command. Add a new badge by adding one array entry, not by touching a script.
Marker-delimited README injection. The cleanest workflow is to have the generator rewrite only a region of the README between HTML comment markers:
<!-- badges:start -->
<!-- badges:end -->
Anything between the markers is owned by the tool; everything outside is yours. This survives humans editing the README and avoids the "regenerate whole file and lose my prose" problem. If the package doesn't ship this, implement it yourself as a thin wrapper around the generator — it's ~20 lines and worth it.
CI step on release, not on every push. Badges for version, licence, and PHP constraint only change at release. Wire generation into your release workflow, commit the SVGs, and let GitHub serve them from the repo. Running it on every PR produces noisy diffs and merge conflicts on the generated files.
- name: Generate badges
run: php artisan badges:generate
- name: Commit
run: |
git config user.name "github-actions"
git config user.email "[email protected]"
git add badges/ README.md
git diff --staged --quiet || git commit -m "chore: refresh badges"
git push
Serve dynamically only when the value is actually dynamic. Static facts (licence, PHP version) should be committed SVGs. Genuinely dynamic values (test coverage, latest release) are better served from a route or a CI artifact than committed, otherwise you're committing on every build.
Testing. Assert on the generated SVG string, not on pixel output — check that the label text is present, the fill colour matches the config, and that user-supplied strings are XML-escaped. If the package exposes a facade, Facade::shouldReceive(...) in tests keeps you from hitting the filesystem.
Composition. If you have several packages in a monorepo, loop over a shared badge definition array and pass a per-package output directory. Keep the definitions in one file at the repo root so badges stay visually consistent across packages.
GitHub's image proxy caches aggressively. raw.githubusercontent.com URLs are proxied through camo and cached for a long time. If you regenerate a badge and the README still shows the old one, that's the cache, not your code. Cache-bust with a query string (?v=2) or, better, reference the badge via a blob/raw URL tied to a commit SHA if you need guaranteed freshness. Committing SVGs and viewing them after a push is usually fine; it's dynamic endpoints that bite you.
Escape everything user-supplied. Badge text lands inside an SVG <text> node. A project name containing &, <, or " will produce invalid SVG that renders as a broken image with no error message. Check whether the package escapes internally; if not, wrap your inputs. This is the single most common failure mode for badge generators.
Text width is the hard part of SVG badges. Shields-style badges size themselves to the label text. If the package hardcodes widths or uses a rough character-count heuristic, long labels will overflow the pill. Look for a width or padding option in the config before assuming it's broken.
File paths are resolved relative to different roots depending on whether you call the generator from a command, a test, or a queued job. Always pass absolute paths (public_path(), base_path(), storage_path()) rather than relative ones. In CI the working directory is the checkout root, which may differ from your local setup.
Publish tags may not exist. Small packages often ship a provider without $publishes tags. If vendor:publish --tag=badges-config silently does nothing, drop the tag and use --provider=. Then diff the published config against the package's own copy to confirm you got the current version.
Config caching in CI. If your pipeline runs php artisan config:cache before badge generation, changes to config/badges.php won't take effect until the cache is cleared. Put generation before the cache step, or config:clear first.
Colour accessibility. Bright green on white at badge size is borderline illegible. If these badges appear in public READMEs, stick to the conventional shields palette (brightgreen, blue, red, lightgrey) — deviating produces badges that look broken rather than bespoke.
Extension points to check for. Custom Blade view for the SVG (lets you add a logo or change the font without forking), a Badge::extend()-style registration hook, and any interface the generator type-hints — implementing that interface and rebinding it in a service provider is the least invasive way to customise output if the config doesn't expose what you need.
Pin the version. With zero dependents and a recent, fast-moving release history, the API can shift between minors. Commit composer.lock and pin ^x.y in require-dev rather than floating.
How can I help you explore Laravel packages today?