# For the Badge: a self-hosted SVG badge generator built as a Nuxt app on Cloudflare

> An MIT licensed Nuxt 4 application that renders developer badges as SVG, stores collections in Cloudflare D1 and KV, and ships with referral codes and background removal in its dependency list. Suited to running your own badge service on Cloudflare, and unsuitable for anyone who wants to self host on Node or Postgres.

**andrewsbarbaro/for-the-badge** — It all started because of an obsession with two words: “build passing”. It all ended with this: badges, for badges’ sake.

- Repository: https://github.com/andrewsbarbaro/for-the-badge
- Website: https://forthebadge.com
- Stars: 1,814 · Forks: 142
- Language: Vue
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/andrewsbarbaro-for-the-badge

## What the application does, and what it stores

For the Badge is a web application for producing the flat rectangular labels developers paste into a README. It advertises four things: a custom badge creator for text, colors and icons, a browsable library of ready-made badges, user accounts that save and manage collections, and sharing by copying a badge URL or downloading an image. The live site is forthebadge.com, and the repository itself is MIT licensed.

The interesting part is that the badge is not an uploaded asset. A badge is addressed by a URL and rendered on demand, which is what makes collections shareable and what makes the referral short IDs possible. Saving a badge means writing a row that encodes its parameters, and displaying it means regenerating the SVG from those parameters on each request.

## The whole stack sits on Cloudflare, which is the main constraint

The tech stack section is short and it dictates everything else. The framework is Nuxt 4 on Vue 3. Deployment is Cloudflare Workers through NuxtHub. The database is Cloudflare D1, which is SQLite, and cache or general key value storage is Cloudflare KV. Drizzle ORM is the database layer, nuxt-auth-utils handles authentication, and nuxt-security is listed under security.

Three consequences follow. There is no Postgres path and no filesystem persistence, so the usual self hosting story of a Node process with a database URL does not apply. Running it elsewhere would mean replacing the binding layer, not just pointing an environment variable at another host. And because Workers is the target, the Wrangler CLI is listed as a prerequisite rather than an optional convenience.

## Getting it running locally takes Corepack, Yarn and an env file

The prerequisites are Node.js 22 or newer, Yarn obtained through Corepack, and the Wrangler CLI for Cloudflare work. The installation sequence the README gives is:

```bash
# Enable Corepack for Yarn
corepack enable

# Clone the repository
git clone https://github.com/forthebadge/for-the-badge.git
cd for-the-badge

# Install dependencies
yarn install

# Copy environment variables
cp .env.example .env
```

You then fill in .env before anything else works, because session handling reads its secret at startup. The development server is:

```bash
# Start development server
yarn dev
```

and it answers on http://localhost:3000. Note that yarn install triggers a postinstall hook running nuxt prepare, so the project is not usable before that finishes.

## Five secrets gate sessions, accounts and stored badges

.env.example marks five variables as required, and each maps to a distinct subsystem rather than being redundant copies of the same value.

```bash
NUXT_SESSION_PASSWORD=your-secure-session-password-here
PASSWORD_PEPPER=your-base64-encoded-pepper
ACCOUNT_HMAC_SECRET=your-base64-encoded-hmac-secret
BADGE_ENCRYPTION_KEY=your-base64-encoded-encryption-key
REFERRAL_SQIDS_ALPHABET=your-58-char-unique-alphabet
```

NUXT_SESSION_PASSWORD is a random string of 32 characters or more for session encryption, and the sample file notes it is for nuxt-auth-utils. PASSWORD_PEPPER and ACCOUNT_HMAC_SECRET are base64 encoded. BADGE_ENCRYPTION_KEY is base64 encoded and 32 bytes, which is the AES key size, so badge data is stored encrypted rather than in plain text. REFERRAL_SQIDS_ALPHABET defines 58 unique characters for the short referral IDs.

That last one is easy to get wrong: an alphabet that is not exactly 58 unique characters changes the ID space and can produce collisions or short IDs that the decoding side cannot resolve.

## Provisioning D1 and KV with Wrangler before the first build

Cloudflare resources are created with the Wrangler CLI, in this order: a D1 database, then a KV namespace, then IDs written into wrangler.jsonc, then the secrets pushed to the Worker.

```bash
# Create a D1 database:
wrangler d1 create forthebadge

# Create a KV namespace:
wrangler kv:namespace create KV
```

Step three is manual. You copy the identifiers from the Wrangler output into wrangler.jsonc yourself, and there is no script in the repository listed to do it. Then the five secrets are set one at a time:

```bash
wrangler secret put NUXT_SESSION_PASSWORD
wrangler secret put PASSWORD_PEPPER
wrangler secret put ACCOUNT_HMAC_SECRET
wrangler secret put BADGE_ENCRYPTION_KEY
wrangler secret put REFERRAL_SQIDS_ALPHABET
```

## The build runs a generated badge manifest before Nuxt compiles

package.json names the package forthebadge at version 4.0.0, declares yarn@4.9.2 as its packageManager, and sets type to module. The script graph has one entry worth noticing. A prebuild hook runs tsx scripts/generate-badges-manifest.ts, and only then does build call nuxt build:

