Some thoughts on extending lexicons
over art by Declan Chidlow on Unsplash
Today, the wonderful folks over at @eurosky.social and @modalfoundation.eurosky.social revealed mu.social as part of their little week of announcements, along side their own relay and PLC mirror (see: What the hell is an atmosphere anyway, by @pds.dad )
The gist is that it’s a Bluesky client that builds upon the foundation of social-app, which is the internal name of the Bluesky mobile and web apps, adding some quality of life improvements and features, like post editing and a dedicated "news" tab for quick skimming through posts of articles made by media institutions based on the topics and accounts you choose, separate from follows.
I am going to only focus on the editing feature.
But first, a quick ATproto crash course
The Authenticated Transfer Protocol, or ATP, or ATproto, or AT Protocol, or however you want to spell it, is a series of rules and standards that allow applications to store all kinds of data in a decentralized manner, while allowing applications to aggregate it and display it to the user.
It has 3 main components or "layers", as I like to call them:
- The AppView
- The Relay
- The PDS, or Personal Data Service
Data is stored in the form of CBOR-encoded JSON inside the PDS, which then gets broadcasted to the relay and aggregated by the AppView.
So for example, if I made a Bluesky post, it would first be made on the PDS, then sent to, for example, Eurosky's relay, and ingested by Bluesky to display it to me, or it could also be ingested by Blacksky to show it on their own AppView.
So, we have a way to share, store, and view data all across the network. But how can we know that the data we get is exactly what we are expecting?
Lexicons
Think of them as "schematics".
They are a type of JSON document that declares a series of fields that a record— which is what the documents inside of our PDS are called— may have, their types, which of them are required and may also include descriptions for developers.
As an example, here is what the space.bunniesin.micro.log lexicon looks like:
{
"id": "space.bunniesin.micro.log",
"defs": {
"main": {
"key": "tid",
"type": "record",
"record": {
"type": "object",
"required": [
"content",
"createdAt"
],
"properties": {
"content": {
"type": "string",
"description": "The primary log content"
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this log was created"
},
"blueskyPost": {
"ref": "com.atproto.repo.strongRef",
"type": "ref",
"description": "Reference to the cross-posted version of the log, unavailable if content exceeds the lexicon's maximum length"
}
}
},
"description": "Simple microblogging lexicon"
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}Quite a mouthful, huh?
It boils down to 3 different fields:
- The log's content, which has an arbitrary length between 0 and infinity (as long as you don't reach the PDS' record size limit)
- The date it was created, which must be a properly formatted date
- And optionally, a strong reference to a Bluesky post
These are published by an owner, in this case, me!
We don't need to get into the details of publishing a lexicon right now, but in summary, to publish a lexicon you need to point the namespace to the account the lexicon is going to be hosted at, and then you create a com.atproto.lexicon.schema record that contains, well, the schema.
Once you publish the lexicon, you can update it all you want, since you are the sole owner of said lexicon. But what happens if another entity, let's say... Eurosky, wants to extend that lexicon to add a new feature to their app?
Extending other people's lexicons
Let's first check what a regular Bluesky post looks like, so we can spot the before and after changes later.
{
"text": "test post",
"$type": "app.bsky.feed.post",
"langs": [
"en"
],
"createdAt": "2026-06-11T18:40:51.157Z"
} Now, let's edit this post using Mu's edit feature.
{
"text": "edited test post!",
"$type": "app.bsky.feed.post",
"langs": [
"en"
],
"createdAt": "2026-06-11T18:40:51.157Z",
+ "updatedAt": "2026-06-11T18:42:45.055Z",
+ "originalText": "test post"
}Aha! It changed!
But... is this valid?
A quick look through bluesky's Post lexicon tells us that it shouldn't be valid, because there is no updatedAt or originalText fields.
But I can clearly see the edited post on both Bluesky and Mu! What is going on?????
Technically speaking, the record is valid, even though it has fields that aren't inside the lexicon definition. This is because records are open unions, which means that anything goes, unless a field that is marked as required is provided and is missing and if there are fields inside of the record are of the same name as the ones in the lexicon have different types.
So I can't create a post that has more than 8 tags, or a post that exceeds 300 characters, and have it display on Bluesky, because the post's record fields do not match their definition inside the schema.
However, I can create a record that has an extra field called, for instance, via, that tells me from which app a post was created, because it is not defined in the schema.
This is by no means recommended, and the AT Protocol documentation has the following to say on the matter:
Third parties can technically insert any additional fields they want into data. This is not the recommended way to extend applications, but it is not specifically disallowed.
And their solution is to have a "sidecar" lexicon, that has a reference to the post.
This is because if a field is added to the lexicon definition, and it has the same name as your custom field but the types don't match, the records will suddenly be invalid!
Which is something that has happened before with the introduction of pinned posts.
Lexicons, Pinned Posts, and Interoperability - AT Protocol
A discussion of Lexicons, schema extensions, and lessons learned from the pinned posts feature collision.
So why is Mu doing this?
Honestly? No idea.
Especially since they seem to be already using Microcosm's constellation service, which allows fetching backlinks an at:// URI! (for verifications)
One idea that I had while drafting this was having a lexicon that contained the original text, a reference to the original post, and a CID (Content Identifier), but I digress.
I've only recently started building my own Atmospheric app, and it's been pretty fun to delve into some of the internals of other applications, especially because I can finally understand why certain parts of the stack are built the way they are, and appreciate them even more than I currently do.
However, It's not my area of expertise, and I trust that they must have a good reason for doing this.
I just hope that we don't get another "pinned post" situation happening when Bluesky solidifies their thoughts of the post editing feature.
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.