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.

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.

2 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.

1 Like

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
2 Likes