lexicons#
lexicons are atproto's schema system. they define what records look like and what APIs accept.
NSIDs#
a Namespace ID identifies a lexicon:
fm.plyr.track
app.bsky.feed.post
com.atproto.repo.createRecord
format is reverse-DNS. the domain owner controls that namespace. this prevents collisions and makes ownership clear.
defining a lexicon#
{
"$type": "com.atproto.lexicon",
"id": "fm.plyr.track",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["title", "artist", "audioUrl", "createdAt"],
"properties": {
"title": {"type": "string"},
"artist": {"type": "string"},
"audioUrl": {"type": "string", "format": "uri"},
"album": {"type": "string"},
"duration": {"type": "integer"},
"createdAt": {"type": "string", "format": "datetime"}
}
}
}
}
}
from plyr.fm/lexicons/track.json
record keys#
- tid: timestamp-based ID. for records where users have many (tracks, likes, posts).
- literal:self: singleton. for records where users have one (profile).
"key": "tid" // generates 3jui7akfj2k2a
"key": "literal:self" // always "self"
knownValues#
extensible enums. the schema declares known values but validators won't reject unknown ones:
"listType": {
"type": "string",
"knownValues": ["album", "playlist", "liked"]
}
this allows schemas to evolve without breaking existing records. new values can be added; old clients just won't recognize them.
from plyr.fm list lexicon
namespace discipline#
plyr.fm uses environment-aware namespaces:
| environment | namespace |
|---|---|
| production | fm.plyr |
| staging | fm.plyr.stg |
| development | fm.plyr.dev |
never hardcode namespaces. configure via settings so dev/staging don't pollute production data.
important: don't reuse another app's lexicons even for similar concepts. plyr.fm defines fm.plyr.like rather than using app.bsky.feed.like. this maintains namespace isolation and avoids coupling to another app's schema evolution.
shared lexicons#
for true interoperability, multiple apps can agree on a common schema:
audio.ooo.track # shared schema for audio content
plyr.fm writes to audio.ooo.track (production) so other audio apps can read the same records. this follows the pattern at standard.site.
benefits:
- one schema for discovery, any app can read it
- content is portable - tracks live in your PDS, playable anywhere
- platform-specific features live as extensions, not forks
from plyr.fm shared audio lexicon research
schema evolution#
atproto schemas can only:
- add optional fields
- add new knownValues
you cannot:
- remove fields
- change required fields
- change field types
plan schemas carefully. once published, breaking changes aren't possible.
why this matters#
lexicons enable the "cooperative computing" model:
- apps agree on schemas → they can read each other's data
- namespace ownership → no collisions, clear responsibility
- extensibility → schemas evolve without breaking
- shared lexicons → true cross-app interoperability