If you’re building a an Atmospheric (AT Protocol) app you may have designed your own lexicon for storing you app’s data.

When I first started out I found it tricky to publish them “properly”, even to know what spec-compliant publishing was.

This is a quick explainer & guide for publishing your lexicon, as a one-off or setting it up in your repo’s CI so you never have to think about it again.

Why would you want this? Once your lexicon is published it'll appear on sites like lexicon.garden, can be found on PDS.ls and similar, and PDS validation will work when your records are written!

The process

Recently I published the me.byjp.emojify.suggestEmoji lexicon; to publish it I had to:

  1. 1.

    Put a com.atproto.lexicon.schema atproto record in my account, holding the lexicon schema

    1. 1.

      I published to my @byjp.me account, but the account handle doesn’t need to match the NSID (Bluesky’s don’t!)

  2. 2.

    Have a DNS TXT record, on the domain matching the lexicon, pointing to that atproto account's DID

    1. 1.

      It must be “all but the last segment” of the lexicon,me.byjp.emojify for me, reversed and with _lexicon. in front—so I had to create a TXT record at _lexicon.emojify.byjp.me (see it here)

    2. 2.

      The record has to be the DID of the account. For me that's did=did:plc:ephkzpinhaqcabtkugtbzrwu — my account (I look it up on PDS.ls). You can't use your handle

    3. 3.

      Note: a record on _lexicon.byjp.me (ie. 'one level up') won’t work. If your app has categories of lexicon (eg. app.bsky.actor.profile + app.bsky.feed.post) you need multiple DNS entries (eg. _lexicon.actor.bsky.app, _lexicon.feed.bsky.app)

It's only those two steps!

Tools

Goat for publishing lexicons to your PDS, at-SDP for publishing your lexicons to a static website that acts as a PDS thanks to the power of .

Both of these tools need you to have set up your _lexicon DNS record; naturally that's super easy on , the atmospheric domain registrar!

Goat

goat (github) is a superb CLI tool for managing atproto things, and it can publish lexicon files to your atproto account:

# Publish the lexicon JSON files to your atproto account
goat lex publish \
  --username byjp.me \
  --password $ACCOUNT_APP_PASSWORD \
  ./lexicons/me/byjp/emojify/*.json

It will:

  • Tell you if your DNS record isn't in place

  • Check the validity of your lexicons

  • Tell you if you're going to change a lexicon

The last may seem counter intuitive, but lexicons aren't meant to change once published. The assumption being that once you've published the lexicon it's likely that someone will publish records that fit that schema, if you change the schema then their record becomes invalid.

So by default goat will stop you from publishing updates. If your update is forward and backward compatible (or if you know no-one has used your lexicon yet) you can use the -u flag to update.

at-SDP

at-sdp (tangled) is my own project; it builds a Statically Defined PDS out of your lexicons (and other JSON atproto records), including all that's needed for a did:web:<your-domain> as your account.

This is a great fit for a side-project where you don't expect to interactively use the atproto account that publishes the lexicons, and for "fire and forget" CI workflows.

With at-SDP you don't need an account on a PDS, a password, or anythng beyond your site deployed with wisp.place.

To set things up you initialize your account with at-sdp init --domain <your-domain>. This makes an at-sdp.json (which you should keep in your repo, if you're planning on doing this in CI).

Then, when you're ready to publish your website (eg. in ./public) with wisp-cli:

# Build a PDS from lexicons & optionally any other records
# and add the files to your existing site in ./public
at-sdp build \
  --out ./public \
  ./lexicons/me/byjp/emojify \
  ./atproto/bluesky/profile.json

# Publish your site with wisp.place
wisp-cli deploy emojify.byjp.me \
  --path ./public \
  --site emojify \
  --password "$ACCOUNT_APP_PASSWORD" \
  --yes

# Wait for your PDS to be live
at-sdp check --site emojify.byjp.me

# Notify the relays (bluesky and blacksky by default)
# that new records have been published
at-sdp notify

I want to make building on atproto as easy as possible for folks, so if you have questions, please on Bluesky and I'll see if I can help.