Badge definitions and awards

Yo @essentialrandom.bsky.social, let’s get the badge NSID published and cleaned up. The @fujocoded/atproto-badges - npmx lib looks fantastic.

Here are some live examples from the conf this year:

{
    "uri": "at://did:plc:3xewinw4wtimo2lqfy5fm5sw/community.lexicon.badge.definition/3mhrjpchjiq2l",
    "cid": "bafyreicwoikmvs5wsyxy3mbaooxhlp3vulry2aruaxgzsgwbdgkrtxp7fe",
    "value": {
        "name": "ATmosphere Conference 2026 Attendee",
        "$type": "community.lexicon.badge.definition",
        "createdAt": "2026-03-24T02:29:08.893Z",
        "description": "Flock'd around and geese'd out!"
    }
}
{
    "uri": "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/community.lexicon.badge.award/i1oZsZlPLWqt0",
    "cid": "bafyreigwuvfyakbkvacwnlknh76knauhdhtripqabpux5tuwgpqgztb5ge",
    "value": {
        "did": "did:plc:cbkjy5n7bk3ax2wplmtjofq2",
        "$type": "community.lexicon.badge.award",
        "badge": {
            "cid": "bafyreiakanlyj3ctebwvfq4ysrzuzq4wltbynzdwt72vzrcejb4gux4tiy",
            "uri": "at://did:plc:3xewinw4wtimo2lqfy5fm5sw/community.lexicon.badge.definition/3mhrjpchjiq2l"
        },
        "issued": "2026-03-29T23:37:20.918Z",
        "signatures": [
            {
                "key": "did:plc:3xewinw4wtimo2lqfy5fm5sw#attestations",
                "$type": "community.lexicon.attestations.signature",
                "signature": {
                    "$bytes": "..."
                }
            }
        ]
    }
}

Valid: https://badge.blue/at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/community.lexicon.badge.award/i1oZsZlPLWqt0

Definitions

  • name (string, should have max length, required)
  • createdAt (string, datetime format, required*)
  • description (string, should have max length, optional*)
  • image (definitionImage, optional)

definitionImage

  • ref (blob, required)
  • size (number, required)
  • mimeType (string, required)

Award

  • badge (strongRef to badge definition, required)
  • issued (string, datetime format, required)
  • did (string, DID format, deprecated*)
  • signatures (array of signature strong ref or inline objects, optional*)

Options

I think the badge definition looks good, no notes.

The “did” on badge awards can be deprecated imo. That was originally added before the signature spec evolved to include the required repository field. Leaving it causes no harm, so this is a very loosely held opinion.

1 Like

This is directly relevant for achievements in the game lexicons. It matches pretty much exactly with what I was originally thinking for the structure.

One thing I want to sort with achievements is showing progress. For example, “Collect 100 hats” is a great opportunity to show progress towards acquisition (e.g. 93/100 with a progress bar). I feel like this is probably up to the application, tho. Like I can use the existing game.hat records in the user’s repository to derive that progress without giving them a partial achievement or anything.

Anywho, I’ll likely leverage these lexicons for achievements in the Pentaract. I’m excited for them to be published.

3 Likes

Hello! Thank you for all your guidance on badges, excited to expand the library and put more badges into the world.

Your definitions make sense to me. Couple notes I wanted to add:

  1. Make did optional rather than deprecated. This leaves space for an award that’s on someone’s PDS (e.g. a community’s) but assigned to a different person within the entity (e.g. a member) by a third party (e.g. Discord giving out achievements).

  2. Have an “official” way to assign badges id: I was going to suggest a human-readable “id” field so we could internally distinguish/group badges, but I just realized that’s what the rkey is for…or is it? (see below)

On Human-readable badge ids:

Use case: these lexicons from the conference :backhand_index_pointing_down:

{
  "name": "Grand Goose Ambassador",
  "$type": "community.lexicon.badge.definition",
  "createdAt": "2026-03-30T09:19:25.897Z",
  "description": "Made at least 100 connections at ATmosphereConf2026"
}

{
  "name": "Serial Honk-nector",
  "$type": "community.lexicon.badge.definition",
  "createdAt": "2026-03-30T09:18:26.810Z",
  "description": "Made at least 75 connections at ATmosphereConf2026"
}

They are paired, but it’s not visible from their names…and I don’t want it to be! The purpose of badge names is to be funny, not “make sense”.

Now, I could/should have given their definitions rkeys like atconf2026_connections_rank_A and atconf2026_connections_rank_B rather than let them be random. That would be enough to remember what they were associated to (atconf2026) and that they are a series of connected badges each with increasing “rank” (connections_rank_X). But is that the right way?

