Rating all the things
All existing proposals are wrong because they are all right
any different applications use some version of rating things. Beacon bits let you rate a place. Bookhive lets you rate books. The list goes on.
The thing about ratings is there is a fixed set of mathematical archetypes for what a rating system can be. It is a finite space. A finite space can be defined.
I’ve been thinking a lot about various utility lexicons that aren’t necessarily meant to live as their own records, though they sure could. They’re just as useful embedded as a typed field within another lexicon. See for example the lexicon community locations schema used within a beacon bits check-in.
Ratings I believe fit into this role. Many many scenarios will make use of ratings. Although each of them will use different shaped reading systems for different purposes. Which brings us back to ratings as a finite space. There are infinitely many different rating systems and scales, but that variety is contained. We can define it.
What's in a rating?
Just 3 lexicons...
- the rating scale (what are the range of possible rating values)
- the rating prompt (what about the subject are you evaluating)
- the rating (your evaluation of the subject, given a particular scale and prompt)
These four distinct levels define the range of possible shapes for a rating scale. Why do we care to define the set space of possible rating scales? Freedom 🦅🇺🇲! Jokes aside, there is no such thing as One Rating System To Rule Them All. Any lexicon attempting to designate one is doomed for irrelevance. One of my design criteria here is that anyone can create a rating system. We should be able to encode existing rating frameworks, such as Likert or 5 stars, define specialized scales, like the iFixit Repairability Score, and come up with our own goofy scales, like a post quality tier list that ranges from Jerry to Paul.
Out of scope
Distinctly, these are not reviews. A review typically consists of a rating and an additional body of content about the experience of the subject, perhaps as a written article or a video. Thinking atmospherically, we can leave the jobs to be done of reviews out of this lexicon. Someone else can create their review lexicon for cheeses and use this lexicon set for the ratings portion of the review.
These records also don't carry any analysis or aggregation guidance. That is the role of an Appview to compute. The only field not focused on the bare minimum information for an effective rating is an optional documentation URI in the scale record, for further information about the scale and how to do relevant computations on its ratings.
How I expect this to be used
Any app would typically bake a rating system into their own lexicon that overall flow of setting a default for how ratings work within your app and schema I think still apply in your app account. You can define a rating system and create your prompts relevant to your app experiences. When you create records with ratings on your users behalf, you embed those defaults. Because I believe a central axis of the atmosphere is agency, we also get this lovely side effect where I can still use your lexicon, but with a different rating system. If I choose another scale my rating might not show up in your aggregation, but that’s ok. Aggregations should use internally consistent datasets. Perhaps another app that consumes and produces your lexicon chooses a different default rating system as a way to craft and differentiate their own experience. Maybe you’re The Verge and you create a silly assessment scale of the long-term viability of streaming surfaces based off of Verizon‘s failed Go90 streaming service. Now people can go and use that to rate streaming services or whatever else on the Go90 scale as officially cryptographically brought to you by @theverge.com. TierZoo the YouTube channel can create and use their official tier list for ranking species in the RPG that is life.
I think it’s important that the arena of creation here is the record and not the lexicon. There’s incredibly high friction to creating a new lexicon and a much smaller group of people who do so compared to the ones who might be the designers of rating system systems, and the raters of things. We can imagine nuanced ways of communicating our opinions.
This lexicon set provides a structurally comprehensive surface for publishing and using rating systems across pretty much any context in the atmosphere. I hope you like it.
Open questions:
- Are these four structures (nominal, ordinal, interval, ratio) comprehensive enough to afford many (all?) feasible rating systems? See Examples for a variety of applications
- Are there design changes you would want included in a published lexicon?
- Could the four scale types be defs within the scale schema?
- Nitpicks?
- Would you use this in your app and how?
- Other challenges or comments?
Please share your thoughts in the community forum.
A general purpose ratings lexicon
I took a stab at solving ratings for everyone forever. I’d love your thoughts.
Types
community.lexicon.rating.scale
{
"lexicon": 1,
"id": "community.lexicon.rating.scale",
"description": "Defines what makes a valid rating value for a scale — the value's shape and measurement level.",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["name", "code"],
"properties": {
"name": { "type": "string", "maxLength": 256 },
"description": { "type": "string", "maxLength": 1024 },
"documentation": { "type": "string", "format": "uri", "maxLength": 1024, "description": "Optional URI linking to further documentation about this scale (e.g. methodology, citations)." },
"langs": {
"type": "array",
"items": { "type": "string", "format": "language" },
"maxItems": 3,
"description": "BCP-47 tag(s) the name/description/option labels in this scale are written in."
},
"code": {
"type": "union",
"refs": [
"#nominal",
"#ordinal",
"#interval",
"#ratio"
]
},
"createdAt": { "type": "string", "format": "datetime" }
}
}
},
"nominal": {
"type": "object",
"required": ["options"],
"properties": {
"options": { "type": "array", "maxItems": 100, "items": { "type": "ref", "ref": "#nominalOption" } }
}
},
"nominalOption": {
"type": "object",
"required": ["label", "value"],
"properties": {
"label": { "type": "string", "maxLength": 128 },
"value": { "type": "string", "maxLength": 64 },
"description": { "type": "string", "maxLength": 256 },
"labelImage": {
"type": "object",
"description": "Optional image for this option (blob reference).",
"properties": {
"ref": { "type": "cid-link" },
"mimeType": { "type": "string" },
"size": { "type": "integer" }
}
}
}
},
"nominalValue": {
"type": "object",
"required": ["value"],
"properties": { "value": { "type": "string", "maxLength": 64 } }
},
"ordinal": {
"type": "object",
"required": ["options"],
"properties": {
"options": { "type": "array", "maxItems": 100, "items": { "type": "ref", "ref": "#ordinalOption" } }
}
},
"ordinalOption": {
"type": "object",
"required": ["label", "value"],
"properties": {
"label": { "type": "string", "maxLength": 128 },
"value": { "type": "integer", "description": "Rank. Leave gaps for future insertions." },
"description": { "type": "string", "maxLength": 256 },
"labelImage": {
"type": "object",
"description": "Optional image for this option (blob reference).",
"properties": {
"ref": { "type": "cid-link" },
"mimeType": { "type": "string" },
"size": { "type": "integer" }
}
}
}
},
"ordinalValue": {
"type": "object",
"required": ["value"],
"properties": { "value": { "type": "integer" } }
},
"interval": {
"type": "object",
"required": ["min", "max", "unitsPerWhole"],
"properties": {
"min": { "type": "integer" },
"max": { "type": "integer" },
"unitsPerWhole": { "type": "integer", "minimum": 1, "description": "Divide the stored integer by this to get the value in `unit`s. Any positive integer, not just a power of 10. Choose min/max directly in the smallest (or coarsest) legal increment — every integer between min and max is then a valid value, by construction." },
"unit": { "type": "string", "maxLength": 64 },
"minLabel": { "type": "string", "maxLength": 128, "description": "Optional label for the minimum value." },
"maxLabel": { "type": "string", "maxLength": 128, "description": "Optional label for the maximum value." },
"minLabelImage": {
"type": "object",
"description": "Optional image for the minimum endpoint (blob reference).",
"properties": {
"ref": { "type": "cid-link" },
"mimeType": { "type": "string" },
"size": { "type": "integer" }
}
},
"maxLabelImage": {
"type": "object",
"description": "Optional image for the maximum endpoint (blob reference).",
"properties": {
"ref": { "type": "cid-link" },
"mimeType": { "type": "string" },
"size": { "type": "integer" }
}
}
}
},
"intervalValue": {
"type": "object",
"required": ["value"],
"properties": { "value": { "type": "integer" } }
},
"ratio": {
"type": "object",
"required": ["min", "max", "unitsPerWhole"],
"properties": {
"min": { "type": "integer" },
"max": { "type": "integer" },
"unitsPerWhole": { "type": "integer", "minimum": 1, "description": "Divide the stored integer by this to get the value in `unit`s. Choose min/max directly in the smallest (or coarsest) legal increment so that every integer between min and max is then a valid value." },
"unit": { "type": "string", "maxLength": 64 },
"minLabel": { "type": "string", "maxLength": 128, "description": "Optional label for the minimum value (true absence)." },
"maxLabel": { "type": "string", "maxLength": 128, "description": "Optional label for the maximum value." },
"minLabelImage": {
"type": "object",
"description": "Optional image for the minimum endpoint (blob reference).",
"properties": {
"ref": { "type": "cid-link" },
"mimeType": { "type": "string" },
"size": { "type": "integer" }
}
},
"maxLabelImage": {
"type": "object",
"description": "Optional image for the maximum endpoint (blob reference).",
"properties": {
"ref": { "type": "cid-link" },
"mimeType": { "type": "string" },
"size": { "type": "integer" }
}
}
}
},
"ratioValue": {
"type": "object",
"required": ["value"],
"properties": { "value": { "type": "integer" } }
}
}
}community.lexicon.rating.rate
{
"lexicon": 1,
"id": "community.lexicon.rating.rate",
"description": "A rating of a subject against a canonical scale and prompt.",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["subject", "scale", "prompt", "value"],
"properties": {
"subject": {
"type": "union",
"description": "The thing being rated: either an ATProto record (via strongRef) or an external URI (website, document, etc).",
"refs": [
"com.atproto.repo.strongRef",
"#subjectUri"
]
},
"prompt": { "type": "ref", "ref": "com.atproto.repo.strongRef", "description": "Pointer to a community.lexicon.rating.prompt record." },
"scale": { "type": "ref", "ref": "com.atproto.repo.strongRef", "description": "Pointer to a canonical community.lexicon.rating.scale record." },
"value": {
"type": "union",
"description": "The chosen value, typed to match the referenced scale's code.",
"refs": [
"community.lexicon.rating.scale#nominalValue",
"community.lexicon.rating.scale#ordinalValue",
"community.lexicon.rating.scale#intervalValue",
"community.lexicon.rating.scale#ratioValue"
]
},
"createdAt": { "type": "string", "format": "datetime" }
}
}
},
"subjectUri": {
"type": "object",
"required": ["uri"],
"properties": {
"uri": { "type": "string", "format": "uri", "maxLength": 2048, "description": "A URI pointing to an external resource (website, document, etc)." }
}
}
}
}A consumer fetches the referenced scale's code, confirming the rating's value's $type matches and that it is within range before accepting and aggregating.
community.lexicon.rating.prompt
{
"lexicon": 1,
"id": "community.lexicon.rating.prompt",
"description": "A prompt or question that a rating answers, clarifying what aspect of a subject is being evaluated.",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["text"],
"properties": {
"text": { "type": "string", "maxLength": 1024, "description": "The prompt or question text." },
"langs": {
"type": "array",
"items": { "type": "string", "format": "language" },
"maxItems": 3,
"description": "BCP-47 tag(s) the prompt text is written in."
},
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
}
}Example Records
Nominal
Rate a recipe with its prominent flavor notes. Flavors have no implicit order or rank to them you’re just selecting from a list of choices. You can even rate the same recipe multiple times to tag with different flavors.
{
"$type": "community.lexicon.rating.scale",
"name": "Flavor Notes",
"langs": ["en"],
"code": {
"$type": "community.lexicon.rating.scale#nominal",
"options": [
{ "label": "Savory", "value": "savory" },
{ "label": "Sweet", "value": "sweet" },
{ "label": "Spicy", "value": "spicy" },
{ "label": "Umami", "value": "umami" },
{ "label": "Acidic", "value": "acidic" },
{ "label": "Bitter", "value": "bitter" }
]
}
}{
"$type": "community.lexicon.rating.prompt",
"text": "What are the dominant flavor notes in this dish?",
"langs": ["en"]
}{
"$type": "community.lexicon.rating.rate",
"subject": { "uri": "at://did:plc:6ayddqghxhciedbaofoxkcbs/io.kich.recipe.recipe/3mnneteylh2oq", "cid": "bafyRecipeCid" },
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/3mvtla5o734fe", "cid": "bafyPromptCid" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/3mvtlakrono6x", "cid": "bafyScaleCid" },
"value": { "$type": "community.lexicon.rating.scale#nominalValue", "value": "savory" },
"createdAt": "2026-09-15T19:30:00Z"
}Ordinal
Rank the subject with one of the listed options. There is a sequence and direction to these ranks, hence they are keyed to a numeric value. I suggest leaving some room between your indexes in case you add more options later. Ordinal scales have no defined distance between options. Your classic Likert scale would fit here.
{
"$type": "community.lexicon.rating.scale",
"name": "Character Tier List",
"langs": ["en"],
"code": {
"$type": "community.lexicon.rating.level.ordinal#code",
"options": [
{ "label": "D", "value": 0 },
{ "label": "C", "value": 10 },
{ "label": "B", "value": 20 },
{ "label": "A", "value": 30 },
{ "label": "S", "value": 40 }
]
}
}{
"$type": "community.lexicon.rating.prompt",
"text": "How would you rank this character in competitive viability?",
"langs": ["en"]
}{
"$type": "community.lexicon.rating.rate",
"subject": { "uri": "at://did:plc:game/character/roster-42", "cid": "bafy3" },
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/tier_prompt", "cid": "bafyA" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/character_tier_def", "cid": "bafy4" },
"value": { "$type": "community.lexicon.rating.level.ordinal#value", "value": 40 },
"createdAt": "2026-08-17T00:00:00Z"
}Interval
Now we get into our truly numeric scales. Interval scales sit on the number line. Zero may not be in your chosen range, it's just a reference point in the line. We also have a unitsPerWhole so that your min and max can represent the total resolution of your scale, with unitsPerWhole as a denominator. This gives you, for example, a 5-star scale with quarter-star increments. The below rating renders out to 4.25 ⭐.
{
"$type": "community.lexicon.rating.scale",
"name": "5-Star Rating",
"langs": ["en"],
"code": {
"$type": "community.lexicon.rating.level.interval#code",
"min": 1,
"max": 20,
"unitsPerWhole": 4,
"unit": "stars"
}
}{
"$type": "community.lexicon.rating.prompt",
"text": "How would you rate the food quality at this restaurant?",
"langs": ["en"]
}{
"$type": "community.lexicon.rating.rate",
"subject": { "uri": "at://did:plc:reviews/restaurant/3mvtnltrq2ld7", "cid": "bafy5" },
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/3mvtnmbqcpmct", "cid": "bafyB" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/3mvtnm2gklbd7", "cid": "bafy6" },
"value": { "$type": "community.lexicon.rating.level.interval#value", "value": 17 },
"createdAt": "2026-08-17T19:00:00Z"
}Ratio
Still numbers, but now zero really means an absence of quantity. An iFixit Repairability Score of 0 means the device is not repairable at all.
{
"$type": "community.lexicon.rating.scale",
"name": "Repairability Score",
"langs": ["en"],
"documentation": "https://www.ifixit.com/Right-to-Repair/Repairability",
"code": {
"$type": "community.lexicon.rating.level.ratio#code",
"min": 0,
"max": 20,
"unitsPerWhole": 2,
"unit": "score"
}
}{
"$type": "community.lexicon.rating.prompt",
"text": "How repairable is this device according to the iFixit standard?",
"langs": ["en"]
}{
"$type": "community.lexicon.rating.rate",
"subject": { "uri": "at://did:plc:device-db/electronics/smartphone/pixel-7", "cid": "bafy7" },
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/3mvtnsxttb3o4", "cid": "bafyC" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/3mvtntiwchwrw", "cid": "bafy8" },
"value": { "$type": "community.lexicon.rating.level.ratio#value", "value": 15 },
"createdAt": "2026-08-17T11:00:00Z"
}15 / 2 = 7.5. Every integer 0–20 is a valid value.
Is/Not
Now that we've covered the basic four types, I'll throw in some other fun rating systems that have been thrown around. Here's Is/Not using our new lexicon set.
Scale Definition
{
"$type": "community.lexicon.rating.scale",
"name": "Is/Not Scale",
"langs": ["en"],
"description": "A scale for lightweight micro-reviews expressing agreement or disagreement with an adjective.",
"code": {
"$type": "community.lexicon.rating.level.ordinal#code",
"options": [
{ "label": "Is really not", "value": -20 },
{ "label": "Is not", "value": -10 },
{ "label": "Neutral", "value": 0 },
{ "label": "Is", "value": 10 },
{ "label": "Is really", "value": 20 }
]
}
}Adjective Prompt
{
"$type": "community.lexicon.rating.prompt",
"text": "Is well-paced?",
"langs": ["en"]
}Rating
{
"$type": "community.lexicon.rating.rate",
"subject": {
"$type": "community.lexicon.rating.rate#subjectUri",
"uri": "https://example.com/article"
},
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/3mvtntqa7ipxf", "cid": "bafyA" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/3mvto2jbvsdfy", "cid": "bafyB" },
"value": {
"$type": "community.lexicon.rating.level.ordinal#value",
"value": 10
},
"createdAt": "2026-09-15T00:00:00Z"
}Netflix Thumbs
{
"$type": "community.lexicon.rating.scale",
"name": "Netflix Thumbs",
"langs": ["en"],
"code": {
"$type": "community.lexicon.rating.scale#ordinal",
"options": [
{ "label": "👎", "value": 0, "description": "Not for me" },
{ "label": "👍", "value": 10, "description": "Thumbs up" },
{ "label": "👍👍", "value": 20, "description": "Double thumbs up" }
]
}
}{
"$type": "community.lexicon.rating.prompt",
"text": "How would you rate this content?",
"langs": ["en"]
}{
"$type": "community.lexicon.rating.rate",
"subject": { "uri": "at://did:web:gamesgamesgamesgames.games/games.gamesgamesgamesgames.game/3moh3b2i2t72x", "cid": "bafyreiezqoaknjv7b3ijq653waoiwqgg2imewkgtxychjvobx2xcjojrcu" },
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/3mvtp7vfiejrp", "cid": "bafyNetflixPrompt" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/3mvtpa6v63qvw", "cid": "bafyNetflixScale" },
"value": { "$type": "community.lexicon.rating.scale#ordinalValue", "value": 20 },
"createdAt": "2026-09-15T19:30:00Z"
}Poast Quality
{
"$type": "community.lexicon.rating.scale",
"name": "Post Quality",
"langs": ["en"],
"code": {
"$type": "community.lexicon.rating.scale#interval",
"min": 1,
"max": 100,
"unitsPerWhole": 1,
"unit": "quality",
"minLabel": "@jsalterego.bsky.social",
"maxLabel": "@goose.art",
"minLabelImage": {
"ref": "bafybeijbwn7r7qkdpn3l5z4k4n2m1j5h4g3f2e1d0c9b8a7z6y5x4w3v2u1t0",
"mimeType": "image/jpeg",
"size": 45234
},
"maxLabelImage": {
"ref": "bafyreidxzrujdbqcq7fcv4kwqvgnqphvfvwxmg5e7z4tqvvp3dwlrsfgee",
"mimeType": "image/jpeg",
"size": 52891
}
}
}{
"$type": "community.lexicon.rating.prompt",
"text": "How would you rate the quality of this post?",
"langs": ["en"]
}{
"$type": "community.lexicon.rating.rate",
"subject": { "uri": "at://did:plc:rnpkyqnmsw4ipey6eotbdnnf/app.bsky.feed.post/3mvtfmq5utc2k", "cid": "bafyreihtjk3auaxpjb24vcgsrdvqmnplgl65uouupqy6me2akmetyyv4ti" },
"prompt": { "uri": "at://did:plc:curator/community.lexicon.rating.prompt/3mvtpnfuelcxm", "cid": "bafyPostQualityPrompt" },
"scale": { "uri": "at://did:plc:curator/community.lexicon.rating.scale/3mvtpnp5nyq5c", "cid": "bafyPostQualityScale" },
"value": { "$type": "community.lexicon.rating.scale#intervalValue", "value": 75 },
"createdAt": "2026-09-15T19:30:00Z"
}Acknowledgements
Thanks to @ngerakines.me and @doriantaylor.com for giving feedback and answering questions as I designed these lexicons. And special shoutout to the @lexicon.garden MCP.
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.