Hello! I wanted to start a general discussion about Lexicon extensions and sidecar records, brought up by the need to connect records, web pages and apps.
For reference:
A sidecar record is a special-by-convention (yet regular) record that holds additional information about another one. For example, a sidecar record for an
app.bsky.feed.postrecord may hold additional information about who’s allowed to reply to the post.
The mechanism is extensively used but AFAIK not fully documented. So to put everyone on the same page, I did some research on patterns out there and some thinking on pros/cons. I tried to be objective, but feel free to point out what I may be missing ![]()
These patterns can overlap, and some examples may be better described as related records than sidecars. But nothing like being wrong on the internet helps standardize a term!
At the end, I’m going to talk about two types of extension systems I’d like input on, one for sidecars, and one within records.
Contents
- Sidecar Pattern #1: Keys derived from the subject
- Sidecar Pattern #2: Explicit reference in records
- Sidecar Pattern #3: Record metadata object keyed by the collection’s NSID (proposed)
- An alternative (#4): Namespaced, schema-defined extension fields embedded in the record
- Open questions
Sidecar Pattern #1: Keys derived from the subject
This covers:
- #A: Reusing the subject’s record key in another collection, e.g.
app.bsky.feed.threadgate[definition, example record] ⇒ the Lexicon explicitly requires the threadgate to share the root post’s record key and repository - #B: Deriving a key from the subject’s full AT URI, e.g. social.coves.community.acceptance [definition, example record] ⇒ the Lexicon specifies a an hash of the subject URI
In this pattern, locating a sidecar from its subject depends on a convention. An app must already know which collection and repository to look in, and how to calculate the key.
To help developers understand the relationship:
- Sidecar records may feature a link back to the subject
- The sidecar’s Lexicon description may document how to map sidecar ⇔ subject
Strengths/weaknesses:
Once the convention is known, an app can fetch a sidecar directly
Metadata can be updated without giving write access to the subject record
Metadata updates don’t create a new CID for the subject record
Sidecars can support independently owned metadata by deriving keys from the full AtUri
Apps must know each sidecar’s collection, repository rules, and key derivation to look for it
Changing the subject record’s key means migrating all sidecars or having an explicit policy for handling old associations
Reusing only the subject’s record keymay collide when different subject collections use the same key (e.g.selfvsselfon two separate Lexicons, avoided by #B)
Sidecar Pattern #2: Explicit reference in records
This covers:
- #A: From the sidecar to the subject, e.g.
tech.transparencia.news.enrichment[definition, example record] ⇒ the Lexicon defines anarticlefield referencing the subject’s URI and CID, alongside additional metadata - #B: From the subject to the sidecar, e.g.
place.atwork.endorsement[definition, example subject, referenced proof] ⇒ the Lexicon defines asignaturesarray containingAtUri+CIDreferences to separate proof records attesting to the endorsement’s content
In this pattern, the record stores an explicit reference, while the Lexicon defines what that reference means. This can also be combined with Pattern #1, such as by reusing the subject’s key.
To help developers understand the relationship:
- The reference points to the target’s repository, collection, and record key, which means:
- Subject ⇒ sidecar: an app can directly discover and fetch the referenced record.
- Sidecar ⇒ subject: finding sidecars that point to a record requires listing and filtering within repositories or maintaining an index
- The referencing field’s Lexicon description may document the semantics of the relationship
Strengths/weaknesses:
Differentiates subjects with the same key across both collections or repositories
Subject ⇒ sidecar: an app reading the subject can locate the linked metadata directly
Sidecar ⇒ subject: adding metadata does not require permission to edit the the subject (no CIDchange)
Sidecar ⇒ subject: multiple apps can attach their own metadata
Sidecar ⇒ subject: discovering sidecars requires a separate lookup mechanism (and potential enumeration, unless #1 is added)
Subject ⇒ sidecar: attaching or changing the pointer requires write access to the subject and changes its CID
Moving or deleting the referenced record can leave dangling references
A reference containing a CIDidentifies a specific version, which requires write permissions to the subject to update
Sidecar Pattern #3: Record metadata object keyed by the collection’s NSID (proposed)
I couldn’t find this pattern implemented, so this counts as a “proposal” of sorts. The most similar pattern I could find is:
- Metadata or configuration for a specific collection or XRPC method, e.g.
at.inlay.endpoint[definition, example record] ⇒ the key is an XRPC method’s NSID, and itsdidfield identifies the service implementing that method.
…which can then be turned into a sidecar record keyed by collection with record key references inside the record.
For example, in the example from the original forum post at://alice.example/community.lexicon.meta.links/app.bsky.feed.post could contain all the reverse links to app.bsky.feed.posts.
{
"$type": "community.lexicon.meta.links",
"342hjkfsd": {
"links": [
{
"href": "https://alice.example/posts/hello",
"rel": "canonical"
}
]
},
"342hjkxyz": {
"links": [
{
"href": "https://alice.example/posts/another",
"rel": "canonical"
}
]
}
}
In this pattern, locating metadata depends on both the subject’s collection and its record key. An app fetches the sidecar record keyed by the collection’s NSID, then reads the entry matching the subject’s record key.
For this example, the convention would be that the sidecar describes subjects in its own repository. So the 342hjkfsd entry refers to at://alice.example/app.bsky.feed.post/342hjkfsd. Metadata for place.stream.livestream/342hjkfsd would live in a separate sidecar record, keyed by place.stream.livestream.
To help developers understand the relationship:
- The sidecar’s Lexicon description would document how the data is meant to be interpreted
- Each entry would have a defined metadata structure (see the
linksarray above), although unions may be accepted - Apps would still need to know which sidecar collections to query and their conventions. Known extension types can be fetched for with a single lookup.
Strengths/weaknesses:
When the convention is known, an app can locate metadata without an index or enumerating records
Within a known sidecar collection, record keys immediately identify whether a subject collections has entries
Can differentiate subjects with the same record key across collections (e.g. selfrecords)
Metadata updates do not modify the subject or change its CID
One fetch retrieves metadata for multiple subjects in the collection
Current repository permissions cannot restrict an app to one target collection’s mapping
The mapping grows with the number of subjects and can exceed record-size limits
Updating one entry replaces the containing record and changes its CID
Concurrent writers updating different entries must coordinate to avoid overwriting each other’s changes
Renaming or deleting a subject requires updating its entry
An alternative (#4): Namespaced, schema-defined extension fields embedded in the record
The other approach I’d like input on is an alternative to sidecar records that puts additional information inside the subject record, in a field named after the Lexicon defining its contents.
Both atmo.rsvp events and BridgyFed posts already add metadata inside records, so embedding extensions isn’t new. What I’m proposing is a shared way to name, document, and validate those extensions that avoids the risk of field-name collisions—one concern raised by [the spec’s recommendation]( https://atproto.com/specs/lexicon#authority-and-control ) against adding additional fields.
This would be a shared convention:
- The field name is the extension Lexicon’s
NSID(possibly with a prefix, here I usedlex:) - Its value conforms to the definition of that Lexicon
- The Lexicon documents what the extension means when attached to another record
For example, a hypothetical extension to an app.bsky.feed.post could be:
{
"$type": "app.bsky.feed.post",
"text": "Hello from my website!",
"createdAt": "2026-10-18T12:00:00Z",
"lex:community.lexicon.meta.links": {
// this is optional, so we can't rely on it
// without an associated special key, it also creates collision risks
// "$type": "community.lexicon.meta.links",
"links": [
{
"href": "https://alice.example/posts/hello",
"rel": "canonical"
}
]
}
}
Here, community.lexicon.meta.links (preceded by lex:) would mean that the field is described by the Lexicon community.lexicon.meta.links. The post’s own $type remains app.bsky.feed.post.
With this method, the extension travels with the record while telling developers where to find its definition. An app can notice that the additional data exists and identify its schema without already knowing which sidecar collection or repository to query.
Strengths/weaknesses:
An app (or app dev) reading the subject record can see that extension data exists
Embedded metadata requires no additional record lookup
The association is unambiguous, including across different collections
Different record types can reuse the same extension definition
Adding or updating metadata requires permission to edit the subject
Validating the subject’s Lexicon does not automatically validate undeclared extensions
Every metadata change changes the subject’s CID
Extensions increase the subject record’s size
For larger or independently updated metadata, the extension could instead contain an AtUri pointing to a separate record.
Open questions
- The obvious one: are there existing approaches I’ve missed, or tradeoffs I’m overlooking?
- For sidecars: is there interest in standardizing how to declare what they extend and how to locate the associated records?
- For embedded extensions: would this shared naming and validation convention be useful? Are there other patterns people would prefer?
No pattern here fits every use case. Regardless, I’d still like apps to have a shared way to discover extensions and find their definitions without having to know about each one in advance, and without competing over the names and meanings of fields in someone else’s Lexicon.