To clarify: I’m not talking about formalizing a “badge series” concept. I just want to be able to order my badges by ID on whatever system and know if I do it alphabetically the same ones will end up together, or give them “codenames” I know will make sense to me (and add one more surface for a fun easter egg).

Is using rkeys this way “protocol-approved”? Do you see a reason rkeyd wouldn’t be enough to distinguish/group badges in human-readable ways? I’m thinking about remote/3rd party use cases most of all, where someone may not be able to choose their own rkey.

2 Likes

I think you can make lists of badges outside of the lexicon if you want such a thing.

1 Like

But then no badge displayer that’s not first party can use it to let you sort through your badges. You end up having to use the title for any type of identification that’s portable, while title should be a cosmetic element.

Again, maybe this is solved by setting a custom rkey, but generally badges having an internal identifier is useful. You really want a list of badges to keep similar ones grouped or to let you select them by a human-readable shorthand, and an id is a painless way to get this property. You could also think about it as name vs title: one is internal facing, and one is to display on the certificate. These are very different.

(And to clarify further, I actually keep having this problem looking up badges on pdsls, and I’d have this problem in any general badge display I make right now)

1 Like

Right, so there’s like a “group ID” included that resolves to somewhere else? Kind of feels like it is asking for the inclusion of this.

Same issue with grouping events, or really anything else. Either all the info remains in a group/upcoming space, and “points to” individual badges / events, or you have a “back link” in the badge record, of where to find the group name / description / etc

1 Like

I think having that level of context in the record key should be avoided. The first thing that comes to mind is if you need to change it (for whatever reason) then you effectively have a new record which breaks references.

Adding an optional field like “group” as a string sounds reasonable.

I could also see a group being a collection record of sorts:

{
    "$type": "community.lexicon.badge.group",
    "name": "Super Connecter",
    "definitions": [
        {
            "uri": "at://did:plc:3xewinw4wtimo2lqfy5fm5sw/community.lexicon.badge.definition/3mhrjpchjiq2l",
            "cid": "bafyreicwoikmvs5wsyxy3mbaooxhlp3vulry2aruaxgzsgwbdgkrtxp7fe"
        },
        {
            "uri": "at://did:plc:3xewinw4wtimo2lqfy5fm5sw/community.lexicon.badge.definition/3ml2f4mribsay",
            "cid": "bafy...p7fe"
        }
    ]
}

IMHO if we want to represent complex hierarchies of badges then that might be better suited for a sidecar or external application or use-specific record. Generally speaking I think of awards, certificates, merit badges, etc. as flat and I don’t think adding a ton of complexity to relationships between them is a good idea.

As I’ve said:

I do think a “badges group” Lexicon is a nice idea, but I don’t want to expand the scope of this. What I have is a more concrete, current need and a “bug-fix” (see proposal at the end).

The needs are:

  1. Allow people to assign an id so they can find/sort them by that (an internal identifier) and not what’s they want to be written on the badge (a cosmetic element)
  2. Creating a human readable slug that makes development, classification, and portability easier

The problem today

Two practical examples hopefully clarify what I mean:

Issue #1: links

Without id:

  • badge-viewer.org/badges/grand-goose-ambassador
  • badge-viewer.org/badges/serial-honk-nector

(??? what are these???)

With id:

  • badge-viewer.org/badges/atconf2026-connections-rank-A
  • badge-viewer.org/badges/atconf2026-connections-rank-B
  • badge-viewer.org/badges/atconf2027-another-badge-1
  • badge-viewer.org/badges/atconf2027-another-badge-2

(I know at a glance what they refer to, I know what I’m clicking on!)

Issue #2: Badge display widget

Without id

[grand-goose-ambassador] [in-the-way-badge-1] [in-the-way-badge-2] [in-the-way-badge-3] [serial-honk-nector]

(badges end up scattered, which I can tell you with surety badge-lovers will hate with a passion)

With id:

[atconf2026-connections-rank-A] [atconf2026-connections-rank-B] [in-the-way-badge-1] [in-the-way-badge-2] [in-the-way-badge-4] 

(related badges are together, all is right with the world, i will display this on my site)

Changing badges

For this particular point:

I am more likely to want to change the title and description than to want to change an identifier.

…and I realize now I may have invalidated some conference badges because I did change one name. Oops.

Proposal: appearance Lexicon

If I did break our conference badges renaming one, this is going to keep happening to others, and it’d be nice to address it now.

