# Publishing ATProto Lexicons Created: [[2025_05_07]] 07:53 Tags: [[ATProto]] ATProto has recently updated their specs to include a way to [publish Lexicons in a similar way to their handle resolution](https://atproto.com/specs/lexicon#lexicon-publication-and-resolution). This is fantastic, since it allows for validating whether a record actually conforms to the lexicon's specification, by resolving the authority of that lexicon (i.e. the one who made it). I've made a few lexicons for my ATProto application, [BookHive](https://bookhive.buzz), and found it difficult to know exactly how to publish the lexicons that I've written. So, I decided to write this guide to help the next person who runs into this. ATProto is a very fast moving field, so sometimes the documentation can be somewhat lacking. ## How it works To understand how to publish Lexicons, we first should understand how the resolution of them works, so we know the right places to put the data. Let's start with an example Lexicon: ```json { "lexicon": 1, "id": "buzz.bookhive.defs", "defs": { "finished": { "type": "token", "description": "User has finished reading the book" } } } ``` This Lexicon describes some constant definitions for [BookHive](https://bookhive.buzz), you'll notice that the `id` is actually a "reverse-DNS identifier" + "name" which ATProto refers to as [an NSID](https://atproto.com/specs/nsid). In my case my app's domain is `bookhive.buzz`, so I reversed it, and added the name of the Lexicon I wanted `defs`. This being a domain name is really handy for understanding the authority that "owns" the Lexicon. To make certain that a Lexicon is really owned by that domain, the owners can "publish" their Lexicon in a publicly accessible and consistent way, which ATProto has called "Lexicon resolution". ### Lexicon Resolution Lexicon Resolution requires 2 things, control of the domain which the Lexicon's NSID refer to (e.g. `bookhive.buzz` in the example above), and for the Lexicon to be published in some account's PDS (where your account data is stored in ATProto). The process of Lexicon resolution is actually pretty simple: 1. Given a Lexicon NSID (`buzz.bookhive.defs`), remove the `name` part, to derive the Domain authority (which was in reverse-DNS notation). - So, we'd end up with `bookhive.buzz` 2. Look for a DNS TXT record at `_lexicon.<domain authority>`, just like [handle resolution](https://atproto.com/guides/identity#handle-resolution), which should have published `did=<account did>` to point to the account which holds the Lexicon schemas. - So, at `_lexicon.bookhive.buzz` there is a TXT record that stores `did=did:plc:enu2j5xjlqsjaylv3du4myh4` (which is the `did` of the [@bookhive.buzz](https://bsky.app/profile/bookhive.buzz) account) 3. In the account's PDS, there should be a collection `com.atproto.lexicon.schema` which stores the schema's NSID as the record key (`rkey`) and the value is the Lexicon schema with `"$type": "com.atproto.lexicon.schema", "lexicon": 1` added to the schema object. - So, at `at://did:plc:enu2j5xjlqsjaylv3du4myh4/com.atproto.lexicon.schema/buzz.bookhive.defs` it will contain the Lexicon schema: ```json { "lexicon": 1, "id": "buzz.bookhive.defs", "defs": { "finished": { "type": "token", "description": "User has finished reading the book" } }, "$type": "com.atproto.lexicon.schema", "lexicon": 1 } ``` And, that is it! If all those steps go right, the Lexicon schema can be fetched and used to validate if the record is "valid" according to the authority that created it. ## How to publish Okay, we've discussed how it works. But, how do we actually set all of this up so I can have my Lexicons published? Alright, here are the commands you'll need. 1. You'll want to install the [GOAT CLI](https://github.com/bluesky-social/indigo/tree/main/cmd/goat)which can easily publish your Lexicons for you - `go install github.com/bluesky-social/indigo/cmd/goat@latest` 2. Login to the account that you want to publish the Lexicons to (I suggest making an account for your app to make it not tied to you, but only a recommendation) - `goat account login --username bookhive.buzz --app-password password --pds-host https://bluesky.nickthesick.com` - PDS host is only required if you are self-hosting, if not it can be omitted to default to Bluesky's PDS hosting 3. Take the Lexicons that you want to publish into your PDS, and publish them onto there: - `goat lex publish ./lexicons/defs.json` At this point, all of the Lexicons should have been published, you can use a tool for browsing PDS data like [ATProto-Browser](https://atproto-browser.vercel.app/) to double check that it was updated properly. For example to look at BookHive's published lexicons you can visit <https://atproto-browser.vercel.app/at/did:plc:enu2j5xjlqsjaylv3du4myh4/com.atproto.lexicon.schema> We aren't done yet, we now need to update our DNS records to include a new TXT record which declares our account's DID to be the authority to check Lexicons against for validity. | Type | Name | Content | | ---- | ------------------------ | -------------------------------------- | | TXT | `_lexicon.<domain>` | `did=<account-did>` | | TXT | `_lexicon.bookhive.buzz` | `did=did:plc:enu2j5xjlqsjaylv3du4myh4` | This varies on your DNS provider, so I can't be of much help here. I hope this was helpful to you! ## References - [ATProto Specification](https://atproto.com/) - [Lexicon Publication and Resolution](https://atproto.com/specs/lexicon#lexicon-publication-and-resolution) - [Handle Resolution](https://atproto.com/guides/identity#handle-resolution) - [GOAT CLI](https://github.com/bluesky-social/indigo/tree/main/cmd/goat) - [A helpful thread](https://bsky.app/profile/nickthesick.com/post/3lojlucj5rk2f) with [@baileytownsend.dev](https://bsky.app/profile/baileytownsend.dev)