```json
"prebuild": "tsx scripts/generate-badges-manifest.ts",
"build": "nuxt build",
```

The manifest step is how the pre-made badge library reaches the app. Because it is a lifecycle hook rather than a manual command, editing or adding badges means rerunning yarn build, and a failure in the generator blocks the build before Nuxt is even invoked. The repository layout supports that pipeline: app/, components/, server/, shared/, public/ and scripts/ at the top level, with tests/ and vitest.config.ts for the test suite.

## Two dependencies point past the badge generator

The dependency list contains @imgly/background-removal at ^1.7.0. That is a substantial library to carry in a badge service, and it points at image handling beyond rendering flat SVG labels. It also implies a browser side workload that has nothing to do with the D1 and KV persistence path.

A second signal sits in the scripts: stripe listens on a webhook path at /api/_webhooks/_stripe-b7e595af. Neither of these is explained in the README, which covers badges, accounts and sharing and then stops. If you are evaluating the project for its licensing surface, both matter: the code is MIT, but the image library it pulls in carries its own terms, and anyone deploying the Stripe webhook inherits a billing surface the documentation does not describe.

## Testing and security checks are wired as yarn scripts

Development tooling is unusually complete for a project of this size. Tests run through Vitest, configured by vitest.config.ts, and the README lists yarn test and yarn test:coverage. package.json also defines yarn test:watch, yarn test:ui, yarn coverage:open, which pipes the coverage report through start, and yarn coverage:clean, which removes the coverage directory with rimraf. 

Security checks are composed rather than single. yarn security:check runs lockfile-lint against yarn.lock restricted to the yarn host and requiring HTTPS, then runs yarn audit:

```json
"security:lockfile": "lockfile-lint --path yarn.lock --allowed-hosts yarn --validate-https",
"security:check": "yarn security:lockfile && yarn security:audit"
```

Restricting the lockfile to a single host is a deliberate supply chain constraint, and it will fail any pull request that adds a dependency resolved from elsewhere.

## Maintenance state and the limits of what the README covers

The last push was on 2026-09-13 and the repository publishes no GitHub releases, so there is no changelog to read against and no tag to pin. Version 4.0.0 in package.json is the only version signal available, and combined with the Nuxt 4 and NuxtHub stack it implies a recent framework migration rather than an incremental release train.

Contributions are handled through CONTRIBUTING.md, code of conduct through CODE_OF_CONDUCT.md, and security reports through SECURITY.md, all present in the repository. Funding runs through Open Collective.

What the README does not cover is the part that would decide a self host. There is no documented schema for the D1 tables, no migration instructions beyond the yarn db:generate entry for drizzle-kit, and no explanation of the referral ID system beyond the alphabet variable. Reading drizzle.config.js and the server/ directory is the only way to see what the encrypted badge rows look like.

## Conclusion

Adopt For the Badge if you want to run your own badge service on Cloudflare Workers and you are comfortable provisioning D1 and KV namespaces through Wrangler. Do not adopt it if you need a Node or Postgres deployment, because the persistence layer is Cloudflare D1 (SQLite) reached through Drizzle ORM and the runtime target is Workers via NuxtHub. Verify first that the five required environment variables in .env.example are set, that wrangler.jsonc carries your database and KV IDs, and that the badge manifest generated by the prebuild script runs cleanly under yarn build.

## FAQ

### How do I set up For the Badge locally?

Enable Corepack, clone https://github.com/forthebadge/for-the-badge.git, run yarn install, copy .env.example to .env and fill in the five required secrets, then start the development server with yarn dev on http://localhost:3000.

### Which environment variables does For the Badge require?

The five required ones are NUXT_SESSION_PASSWORD, PASSWORD_PEPPER, ACCOUNT_HMAC_SECRET, BADGE_ENCRYPTION_KEY and REFERRAL_SQIDS_ALPHABET. BADGE_ENCRYPTION_KEY must be base64 encoded and 32 bytes, and REFERRAL_SQIDS_ALPHABET needs 58 unique characters.

### Can I run For the Badge without Cloudflare?

The README describes Cloudflare Workers through NuxtHub as the deployment target, with Cloudflare D1 as the database and Cloudflare KV for storage. No alternative host or database is documented, so a non Cloudflare deployment would mean replacing that layer rather than changing a setting.

### How do I create the database and storage for For the Badge?

Run wrangler d1 create forthebadge and wrangler kv:namespace create KV, then copy the returned identifiers into wrangler.jsonc yourself. The README does not describe a script that does this step for you.

### What licence is For the Badge released under?

The project is licensed under the MIT License, as stated in the README and confirmed by the license field in package.json.

## Sources

- [andrewsbarbaro/for-the-badge on GitHub](https://github.com/andrewsbarbaro/for-the-badge)
- [Issues](https://github.com/andrewsbarbaro/for-the-badge/issues)
- [License: MIT](https://github.com/andrewsbarbaro/for-the-badge/blob/master/LICENSE)
- [Project website](https://forthebadge.com)
- [README](https://github.com/andrewsbarbaro/for-the-badge/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/andrewsbarbaro-for-the-badge