Likely, there needs to be a clearer separation of:

  1. Badge identifiers (certified, unchangeable)
  2. Badge cosmetic elements (potentially changeable)

Right now, name is being conflated as both identifier and display title, which causes the issue above.

Proposal: two separate lexicons community.lexicon.badge.definition (standardized internal definition), community.lexicon.badge.appearance (standardized external display). The first one is what get signed into a PDS.

{
  name: atconf2026-connections-rank-A,
  appearance: {
      uri: at://[conf-did]/community.lexicon.badge.appearance/whatever
      cid: (optional)
  },
  "$type": "community.lexicon.badge.definition",
  "createdAt": "2026-03-30T09:19:25.897Z",
  "description": "[General description that communicates intent, not meant to be creative]"
}
{
  "$type": "community.lexicon.badge.appearance",
  "title": "Grand Goose Ambassador"
  "description": "Made at least 100 connections at ATmosphereConf2026"
}

If you add a CID the badge cannot have its appearance changed, but a badge without a CID can be changed.

The reason I want this is:

  1. merging definition and cosmetic elements in one makes badges brittle, but…
  2. …without cosmetic elements (title/description) being standardized and portable, badges are useless for most cases I care about
3 Likes

As promised to @ngerakines.me, here’s a more formal proposal for “badge appearance”. This is not the complete proposal yet: I need to write the Lexicon definition and put down some thoughts about how to specify colors and images.

I’ll get to it by end of week (promise), but in the meantime I’m putting something down for initial comments.

Proposal: Appearance Lexicon for Badges

The Why

  • community.lexicon.badge.definition currently flattens the “name” field for both badge identification and badge display name
  • This makes it impossible to edit the display name of a badge without mutating the definition itself, causing all records to point to an older badge definition that no longer exists on the PDS and whose content cannot be verified.
  • While badges definitions shouldn’t change, there are many valid reasons for updating their appearance, including:
    • Typos
    • New Discriminant (e.g. Conference Attendee => 2026 Conference Attendee, should it gain a surprise second edition)
    • Getting money to commission someone a better badge
    • A kind soul offers to improve your alt text
    • “I thought of a funnier display name/description and now I’m mad”
  • This gets further compounded if we add necessary fields like:
    • Color
    • Icon
    • Image

Solution: Appearance lexicon for badges

community.lexicon.badge.appearance defines the user-facing presentation for a badge.

A badge definition represents the stable badge being awarded: it has a unique name and unchangeable description. Recipients can trust that the meaning of the badge they hold will not change under them.

Badge appearance represents how that badge should be displayed: visuals, “copy”, and link to learn more.

This separates two concerns that are currently conflated: stable identity and mutable presentation, what the badge is vs what the badge looks like. It allows for progressive enhancement and extensibility, which is particularly valuable for communities like fandom that use badges as self expression and not simple credentials. While this is a badge-specific proposal, this conflation has been a recurring issue trying to map some open standards onto fandom’s “way of (online) life”. It’d be good to name this as a pattern, and put out an example of how to do it differently.

Proposal

1. Update community.lexicon.badge.definition

A badge definition is stable for the lifetime of the badge. This already exists today.

Proposed Change

  • Add an (optional) reference to a community.lexicon.badge.appearance record describing how this badge looks
  • If a cid is present, the badge is only valid for that specific version of the appearance record (useful for when you want to avoid any tampering)
  • If no appearance is present, name and description can be a display fallback

Example:

{
  "$type": "community.lexicon.badge.definition",
  "name": "atconf2026-connections-rank-a",
  "createdAt": "2026-03-30T09:19:25.897Z",
  "description": "Awarded for making at least 100 connections at ATmosphereConf2026.",
  "appearance": {
    "uri": "at://did:plc:example/community.lexicon.badge.appearance/example",
    "cid": "bafyreicwoikmvs5wsyxy3mbaooxhlp3vulry2aruaxgzsgwbdgkrtxp7fe"
  }
}

Side proposal: doing the renaming of nameid in the same change. The current name does double duty as stable identifier and display string, and id for the identifier would make its purpose clear.

2. Create community.lexicon.badge.appearance

A badge appearance describes how a badge should be displayed.

Challenge: we want to support both types of badges:

  • Icon + colors badges, easy to create for people who just want to put a badge out without thinking too hard about it (I’m told they exist)

  • Image Badges, for the rest of us who miss the geocities era. Let’s take one of ours, for reference:
    Badge (1) copy

Fields:

  • title: display title for the badge.
  • displayDescription (optional): display description for the badge. Can be longer than original badge description. Falls back to community.lexicon.badge.definition#description if not present.
  • image (optional): visual representation of the badge. Like Bluesky’s, it has alt, image, and aspectRatio (but flattened).
  • icon (optional): an emoji or character or small image that represents the badge (e.g. :star:, ⌘, or an actual image). If not defined, falls back to first letter of the title. Personally, I’d allow 2-3 graphemes with the understanding that it may be cut to 1.
  • accentColor (optional): primary color of the badge (leaves door open for distinguishing background + foreground combinations in the future).
  • url (optional): where to go to learn more about the badge (can be an actual URL or an ATUri).

Important: image and icon/accentColor are not mutually exclusive. icon can be used to represent the image in space-constrained environments. But when possible implementers should prefer image and consider icon/accentColor a fallback

Examples

  1. Conference badge:
{
  "$type": "community.lexicon.badge.appearance",
  "title": "Grand Goose Ambassador",
  "icon": "🪿",
  "accentColor": "#fbbf24", // yellow,
  "url": "https://atmosphereconf.org/"
}

Description is absent, so it falls back to the one in the original badge.

  1. Badge (1) copy
{
  "$type": "community.lexicon.badge.appearance",
  "title": "Code Gitter",
  "displayDescription": "Some people would do anything for catboys, even learn version control.",
  "url": "https://www.fujoweb.dev/",
  "icon": "😼💻",
  "accentColor": "#e0b8a3", // Git's signature peach color
  "image": {
	  "blob": {
	    "$type": "blob",
	    "ref": {
	      "$link": "bafkreiexample"
	    },
	    "mimeType": "image/png",
	    "size": 12345
	  },
	  "alt": "FujoGuide's Git but he's nyancat, creating a rainbow as he dashes through the sky. Let's git coding is written to his right.",
      "aspectRatio": {"width": 88, "height": 31}
  }
}

[edit] To illustrate the difference between definition and appearance, here’s how a potential definition would look like. It has a more factual name and description, but where’s the fun in that?

{
  "$type": "community.lexicon.badge.definition",
  "name": "FujoGuide Issue 1 Buyer",
  "description": "Awarded to folks who bought FujoGuide Issue 1",
  "appearance": {
      "uri": "at://did:plc:example/community.lexicon.badge.appearance/example",
   }
 }

Things I’d like input on

It’s a few, so please do pick and choose if you have thoughts:

Original badge definitions:

  1. id vs name: is this a crusade only I care about and should relent on?
  2. Anyone in favor (or really against) having id + name so a badge fallback could still display both a title and description?

Badge appereance:

  1. Do people prefer tagline or displayDescription for the appearance copy field? tagline feels more true, but taglines are by design shorter and less descriptive. displayDescription is verbose, but it separates it from “description” in the badge.
  2. Any sanctioned way to leave space for “rich text descriptions” in the future? What about different formats for them? (yes, we could do the same thing as profile descriptions and render links + mentions, but that’s unstructured and doesn’t preserve DIDs for mentions).
  3. Should image always win over icon, or is it more an “alternate renderings according to context”? Personal preference would be image always wins over emoji-based icon, but icon in image can be used as a contextual display (so it can be displayed as a shorthand in e.g. small contexts).
  4. Would people be open to colors as an object rather than a single value (e.g. colors: {accent, background})? May I interest you in a richer definition of color than just hex?
  5. How are people supposed to extend this with their special display metadata (because they’ll want to)?

Let me know, and I’ll work on a more formal proposal in the next days!

1 Like

FYI @iame.li @natalie.sh

I’m glad this has been well thought through.

One thing that could happen is fake certificates that report ‘verified’ falsley, given implementations of that could be built by anyone.

You’d want a trusted certificate verifying authority, wouldn’t you? Like a webrowser company does.

I like the idea of using a field to create limited editions if that’s not beyond consideration

For example by numbering against a total “1/10”

Everything in atproto is signed by accounts using their identity keys.

So if you trust the issuer, you trust them.

Any account can issue badges, up to you if you trust the account.

How would that work if I pretend I’ve been awarded a rare badge from a trusted third-party issuer and I present a fake certificate as being verified?

You can’t fake being issued a badge - the issuer signs it.

Everyone is a “first party issuer” on atproto.

You can read more about badge blue design here:

And Attested Network Working Groups > Attested Network builds on this approach for payments.

I think for more serious badges the answer is that the crypto proof ‘verification’ is carried out on the local device, which I hadn’t considered.

1 Like

Yeah, any party can verify. All you need is the DID document to get the #atproto verification method and then the slice using com.atproto.sync.getRecord Documentation (EN) - Lexicon Garden.